diff --git a/config.py b/config.py index c5929f3..a0e6233 100644 --- a/config.py +++ b/config.py @@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.742" +SYSTEM_VERSION = "V10.743" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/docs/AI_INTELLIGENCE_MODULE_SOT.md b/docs/AI_INTELLIGENCE_MODULE_SOT.md index 97da548..4060bf6 100644 --- a/docs/AI_INTELLIGENCE_MODULE_SOT.md +++ b/docs/AI_INTELLIGENCE_MODULE_SOT.md @@ -1,8 +1,8 @@ # PChome 業績成長自動化作戰系統 — AI 競價情報模組 Single Source of Truth > **最後更新**: 2026-07-03 (台北時間) -> **狀態**: 🟢 四 AI Agent 自動化閉環已落地;LLM 路由紅線升級為 Ollama-first 三主機級聯;PChome 後台業績匯入韌性已補強;產品定位正名為「PChome 業績成長自動化作戰系統」;外部市場來源正規化層、自動同步、作戰清單與價格參考表優先讀取、CSV 備援預檢、前台操作入口、高可見頁面繁中化守門、比價/作戰 UI 工作台化、AI 密集工作台文字密度守門、跨平台來源治理、商品身份 UI 契約、sitewide visual QA runtime readback、PChome auto-policy authorization guard monitoring、decision preflight machine evidence monitoring、decision closeout monitoring、authorization issuer gate monitoring、signing decision preflight monitoring、signing decision closeout monitoring、signing issuer guard monitoring、signing issuer closeout monitoring、signing execution preflight monitoring、signing execution closeout monitoring、signed receipt preflight monitoring、signed receipt closeout monitoring、signed receipt evidence intake monitoring、detached verification evidence validation monitoring、verifier receipt closeout monitoring、authorization evidence execution preflight monitoring 與 authorization evidence execution closeout monitoring 已建立,GCP embedding 熔斷延後處理、110 proxy rescue 與 direct host health skip 已建立 -> **適用版本**: V10.742 +> **狀態**: 🟢 四 AI Agent 自動化閉環已落地;LLM 路由紅線升級為 Ollama-first 三主機級聯;PChome 後台業績匯入韌性已補強;產品定位正名為「PChome 業績成長自動化作戰系統」;外部市場來源正規化層、自動同步、作戰清單與價格參考表優先讀取、CSV 備援預檢、前台操作入口、高可見頁面繁中化守門、比價/作戰 UI 工作台化、AI 密集工作台文字密度守門、跨平台來源治理、商品身份 UI 契約、sitewide visual QA runtime readback、PChome auto-policy authorization guard monitoring、decision preflight machine evidence monitoring、decision closeout monitoring、authorization issuer gate monitoring、signing decision preflight monitoring、signing decision closeout monitoring、signing issuer guard monitoring、signing issuer closeout monitoring、signing execution preflight monitoring、signing execution closeout monitoring、signed receipt preflight monitoring、signed receipt closeout monitoring、signed receipt evidence intake monitoring、detached verification evidence validation monitoring、verifier receipt closeout monitoring、authorization evidence execution preflight monitoring、authorization evidence execution closeout monitoring 與 controlled apply final preflight monitoring 已建立,GCP embedding 熔斷延後處理、110 proxy rescue 與 direct host health skip 已建立 +> **適用版本**: V10.743 --- @@ -868,6 +868,7 @@ POSTGRES_HOST=momo-db | 2026-07-03 | PChome auto-policy verifier receipt closeout 必須有 runtime monitoring | V10.740 起 `/api/ai-automation/smoke`、`/api/ai-automation/scheduled-health-summary` 與 `/metrics` 必須輸出 `PChome auto-policy verifier receipt closeout` / `pchome_auto_policy_verifier_receipt_closeout`;此 runtime check 自動驗證 verifier receipt evidence handoff、12 個 handoff fields、10 個 handoff acceptance gates、12 個 closeout checks、12 個 detached verification evidence validation checks、10 個 required external receipt evidence、8 個 external receipt acceptance gates、post-apply verifier requirement 與 same-run production truth requirement,但明確標記 `reads_secret_count=0`、`executes_script_count=0`、`executes_sql_count=0`、`writes_database_count=0`、`signs_database_apply_authorization_count=0`、`primary_human_gate_count=0`、`requires_detached_signature_verification=true`、`detached_signature_verification_performed=false`、`verifier_receipt_persisted=false`、`external_signed_authorization_receipt_included=false`、`signed_authorization_receipt_included=false`、`signature_material_included=false`、`accepts_plaintext_secret=false`、`ready_for_database_apply_now=false`、`issues_database_apply_authorization=false`、`signs_database_apply_authorization=false`、`secret_material_included=false`、`secret_material_required_in_preview=false`,不讀 secret、不執行 detached verification、不持久化 verifier receipt、不執行 SQL、不寫 DB、不含 signed receipt 或 signature material、不接受 plaintext secret、不簽發 apply authorization。 | | 2026-07-03 | PChome auto-policy authorization evidence execution preflight 必須有 runtime monitoring | V10.741 起 `/api/ai-automation/smoke`、`/api/ai-automation/scheduled-health-summary` 與 `/metrics` 必須輸出 `PChome auto-policy authorization evidence execution preflight` / `pchome_auto_policy_authorization_evidence_execution_preflight`;此 runtime check 自動驗證 future database apply authorization verifier handoff、authorization evidence execution preflight package、12 個 preflight checks、12 個 execution fields、10 個 execution acceptance gates、verifier receipt closeout carry-forward、same-run production truth requirement 與 post-apply verifier requirement,但明確標記 `reads_secret_count=0`、`executes_script_count=0`、`executes_sql_count=0`、`writes_database_count=0`、`signs_database_apply_authorization_count=0`、`primary_human_gate_count=0`、`executes_authorization_evidence=false`、`requires_detached_signature_verification=true`、`detached_signature_verification_performed=false`、`verifier_receipt_persisted=false`、`external_signed_authorization_receipt_included=false`、`signed_authorization_receipt_included=false`、`signature_material_included=false`、`accepts_plaintext_secret=false`、`ready_for_database_apply_now=false`、`issues_database_apply_authorization=false`、`signs_database_apply_authorization=false`、`secret_material_included=false`、`secret_material_required_in_preview=false`,不讀 secret、不執行 authorization evidence、不執行 detached verification、不持久化 verifier receipt、不執行 SQL、不寫 DB、不含 signed receipt 或 signature material、不接受 plaintext secret、不簽發 apply authorization。 | | 2026-07-03 | PChome auto-policy authorization evidence execution closeout 必須有 runtime monitoring | V10.742 起 `/api/ai-automation/smoke`、`/api/ai-automation/scheduled-health-summary` 與 `/metrics` 必須輸出 `PChome auto-policy authorization evidence execution closeout` / `pchome_auto_policy_authorization_evidence_execution_closeout`;此 runtime check 自動驗證 future database apply authorization final verifier gate、authorization evidence execution closeout package、12 個 closeout checks、12 個 closeout fields、10 個 closeout acceptance gates、authorization evidence execution preflight carry-forward、verifier receipt closeout carry-forward、same-run production truth requirement 與 post-apply verifier requirement,但明確標記 `reads_secret_count=0`、`executes_script_count=0`、`executes_sql_count=0`、`writes_database_count=0`、`signs_database_apply_authorization_count=0`、`primary_human_gate_count=0`、`executes_authorization_evidence=false`、`executes_database_apply=false`、`database_apply_authorized=false`、`requires_detached_signature_verification=true`、`detached_signature_verification_performed=false`、`verifier_receipt_persisted=false`、`external_signed_authorization_receipt_included=false`、`signed_authorization_receipt_included=false`、`signature_material_included=false`、`accepts_plaintext_secret=false`、`ready_for_database_apply_now=false`、`issues_database_apply_authorization=false`、`signs_database_apply_authorization=false`、`secret_material_included=false`、`secret_material_required_in_preview=false`,不讀 secret、不執行 authorization evidence、不執行 database apply、不執行 detached verification、不持久化 verifier receipt、不執行 SQL、不寫 DB、不含 signed receipt 或 signature material、不接受 plaintext secret、不簽發 apply authorization。 | +| 2026-07-03 | PChome auto-policy controlled apply final preflight 必須有 runtime monitoring | V10.743 起 `/api/ai-automation/smoke`、`/api/ai-automation/scheduled-health-summary` 與 `/metrics` 必須輸出 `PChome auto-policy controlled apply final preflight` / `pchome_auto_policy_controlled_apply_final_preflight`;此 runtime check 自動驗證 authorization evidence execution closeout carry-forward、database apply authorization final verifier gate、controlled apply final preflight package、rollback binding、post-apply verifier binding、12 個 final preflight checks、12 個 preflight fields、10 個 acceptance gates、dry-run-only、check-mode-only、same-run production truth requirement 與 future controlled dry-run package readiness,但明確標記 `reads_secret_count=0`、`executes_script_count=0`、`executes_sql_count=0`、`writes_database_count=0`、`signs_database_apply_authorization_count=0`、`primary_human_gate_count=0`、`rollback_execution_authorized=false`、`rollback_executes_sql=false`、`rollback_writes_database=false`、`post_apply_verifier_execution_authorized_in_preview=false`、`executes_database_apply=false`、`ready_for_database_apply_now=false`、`database_apply_authorized=false`、`issues_database_apply_authorization=false`、`signs_database_apply_authorization=false`,不讀 secret、不執行 authorization evidence、不執行 database apply、不執行 endpoint/SQL、不寫 DB、不簽發 apply authorization,只允許進入下一段 controlled dry-run package lane。 | | 2026-06-29 | PChome DB apply 授權 lane 必須先通過 no-write guard / decision preflight / decision closeout / issuer gate / signing-decision preflight / signing-decision closeout / signing-issuer guard | V10.725 的 PChome mapping backlog auto-policy 已新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-lane-guard`、`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-decision-preflight`、`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-decision-closeout`、`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-issuer-gate`、`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-decision-preflight`、`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-decision-closeout` 與 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-issuer-guard`;這些 endpoint 只驗證 final exact request package、same-run production truth requirement、secret rejection、rollback boundary、lane entry requirements、decision input requirements、rejection policy、post-apply verifier、future authorization decision package、final nonsecret authorization envelope、signing decision preflight inputs、unsigned signing decision package 與 signable request boundary,不讀 secret、不執行 shell/SQL、不寫 DB,也不簽發 database apply authorization。 | | 2026-06-29 | PChome DB apply 授權簽署發行者 lane 必須先產出 final signable request package | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-issuer-closeout`;此 endpoint 只把 signing-issuer guard 的 signable request boundary 收斂成 final signable request package 與 closeout contract,確認 fresh production truth、post-apply verifier、migration hash、secret boundary 與 no-side-effect checks,不讀 secret、不簽發 authorization、不執行 shell/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | | 2026-06-29 | PChome DB apply 授權簽署執行 lane 必須先通過 operator-held secret boundary preflight | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-execution-preflight`;此 endpoint 只把 final signable request package 轉成 future signing execution preflight package、operator-held secret boundary contract、nonsecret signing inputs、command-shape preview、rollback boundary 與 abort conditions,不讀 secret、不接受 plaintext secret、不簽發 authorization、不執行 shell/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | diff --git a/docs/guides/pchome_ai_automation_priority_backlog.md b/docs/guides/pchome_ai_automation_priority_backlog.md index aac5320..0ab2f4c 100644 --- a/docs/guides/pchome_ai_automation_priority_backlog.md +++ b/docs/guides/pchome_ai_automation_priority_backlog.md @@ -9,7 +9,7 @@ 以下是使用者在主線推進期間插入、且必須保留在完整工作項目裡的要求。後續不得再把這些需求漏掉或變成口頭提醒。 1. 正式環境才是最新版本真相;不得用本機檔案、舊筆記、舊分支蓋過 production truth。 -2. 版本不得搞錯;目前正式環境 `/health` 版本推進目標為 `V10.742`,完成條件必須包含明確版本 bump、部署與 readback。 +2. 版本不得搞錯;目前正式環境 `/health` 版本推進目標為 `V10.743`,完成條件必須包含明確版本 bump、部署與 readback。 3. GitHub 全面 freeze;不得使用 GitHub、`gh`、GitHub API、GitHub Actions、PR、issue、mirror 或 read-only GitHub 流程。 4. 實作結果必須推到 Gitea `dev` 與 `main`;若改到 runtime 行為,還必須部署到正式環境並回讀。 5. 主方向是 AI 自動化,不是人工審核;人工欄位只能當 evidence / ledger / UI truth,不得阻擋低爆炸半徑、可驗證、可回滾的 controlled apply。 @@ -41,7 +41,7 @@ 已完成: -- 正式環境版本真相 guard:`/health` 是最高真相,本輪推進目標為 `V10.742`。 +- 正式環境版本真相 guard:`/health` 是最高真相,本輪推進目標為 `V10.743`。 - Gitea `dev` / `main` source truth 對齊。 - Retry exception controlled apply executor 已落地,目標表為 `pchome_product_matches`。 - 正式 DB 已 controlled apply 4 個 selectors: @@ -204,10 +204,17 @@ - `/api/ai-automation/scheduled-health-summary` 已輸出 `pchome_auto_policy_authorization_evidence_execution_closeout` family - `/metrics` 已輸出 `momo_ai_automation_scheduled_health_family_status{family="pchome_auto_policy_authorization_evidence_execution_closeout",...}` - 不執行 authorization evidence、不執行 database apply、不執行 detached signature verification、不持久化 verifier receipt、不包含 external signed receipt、不包含 signed receipt、不包含 signature material、不接受 plaintext secret、不讀 secret、不執行 SQL、不寫 DB、不簽發 apply authorization,下一個機器動作是 controlled-apply final preflight lane +- PChome auto-policy controlled-apply final preflight runtime monitoring 已完成: + - `/api/ai-automation/smoke` 已加入 `PChome auto-policy controlled apply final preflight` + - 以本機 contract fixture + machine evidence adapter 自動推進 controlled dry-run package path + - 明確標記 `controlled_apply_final_preflight_check_count=12`、`controlled_apply_final_preflight_pass_count=12`、`controlled_apply_final_preflight_field_count=12`、`controlled_apply_final_preflight_acceptance_gate_count=10`、`rollback_binding_count=1`、`rollback_binding_field_count=8`、`post_apply_verifier_binding_count=1`、`post_apply_verifier_binding_field_count=8`、`dry_run_only=true`、`check_mode_only=true`、`writes_database_count=0`、`signs_database_apply_authorization_count=0`、`reads_secret_count=0`、`primary_human_gate_count=0` + - `/api/ai-automation/scheduled-health-summary` 已輸出 `pchome_auto_policy_controlled_apply_final_preflight` family + - `/metrics` 已輸出 `momo_ai_automation_scheduled_health_family_status{family="pchome_auto_policy_controlled_apply_final_preflight",...}` + - 不執行 authorization evidence、不執行 database apply、不執行 endpoint/SQL、不寫 DB、不讀 secret、不簽發 apply authorization,下一個機器動作是 controlled dry-run package lane 進行中 / 下一步,必須照順序: -1. 將 auto-policy authorization evidence execution closeout 往 controlled-apply final preflight lane 推進。 +1. 將 auto-policy controlled apply final preflight 往 controlled dry-run package lane 推進。 完成標準: @@ -374,7 +381,7 @@ 進行中 / 下一步,必須照順序: -1. 將 auto-policy authorization evidence execution closeout 推進到 controlled-apply final preflight lane。 +1. 將 auto-policy controlled apply final preflight 推進到 controlled dry-run package lane。 2. 對其他 safe mapping backlog lanes 套用 receipt / replay / drift verifier 模式。 3. 為每一類 controlled apply 自動產生 rollback evidence package。 4. 把 AI automation metrics 持續整合進既有 observability surfaces。 @@ -463,6 +470,7 @@ | P3.19 | PChome auto-policy verifier receipt closeout monitoring | 已完成 | AI smoke + scheduled family + metrics family prove verifier receipt evidence handoff, 12 handoff fields, 10 handoff acceptance gates, no detached verification execution, no verifier receipt persist, no signed receipt, no signature material, no plaintext secret, no secret read, no DB write, no signing | P3.20 authorization evidence execution preflight monitoring | | P3.20 | PChome auto-policy authorization evidence execution preflight monitoring | 已完成 | AI smoke + scheduled family + metrics family prove database apply authorization verifier handoff, 12 execution fields, 10 execution acceptance gates, no authorization evidence execution, no detached verification execution, no verifier receipt persist, no signed receipt, no signature material, no plaintext secret, no secret read, no DB write, no signing | P3.21 authorization evidence execution closeout monitoring | | P3.21 | PChome auto-policy authorization evidence execution closeout monitoring | 已完成 | AI smoke + scheduled family + metrics family prove database apply authorization final verifier gate, 12 closeout fields, 10 closeout acceptance gates, no authorization evidence execution, no database apply, no detached verification execution, no verifier receipt persist, no signed receipt, no signature material, no plaintext secret, no secret read, no DB write, no signing | P3.22 controlled-apply final preflight monitoring | +| P3.22 | PChome auto-policy controlled-apply final preflight monitoring | 已完成 | AI smoke + scheduled family + metrics family prove rollback binding, post-apply verifier binding, 12 final preflight fields, 10 acceptance gates, dry-run-only/check-mode-only, no authorization evidence execution, no database apply, no endpoint/SQL, no secret read, no DB write, no signing | P3.23 controlled dry-run package monitoring | | P4.1 | Source / deployment / runtime truth package | 已完成 | `scripts/ops/report_source_deploy_runtime_truth.py` + focused tests | 每次 Gitea push / production deploy 後執行 P4 report | | P2.2 | Benchmark guardrails applied to core AI surfaces | 已完成 | `/ai_intelligence` + `/observability/overview` golden-signal strips + focused tests | 後續 safe automation lanes 套同樣 first-viewport summary | diff --git a/services/ai_automation_smoke_service.py b/services/ai_automation_smoke_service.py index 4665731..ff3773e 100644 --- a/services/ai_automation_smoke_service.py +++ b/services/ai_automation_smoke_service.py @@ -450,6 +450,23 @@ def build_scheduled_automation_health_summary( auto_policy_authorization_evidence_closeout_details = ( auto_policy_authorization_evidence_closeout.get("details") or {} ) + auto_policy_controlled_apply_final_preflight = _find_check( + source_result, + "PChome auto-policy controlled apply final preflight", + ) + auto_policy_controlled_apply_final_preflight_details = ( + auto_policy_controlled_apply_final_preflight.get("details") or {} + ) + if ( + not auto_policy_controlled_apply_final_preflight + or not auto_policy_controlled_apply_final_preflight_details + ): + auto_policy_controlled_apply_final_preflight = ( + _pchome_auto_policy_controlled_apply_final_preflight_check() + ) + auto_policy_controlled_apply_final_preflight_details = ( + auto_policy_controlled_apply_final_preflight.get("details") or {} + ) surface_readback = _find_check(source_result, "AI surface HTML readback") surface_details = surface_readback.get("details") or {} sitewide_readback = _find_check(source_result, "Sitewide UI/UX Agent readback") @@ -3908,6 +3925,396 @@ def build_scheduled_automation_health_summary( "writes_database": False, }, }, + { + "key": "pchome_auto_policy_controlled_apply_final_preflight", + "label": "PChome auto-policy controlled apply final preflight", + "status": ( + auto_policy_controlled_apply_final_preflight.get("status") + or "warning" + ), + "summary": ( + auto_policy_controlled_apply_final_preflight.get("summary") + or ( + "PChome auto-policy controlled apply final preflight " + "has no latest check." + ) + ), + "next_machine_action": ( + auto_policy_controlled_apply_final_preflight_details.get( + "next_machine_action" + ) + or ( + "continue_pchome_auto_policy_controlled_apply_final_preflight_monitoring" + if auto_policy_controlled_apply_final_preflight.get("status") + == "ok" + else "refresh_pchome_auto_policy_controlled_apply_final_preflight" + ) + ), + "details": { + "policy": auto_policy_controlled_apply_final_preflight_details.get( + "policy" + ), + "result": auto_policy_controlled_apply_final_preflight_details.get( + "result" + ), + "controlled_apply_final_preflight_ready_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "controlled_apply_final_preflight_ready_count" + ) + or 0 + ), + "controlled_apply_final_preflight_check_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "controlled_apply_final_preflight_check_count" + ) + or 0 + ), + "controlled_apply_final_preflight_pass_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "controlled_apply_final_preflight_pass_count" + ) + or 0 + ), + "controlled_apply_final_preflight_waiting_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "controlled_apply_final_preflight_waiting_count" + ) + or 0 + ), + "authorization_evidence_execution_closeout_ready_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "authorization_evidence_execution_closeout_ready_count" + ) + or 0 + ), + "authorization_evidence_execution_closeout_check_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "authorization_evidence_execution_closeout_check_count" + ) + or 0 + ), + "authorization_evidence_execution_preflight_ready_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "authorization_evidence_execution_preflight_ready_count" + ) + or 0 + ), + "authorization_evidence_execution_preflight_check_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "authorization_evidence_execution_preflight_check_count" + ) + or 0 + ), + "authorization_verifier_receipt_closeout_ready_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "authorization_verifier_receipt_closeout_ready_count" + ) + or 0 + ), + "verifier_receipt_closeout_check_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "verifier_receipt_closeout_check_count" + ) + or 0 + ), + "database_apply_final_verifier_gate_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "database_apply_final_verifier_gate_count" + ) + or 0 + ), + "database_apply_authorization_final_verifier_gate_ready_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "database_apply_authorization_final_verifier_gate_ready_count" + ) + or 0 + ), + "controlled_apply_final_preflight_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "controlled_apply_final_preflight_count" + ) + or 0 + ), + "controlled_apply_final_preflight_field_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "controlled_apply_final_preflight_field_count" + ) + or 0 + ), + "controlled_apply_final_preflight_acceptance_gate_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "controlled_apply_final_preflight_acceptance_gate_count" + ) + or 0 + ), + "rollback_binding_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "rollback_binding_count" + ) + or 0 + ), + "rollback_binding_field_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "rollback_binding_field_count" + ) + or 0 + ), + "post_apply_verifier_binding_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "post_apply_verifier_binding_count" + ) + or 0 + ), + "post_apply_verifier_binding_field_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "post_apply_verifier_binding_field_count" + ) + or 0 + ), + "authorization_evidence_execution_closeout_field_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "authorization_evidence_execution_closeout_field_count" + ) + or 0 + ), + "authorization_evidence_execution_closeout_acceptance_gate_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "authorization_evidence_execution_closeout_acceptance_gate_count" + ) + or 0 + ), + "post_apply_verifier_required_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "post_apply_verifier_required_count" + ) + or 0 + ), + "same_run_truth_required_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "same_run_truth_required_count" + ) + or 0 + ), + "reads_secret_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "reads_secret_count" + ) + or 0 + ), + "executes_script_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "executes_script_count" + ) + or 0 + ), + "executes_endpoint_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "executes_endpoint_count" + ) + or 0 + ), + "executes_sql_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "executes_sql_count" + ) + or 0 + ), + "writes_database_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "writes_database_count" + ) + or 0 + ), + "signs_database_apply_authorization_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "signs_database_apply_authorization_count" + ) + or 0 + ), + "primary_human_gate_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "primary_human_gate_count" + ) + or 0 + ), + "manual_review_required_count": int( + auto_policy_controlled_apply_final_preflight_details.get( + "manual_review_required_count" + ) + or 0 + ), + "manual_review_mode": ( + auto_policy_controlled_apply_final_preflight_details.get( + "manual_review_mode" + ) + ), + "payload_source": auto_policy_controlled_apply_final_preflight_details.get( + "payload_source" + ), + "execute_fetch": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "execute_fetch" + ) + ), + "outbound_network": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "outbound_network" + ) + ), + "business_data_source": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "business_data_source" + ) + ), + "ready_for_future_database_apply_controlled_apply_final_preflight": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "ready_for_future_database_apply_controlled_apply_final_preflight" + ) + ), + "can_enter_future_database_apply_controlled_dry_run_package": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "can_enter_future_database_apply_controlled_dry_run_package" + ) + ), + "controlled_apply_final_preflight_ready": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "controlled_apply_final_preflight_ready" + ) + ), + "rollback_binding_ready": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "rollback_binding_ready" + ) + ), + "post_apply_verifier_binding_ready": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "post_apply_verifier_binding_ready" + ) + ), + "rollback_bound": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "rollback_bound" + ) + ), + "post_apply_verifier_bound": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "post_apply_verifier_bound" + ) + ), + "dry_run_only": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "dry_run_only" + ) + ), + "check_mode_only": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "check_mode_only" + ) + ), + "rollback_execution_authorized": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "rollback_execution_authorized" + ) + ), + "rollback_executes_sql": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "rollback_executes_sql" + ) + ), + "rollback_writes_database": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "rollback_writes_database" + ) + ), + "post_apply_verifier_execution_authorized_in_preview": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "post_apply_verifier_execution_authorized_in_preview" + ) + ), + "permits_future_database_apply_controlled_dry_run_package": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "permits_future_database_apply_controlled_dry_run_package" + ) + ), + "accepts_plaintext_secret": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "accepts_plaintext_secret" + ) + ), + "reads_secret_in_preview": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "reads_secret_in_preview" + ) + ), + "signature_material_included": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "signature_material_included" + ) + ), + "secret_material_included": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "secret_material_included" + ) + ), + "secret_material_required_in_preview": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "secret_material_required_in_preview" + ) + ), + "executes_authorization_evidence": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "executes_authorization_evidence" + ) + ), + "executes_database_apply": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "executes_database_apply" + ) + ), + "executes_endpoint": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "executes_endpoint" + ) + ), + "executes_sql": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "executes_sql" + ) + ), + "ready_for_database_apply_now": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "ready_for_database_apply_now" + ) + ), + "database_apply_authorized": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "database_apply_authorized" + ) + ), + "issues_database_apply_authorization": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "issues_database_apply_authorization" + ) + ), + "signs_database_apply_authorization": bool( + auto_policy_controlled_apply_final_preflight_details.get( + "signs_database_apply_authorization" + ) + ), + "next_machine_action": ( + auto_policy_controlled_apply_final_preflight_details.get( + "next_machine_action" + ) + or ( + "continue_pchome_auto_policy_controlled_apply_final_preflight_monitoring" + if auto_policy_controlled_apply_final_preflight.get("status") + == "ok" + else "refresh_pchome_auto_policy_controlled_apply_final_preflight" + ) + ), + "writes_database": False, + }, + }, { "key": "ai_surface_html_readback", "label": "AI surface HTML readback", @@ -9984,6 +10391,543 @@ def _pchome_auto_policy_authorization_evidence_execution_closeout_check() -> Dic ) +def _pchome_auto_policy_controlled_apply_final_preflight_check() -> Dict[str, Any]: + """Read-only monitor for the future controlled-apply final preflight.""" + try: + from services import pchome_mapping_backlog_service as backlog + from services.ai_exception_contract import ( + LEGACY_REVIEW_REQUIRED_COUNT_KEY, + PRIMARY_HUMAN_GATE_COUNT_KEY, + ) + + payload = _pchome_auto_policy_decision_preflight_contract_payload() + preflight = ( + backlog.build_pchome_auto_policy_db_apply_controlled_apply_final_preflight( + payload, + batch_size=max( + 1, min(_PCHOME_AUTO_POLICY_DECISION_PREFLIGHT_BATCH_SIZE, 50) + ), + execute_fetch=True, + timeout_seconds=max( + 1, + min(_PCHOME_AUTO_POLICY_DECISION_PREFLIGHT_TIMEOUT_SECONDS, 30), + ), + http_get=_pchome_auto_policy_machine_fetch_evidence_get, + ) + ) + summary = preflight.get("summary") or {} + future_preflight = ( + preflight.get("future_database_apply_controlled_apply_final_preflight") + or {} + ) + final_preflight = preflight.get("controlled_apply_final_preflight") or {} + contract = preflight.get("controlled_apply_final_preflight_contract") or {} + safety = preflight.get("safety") or {} + rollback_binding = final_preflight.get("rollback_binding") or {} + verifier_binding = ( + final_preflight.get("post_apply_verifier_binding") or {} + ) + preflight_fields = list( + final_preflight.get("controlled_apply_final_preflight_fields") or [] + ) + preflight_gates = list( + final_preflight.get("controlled_apply_final_preflight_acceptance_gates") + or [] + ) + result = str(preflight.get("result") or "UNKNOWN") + + ready_count = int( + summary.get("controlled_apply_final_preflight_ready_count") or 0 + ) + check_count = int( + summary.get("controlled_apply_final_preflight_check_count") or 0 + ) + pass_count = int( + summary.get("controlled_apply_final_preflight_pass_count") or 0 + ) + waiting_count = int( + summary.get("controlled_apply_final_preflight_waiting_count") or 0 + ) + evidence_closeout_ready_count = int( + summary.get("authorization_evidence_execution_closeout_ready_count") or 0 + ) + evidence_closeout_check_count = int( + summary.get("authorization_evidence_execution_closeout_check_count") or 0 + ) + evidence_preflight_ready_count = int( + summary.get("authorization_evidence_execution_preflight_ready_count") or 0 + ) + evidence_preflight_check_count = int( + summary.get("authorization_evidence_execution_preflight_check_count") or 0 + ) + verifier_closeout_ready_count = int( + summary.get("authorization_verifier_receipt_closeout_ready_count") or 0 + ) + verifier_closeout_check_count = int( + summary.get("verifier_receipt_closeout_check_count") or 0 + ) + final_verifier_gate_count = int( + summary.get("database_apply_final_verifier_gate_count") or 0 + ) + final_verifier_gate_ready_count = int( + summary.get("database_apply_authorization_final_verifier_gate_ready_count") + or 0 + ) + final_preflight_count = int( + summary.get("controlled_apply_final_preflight_count") or 0 + ) + final_preflight_field_count = int( + summary.get("controlled_apply_final_preflight_field_count") or 0 + ) + final_preflight_gate_count = int( + summary.get("controlled_apply_final_preflight_acceptance_gate_count") or 0 + ) + rollback_binding_count = int(summary.get("rollback_binding_count") or 0) + rollback_binding_field_count = int( + summary.get("rollback_binding_field_count") or 0 + ) + verifier_binding_count = int( + summary.get("post_apply_verifier_binding_count") or 0 + ) + verifier_binding_field_count = int( + summary.get("post_apply_verifier_binding_field_count") or 0 + ) + evidence_closeout_field_count = int( + summary.get("authorization_evidence_execution_closeout_field_count") or 0 + ) + evidence_closeout_gate_count = int( + summary.get( + "authorization_evidence_execution_closeout_acceptance_gate_count" + ) + or 0 + ) + verifier_required_count = int( + summary.get("post_apply_verifier_required_count") or 0 + ) + same_run_truth_count = int(summary.get("same_run_truth_required_count") or 0) + writes_script_count = int(summary.get("writes_script_count") or 0) + writes_artifact_count = int(summary.get("writes_artifact_count") or 0) + reads_secret_count = int(summary.get("reads_secret_count") or 0) + executes_script_count = int(summary.get("executes_script_count") or 0) + executes_migration_count = int(summary.get("executes_migration_count") or 0) + executes_endpoint_count = int(summary.get("executes_endpoint_count") or 0) + executes_sql_count = int(summary.get("executes_sql_count") or 0) + writes_database_count = int(summary.get("writes_database_count") or 0) + signs_database_apply_authorization_count = int( + summary.get("signs_database_apply_authorization_count") or 0 + ) + primary_human_gate_count = int(summary.get(PRIMARY_HUMAN_GATE_COUNT_KEY) or 0) + manual_review_required_count = int( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY) or 0 + ) + side_effect_count = ( + writes_script_count + + writes_artifact_count + + reads_secret_count + + executes_script_count + + executes_migration_count + + executes_endpoint_count + + executes_sql_count + + writes_database_count + + signs_database_apply_authorization_count + ) + risk_detected = any( + [ + side_effect_count, + primary_human_gate_count, + manual_review_required_count, + safety.get("reads_secret_in_preview") is not False, + safety.get("writes_file") is not False, + safety.get("writes_script_in_preview") is not False, + safety.get("writes_artifact_in_preview") is not False, + safety.get("executes_script") is not False, + safety.get("executes_endpoint") is not False, + safety.get("executes_migration") is not False, + safety.get("executes_sql") is not False, + safety.get("writes_database") is not False, + safety.get("signs_database_apply_authorization") is not False, + safety.get("performs_detached_signature_verification") is not False, + safety.get("persists_verifier_receipt") is not False, + safety.get("executes_authorization_evidence") is not False, + safety.get("executes_database_apply") is not False, + future_preflight.get("ready_for_database_apply_now") is not False, + future_preflight.get("database_apply_authorized") is not False, + future_preflight.get("issues_database_apply_authorization") is not False, + future_preflight.get("signs_database_apply_authorization") is not False, + future_preflight.get("executes_authorization_evidence") is not False, + future_preflight.get("executes_database_apply") is not False, + future_preflight.get("executes_endpoint") is not False, + future_preflight.get("executes_sql") is not False, + future_preflight.get("writes_database") is not False, + future_preflight.get("reads_secret_in_preview") is not False, + final_preflight.get("ready_for_database_apply_now") is not False, + final_preflight.get("database_apply_authorized") is not False, + final_preflight.get("issues_database_apply_authorization") + is not False, + final_preflight.get("signs_database_apply_authorization") + is not False, + final_preflight.get("executes_authorization_evidence") is not False, + final_preflight.get("executes_database_apply") is not False, + final_preflight.get("executes_endpoint_in_preview") is not False, + final_preflight.get("executes_sql_in_preview") is not False, + final_preflight.get("writes_database_in_preview") is not False, + final_preflight.get("accepts_plaintext_secret") is not False, + final_preflight.get("reads_secret_in_preview") is not False, + final_preflight.get("signature_material_included") is not False, + final_preflight.get("secret_material_included") is not False, + final_preflight.get("secret_material_required_in_preview") + is not False, + rollback_binding.get("rollback_execution_authorized") is not False, + rollback_binding.get("rollback_executes_sql") is not False, + rollback_binding.get("rollback_writes_database") is not False, + rollback_binding.get("rollback_reads_secret") is not False, + verifier_binding.get("verifier_execution_authorized_in_preview") + is not False, + verifier_binding.get("database_apply_authorized") is not False, + contract.get("accepts_plaintext_secret") is not False, + contract.get("performs_detached_signature_verification") is not False, + contract.get("persists_verifier_receipt") is not False, + contract.get("executes_authorization_evidence") is not False, + contract.get("executes_database_apply") is not False, + contract.get("executes_endpoint") is not False, + contract.get("executes_sql") is not False, + contract.get("issues_database_apply_authorization") is not False, + contract.get("database_apply_authorized") is not False, + contract.get("ready_for_database_apply_now") is not False, + contract.get("signs_database_apply_authorization") is not False, + contract.get("writes_database") is not False, + contract.get("executes_in_preview") is not False, + contract.get("secret_material_required_in_preview") is not False, + ] + ) + final_preflight_contract_present = ( + result == "DB_APPLY_CONTROLLED_APPLY_FINAL_PREFLIGHT_READY" + and ready_count == 1 + and check_count >= 12 + and pass_count == check_count + and waiting_count == 0 + and evidence_closeout_ready_count == 1 + and evidence_closeout_check_count == 12 + and evidence_preflight_ready_count == 1 + and evidence_preflight_check_count == 12 + and verifier_closeout_ready_count == 1 + and verifier_closeout_check_count == 12 + and final_verifier_gate_count == 1 + and final_verifier_gate_ready_count == 1 + and final_preflight_count == 1 + and final_preflight_field_count == 12 + and final_preflight_gate_count == 10 + and rollback_binding_count == 1 + and rollback_binding_field_count == 8 + and verifier_binding_count == 1 + and verifier_binding_field_count == 8 + and evidence_closeout_field_count == 12 + and evidence_closeout_gate_count == 10 + and verifier_required_count == 1 + and same_run_truth_count == 1 + and "rollback_binding_id" in preflight_fields + and "post_apply_verifier_bound" in preflight_gates + and future_preflight.get( + "ready_for_future_database_apply_controlled_apply_final_preflight" + ) + is True + and future_preflight.get( + "can_enter_future_database_apply_controlled_dry_run_package" + ) + is True + and future_preflight.get("controlled_apply_final_preflight_ready") + is True + and future_preflight.get("rollback_binding_ready") is True + and future_preflight.get("post_apply_verifier_binding_ready") is True + and final_preflight.get("authorization_material_type") + == "controlled_apply_final_preflight" + and final_preflight.get( + "ready_for_future_database_apply_controlled_apply_final_preflight" + ) + is True + and final_preflight.get("dry_run_only") is True + and final_preflight.get("check_mode_only") is True + and final_preflight.get("rollback_bound") is True + and final_preflight.get("post_apply_verifier_bound") is True + and final_preflight.get("requires_fresh_production_truth_in_same_run") + is True + and final_preflight.get("requires_post_apply_verifier") is True + and final_preflight.get("hash_matches") is True + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and rollback_binding.get("rollback_reads_secret") is False + and rollback_binding.get("rollback_requires_same_run_truth") is True + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("same_run_production_truth_required") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False + and contract.get("machine_verifiable") is True + and contract.get("permits_future_database_apply_controlled_dry_run_package") + is True + and contract.get("accepts_plaintext_secret") is False + and contract.get("executes_authorization_evidence") is False + and contract.get("executes_database_apply") is False + and contract.get("executes_endpoint") is False + and contract.get("executes_sql") is False + and contract.get("writes_database") is False + and safety.get("manual_review_mode") == "exception_only" + ) + + if risk_detected: + status = "critical" + summary_text = ( + "PChome auto-policy controlled apply final preflight " + "偵測到 secret、execution、SQL、DB write、rollback execution、" + "database apply、簽發授權或人工主 gate 風險" + ) + next_machine_action = ( + "halt_pchome_auto_policy_controlled_apply_final_preflight_and_inspect_risk" + ) + elif final_preflight_contract_present: + status = "ok" + summary_text = ( + "PChome auto-policy controlled apply final preflight " + f"已自動完成 {pass_count}/{check_count} checks,rollback/verifier binding 已綁定,apply/write/sign=0" + ) + next_machine_action = ( + "continue_to_pchome_auto_policy_controlled_dry_run_package_lane" + ) + else: + status = "warning" + summary_text = ( + "PChome auto-policy controlled apply final preflight " + "尚未達自動監控契約" + ) + next_machine_action = ( + "refresh_pchome_auto_policy_controlled_apply_final_preflight" + ) + + return _check( + "PChome auto-policy controlled apply final preflight", + status, + summary_text, + { + "policy": preflight.get("policy"), + "result": result, + "source_policy": preflight.get("source_policy"), + "controlled_apply_final_preflight_ready_count": ready_count, + "controlled_apply_final_preflight_check_count": check_count, + "controlled_apply_final_preflight_pass_count": pass_count, + "controlled_apply_final_preflight_waiting_count": waiting_count, + "authorization_evidence_execution_closeout_ready_count": ( + evidence_closeout_ready_count + ), + "authorization_evidence_execution_closeout_check_count": ( + evidence_closeout_check_count + ), + "authorization_evidence_execution_preflight_ready_count": ( + evidence_preflight_ready_count + ), + "authorization_evidence_execution_preflight_check_count": ( + evidence_preflight_check_count + ), + "authorization_verifier_receipt_closeout_ready_count": ( + verifier_closeout_ready_count + ), + "verifier_receipt_closeout_check_count": ( + verifier_closeout_check_count + ), + "database_apply_final_verifier_gate_count": final_verifier_gate_count, + "database_apply_authorization_final_verifier_gate_ready_count": ( + final_verifier_gate_ready_count + ), + "controlled_apply_final_preflight_count": final_preflight_count, + "controlled_apply_final_preflight_field_count": ( + final_preflight_field_count + ), + "controlled_apply_final_preflight_acceptance_gate_count": ( + final_preflight_gate_count + ), + "rollback_binding_count": rollback_binding_count, + "rollback_binding_field_count": rollback_binding_field_count, + "post_apply_verifier_binding_count": verifier_binding_count, + "post_apply_verifier_binding_field_count": ( + verifier_binding_field_count + ), + "authorization_evidence_execution_closeout_field_count": ( + evidence_closeout_field_count + ), + "authorization_evidence_execution_closeout_acceptance_gate_count": ( + evidence_closeout_gate_count + ), + "post_apply_verifier_required_count": verifier_required_count, + "same_run_truth_required_count": same_run_truth_count, + "writes_script_count": writes_script_count, + "writes_artifact_count": writes_artifact_count, + "reads_secret_count": reads_secret_count, + "executes_script_count": executes_script_count, + "executes_migration_count": executes_migration_count, + "executes_endpoint_count": executes_endpoint_count, + "executes_sql_count": executes_sql_count, + "writes_database_count": writes_database_count, + "signs_database_apply_authorization_count": ( + signs_database_apply_authorization_count + ), + "primary_human_gate_count": primary_human_gate_count, + "manual_review_required_count": manual_review_required_count, + "manual_review_mode": safety.get("manual_review_mode"), + "payload_source": "local_contract_fixture", + "machine_fetch_evidence_source": "local_schema_fixture", + "http_get_adapter": "_pchome_auto_policy_machine_fetch_evidence_get", + "execute_fetch": True, + "outbound_network": False, + "business_data_source": False, + "ready_for_future_database_apply_controlled_apply_final_preflight": bool( + future_preflight.get( + "ready_for_future_database_apply_controlled_apply_final_preflight" + ) + ), + "can_enter_future_database_apply_controlled_dry_run_package": bool( + future_preflight.get( + "can_enter_future_database_apply_controlled_dry_run_package" + ) + ), + "controlled_apply_final_preflight_ready": bool( + future_preflight.get("controlled_apply_final_preflight_ready") + ), + "rollback_binding_ready": bool( + future_preflight.get("rollback_binding_ready") + ), + "post_apply_verifier_binding_ready": bool( + future_preflight.get("post_apply_verifier_binding_ready") + ), + "rollback_bound": bool(final_preflight.get("rollback_bound")), + "post_apply_verifier_bound": bool( + final_preflight.get("post_apply_verifier_bound") + ), + "dry_run_only": bool(final_preflight.get("dry_run_only")), + "check_mode_only": bool(final_preflight.get("check_mode_only")), + "rollback_execution_authorized": bool( + rollback_binding.get("rollback_execution_authorized") + ), + "rollback_executes_sql": bool( + rollback_binding.get("rollback_executes_sql") + ), + "rollback_writes_database": bool( + rollback_binding.get("rollback_writes_database") + ), + "rollback_reads_secret": bool( + rollback_binding.get("rollback_reads_secret") + ), + "rollback_requires_same_run_truth": bool( + rollback_binding.get("rollback_requires_same_run_truth") + ), + "post_apply_verifier_must_run_after_apply": bool( + verifier_binding.get("verifier_must_run_after_apply") + ), + "post_apply_verifier_execution_authorized_in_preview": bool( + verifier_binding.get("verifier_execution_authorized_in_preview") + ), + "permits_future_database_apply_controlled_dry_run_package": bool( + contract.get( + "permits_future_database_apply_controlled_dry_run_package" + ) + ), + "accepts_plaintext_secret": bool( + final_preflight.get("accepts_plaintext_secret") + or contract.get("accepts_plaintext_secret") + ), + "reads_secret_in_preview": bool( + future_preflight.get("reads_secret_in_preview") + or final_preflight.get("reads_secret_in_preview") + or safety.get("reads_secret_in_preview") + ), + "signature_material_included": bool( + final_preflight.get("signature_material_included") + ), + "secret_material_included": bool( + final_preflight.get("secret_material_included") + ), + "secret_material_required_in_preview": bool( + final_preflight.get("secret_material_required_in_preview") + or contract.get("secret_material_required_in_preview") + ), + "executes_authorization_evidence": bool( + future_preflight.get("executes_authorization_evidence") + or final_preflight.get("executes_authorization_evidence") + or contract.get("executes_authorization_evidence") + or safety.get("executes_authorization_evidence") + ), + "executes_database_apply": bool( + future_preflight.get("executes_database_apply") + or final_preflight.get("executes_database_apply") + or contract.get("executes_database_apply") + or safety.get("executes_database_apply") + ), + "executes_endpoint": bool( + future_preflight.get("executes_endpoint") + or contract.get("executes_endpoint") + or safety.get("executes_endpoint") + ), + "executes_sql": bool( + future_preflight.get("executes_sql") + or contract.get("executes_sql") + or safety.get("executes_sql") + ), + "ready_for_database_apply_now": bool( + future_preflight.get("ready_for_database_apply_now") + or final_preflight.get("ready_for_database_apply_now") + or contract.get("ready_for_database_apply_now") + ), + "database_apply_authorized": bool( + future_preflight.get("database_apply_authorized") + or final_preflight.get("database_apply_authorized") + or verifier_binding.get("database_apply_authorized") + or contract.get("database_apply_authorized") + ), + "issues_database_apply_authorization": bool( + future_preflight.get("issues_database_apply_authorization") + or final_preflight.get("issues_database_apply_authorization") + or contract.get("issues_database_apply_authorization") + ), + "signs_database_apply_authorization": bool( + future_preflight.get("signs_database_apply_authorization") + or final_preflight.get("signs_database_apply_authorization") + or contract.get("signs_database_apply_authorization") + ), + "requires_post_apply_verifier": bool( + final_preflight.get("requires_post_apply_verifier") + ), + "requires_fresh_production_truth": bool( + final_preflight.get( + "requires_fresh_production_truth_in_same_run" + ) + ), + "writes_database": False, + "next_machine_action": next_machine_action, + }, + ) + except Exception as exc: + return _check( + "PChome auto-policy controlled apply final preflight", + "warning", + ( + "PChome auto-policy controlled apply final preflight " + f"例行監控暫時無法讀取:{exc}" + ), + { + "writes_database": False, + "writes_database_count": 0, + "signs_database_apply_authorization_count": 0, + "primary_human_gate_count": 0, + "execute_fetch": True, + "outbound_network": False, + "business_data_source": False, + "payload_source": "local_contract_fixture", + "next_machine_action": ( + "refresh_pchome_auto_policy_controlled_apply_final_preflight" + ), + }, + ) + + def _ai_surface_html_readback_check() -> Dict[str, Any]: """Read-only AI product surface guardrail readback.""" try: @@ -10228,6 +11172,7 @@ def collect_ai_automation_smoke(*, record_history: bool = True, history_limit: i _pchome_auto_policy_verifier_receipt_closeout_check(), _pchome_auto_policy_authorization_evidence_execution_preflight_check(), _pchome_auto_policy_authorization_evidence_execution_closeout_check(), + _pchome_auto_policy_controlled_apply_final_preflight_check(), _ai_surface_html_readback_check(), _sitewide_ui_ux_agent_check(), _sitewide_visual_qa_check(), diff --git a/tests/test_ai_automation_metrics.py b/tests/test_ai_automation_metrics.py index e3b2156..3ea70c3 100644 --- a/tests/test_ai_automation_metrics.py +++ b/tests/test_ai_automation_metrics.py @@ -107,10 +107,10 @@ def test_system_metrics_exports_scheduled_health_summary(): Gauge, { "summary": { - "ok": 21, + "ok": 22, "warning": 1, "critical": 0, - "total": 22, + "total": 23, "primary_human_gate_count": 0, "writes_database_count": 0, }, @@ -134,6 +134,7 @@ def test_system_metrics_exports_scheduled_health_summary(): {"key": "pchome_auto_policy_verifier_receipt_closeout", "status": "ok"}, {"key": "pchome_auto_policy_authorization_evidence_execution_preflight", "status": "ok"}, {"key": "pchome_auto_policy_authorization_evidence_execution_closeout", "status": "ok"}, + {"key": "pchome_auto_policy_controlled_apply_final_preflight", "status": "ok"}, {"key": "sitewide_ui_ux_agent", "status": "ok"}, {"key": "sitewide_visual_qa", "status": "ok"}, ], @@ -141,7 +142,7 @@ def test_system_metrics_exports_scheduled_health_summary(): ) output = generate_latest(registry).decode("utf-8") - assert 'momo_ai_automation_scheduled_health_summary_total{status="ok"} 21.0' in output + assert 'momo_ai_automation_scheduled_health_summary_total{status="ok"} 22.0' in output assert 'momo_ai_automation_scheduled_health_summary_total{status="warning"} 1.0' in output assert ( 'momo_ai_automation_scheduled_health_family_status{family="ai_automation_smoke",status="ok"} 1.0' @@ -219,6 +220,10 @@ def test_system_metrics_exports_scheduled_health_summary(): 'momo_ai_automation_scheduled_health_family_status{family="pchome_auto_policy_authorization_evidence_execution_closeout",status="ok"} 1.0' in output ) + assert ( + 'momo_ai_automation_scheduled_health_family_status{family="pchome_auto_policy_controlled_apply_final_preflight",status="ok"} 1.0' + in output + ) assert ( 'momo_ai_automation_scheduled_health_family_status{family="sitewide_ui_ux_agent",status="ok"} 1.0' in output diff --git a/tests/test_ai_automation_smoke_service.py b/tests/test_ai_automation_smoke_service.py index 4e9ca74..eabcae5 100644 --- a/tests/test_ai_automation_smoke_service.py +++ b/tests/test_ai_automation_smoke_service.py @@ -938,6 +938,95 @@ def _auto_policy_authorization_evidence_execution_closeout_history_check(status= } +def _auto_policy_controlled_apply_final_preflight_history_check(status="ok"): + return { + "name": "PChome auto-policy controlled apply final preflight", + "status": status, + "summary": ( + "PChome auto-policy controlled apply final preflight 已自動完成" + ), + "details": { + "policy": ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_apply_" + "final_preflight" + ), + "result": "DB_APPLY_CONTROLLED_APPLY_FINAL_PREFLIGHT_READY", + "controlled_apply_final_preflight_ready_count": 1, + "controlled_apply_final_preflight_check_count": 12, + "controlled_apply_final_preflight_pass_count": 12, + "controlled_apply_final_preflight_waiting_count": 0, + "authorization_evidence_execution_closeout_ready_count": 1, + "authorization_evidence_execution_closeout_check_count": 12, + "authorization_evidence_execution_preflight_ready_count": 1, + "authorization_evidence_execution_preflight_check_count": 12, + "authorization_verifier_receipt_closeout_ready_count": 1, + "verifier_receipt_closeout_check_count": 12, + "database_apply_final_verifier_gate_count": 1, + "database_apply_authorization_final_verifier_gate_ready_count": 1, + "controlled_apply_final_preflight_count": 1, + "controlled_apply_final_preflight_field_count": 12, + "controlled_apply_final_preflight_acceptance_gate_count": 10, + "rollback_binding_count": 1, + "rollback_binding_field_count": 8, + "post_apply_verifier_binding_count": 1, + "post_apply_verifier_binding_field_count": 8, + "authorization_evidence_execution_closeout_field_count": 12, + "authorization_evidence_execution_closeout_acceptance_gate_count": 10, + "post_apply_verifier_required_count": 1, + "same_run_truth_required_count": 1, + "writes_script_count": 0, + "writes_artifact_count": 0, + "reads_secret_count": 0, + "executes_script_count": 0, + "executes_migration_count": 0, + "executes_endpoint_count": 0, + "executes_sql_count": 0, + "writes_database_count": 0, + "signs_database_apply_authorization_count": 0, + "primary_human_gate_count": 0, + "manual_review_required_count": 0, + "manual_review_mode": "exception_only", + "payload_source": "local_contract_fixture", + "execute_fetch": True, + "outbound_network": False, + "business_data_source": False, + "ready_for_future_database_apply_controlled_apply_final_preflight": True, + "can_enter_future_database_apply_controlled_dry_run_package": True, + "controlled_apply_final_preflight_ready": True, + "rollback_binding_ready": True, + "post_apply_verifier_binding_ready": True, + "rollback_bound": True, + "post_apply_verifier_bound": True, + "dry_run_only": True, + "check_mode_only": True, + "rollback_execution_authorized": False, + "rollback_executes_sql": False, + "rollback_writes_database": False, + "rollback_reads_secret": False, + "rollback_requires_same_run_truth": True, + "post_apply_verifier_must_run_after_apply": True, + "post_apply_verifier_execution_authorized_in_preview": False, + "permits_future_database_apply_controlled_dry_run_package": True, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "secret_material_required_in_preview": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "next_machine_action": ( + "continue_to_pchome_auto_policy_controlled_dry_run_package_lane" + ), + }, + } + + def test_event_router_smoke_reports_queued_deliveries(tmp_path, monkeypatch): from services import ai_automation_metrics as metrics from services import ai_automation_smoke_service as smoke @@ -990,6 +1079,7 @@ def test_collect_ai_automation_smoke_uses_worst_status(monkeypatch): monkeypatch.setattr(smoke, "_pchome_auto_policy_verifier_receipt_closeout_check", lambda: smoke._check("auto-policy verifier receipt closeout", "ok", "ok")) monkeypatch.setattr(smoke, "_pchome_auto_policy_authorization_evidence_execution_preflight_check", lambda: smoke._check("auto-policy authorization evidence execution preflight", "ok", "ok")) monkeypatch.setattr(smoke, "_pchome_auto_policy_authorization_evidence_execution_closeout_check", lambda: smoke._check("auto-policy authorization evidence execution closeout", "ok", "ok")) + monkeypatch.setattr(smoke, "_pchome_auto_policy_controlled_apply_final_preflight_check", lambda: smoke._check("auto-policy controlled apply final preflight", "ok", "ok")) monkeypatch.setattr(smoke, "_ai_surface_html_readback_check", lambda: smoke._check("surface", "ok", "ok")) monkeypatch.setattr(smoke, "_sitewide_ui_ux_agent_check", lambda: smoke._check("sitewide", "ok", "ok")) monkeypatch.setattr(smoke, "_sitewide_visual_qa_check", lambda: smoke._check("visual", "ok", "ok")) @@ -997,7 +1087,7 @@ def test_collect_ai_automation_smoke_uses_worst_status(monkeypatch): result = smoke.collect_ai_automation_smoke(record_history=False) assert result["status"] == "critical" - assert result["summary"] == {"ok": 25, "warning": 1, "critical": 1, "total": 27} + assert result["summary"] == {"ok": 26, "warning": 1, "critical": 1, "total": 28} def test_pchome_controlled_apply_drift_monitor_reports_verified_zero_drift(monkeypatch): @@ -3163,6 +3253,106 @@ def test_pchome_auto_policy_authorization_evidence_execution_closeout_reports_fi ) +def test_pchome_auto_policy_controlled_apply_final_preflight_reports_bindings_no_apply(): + from services import ai_automation_smoke_service as smoke + + result = smoke._pchome_auto_policy_controlled_apply_final_preflight_check() + + assert result["status"] == "ok" + assert result["details"]["result"] == ( + "DB_APPLY_CONTROLLED_APPLY_FINAL_PREFLIGHT_READY" + ) + assert result["details"]["controlled_apply_final_preflight_pass_count"] == 12 + assert result["details"]["controlled_apply_final_preflight_check_count"] == 12 + assert result["details"]["controlled_apply_final_preflight_waiting_count"] == 0 + assert ( + result["details"]["authorization_evidence_execution_closeout_ready_count"] + == 1 + ) + assert ( + result["details"]["authorization_evidence_execution_closeout_check_count"] + == 12 + ) + assert result["details"]["database_apply_final_verifier_gate_count"] == 1 + assert ( + result["details"][ + "database_apply_authorization_final_verifier_gate_ready_count" + ] + == 1 + ) + assert result["details"]["controlled_apply_final_preflight_count"] == 1 + assert result["details"]["controlled_apply_final_preflight_field_count"] == 12 + assert ( + result["details"]["controlled_apply_final_preflight_acceptance_gate_count"] + == 10 + ) + assert result["details"]["rollback_binding_count"] == 1 + assert result["details"]["rollback_binding_field_count"] == 8 + assert result["details"]["post_apply_verifier_binding_count"] == 1 + assert result["details"]["post_apply_verifier_binding_field_count"] == 8 + assert result["details"]["post_apply_verifier_required_count"] == 1 + assert result["details"]["same_run_truth_required_count"] == 1 + assert result["details"]["payload_source"] == "local_contract_fixture" + assert result["details"]["machine_fetch_evidence_source"] == "local_schema_fixture" + assert result["details"]["outbound_network"] is False + assert result["details"]["business_data_source"] is False + assert result["details"]["primary_human_gate_count"] == 0 + assert result["details"]["manual_review_required_count"] == 0 + assert result["details"]["reads_secret_count"] == 0 + assert result["details"]["executes_script_count"] == 0 + assert result["details"]["executes_endpoint_count"] == 0 + assert result["details"]["executes_sql_count"] == 0 + assert result["details"]["writes_database_count"] == 0 + assert result["details"]["signs_database_apply_authorization_count"] == 0 + assert ( + result["details"][ + "ready_for_future_database_apply_controlled_apply_final_preflight" + ] + is True + ) + assert ( + result["details"]["can_enter_future_database_apply_controlled_dry_run_package"] + is True + ) + assert result["details"]["controlled_apply_final_preflight_ready"] is True + assert result["details"]["rollback_binding_ready"] is True + assert result["details"]["post_apply_verifier_binding_ready"] is True + assert result["details"]["rollback_bound"] is True + assert result["details"]["post_apply_verifier_bound"] is True + assert result["details"]["dry_run_only"] is True + assert result["details"]["check_mode_only"] is True + assert result["details"]["rollback_execution_authorized"] is False + assert result["details"]["rollback_executes_sql"] is False + assert result["details"]["rollback_writes_database"] is False + assert result["details"]["rollback_reads_secret"] is False + assert result["details"]["rollback_requires_same_run_truth"] is True + assert result["details"]["post_apply_verifier_must_run_after_apply"] is True + assert ( + result["details"]["post_apply_verifier_execution_authorized_in_preview"] + is False + ) + assert ( + result["details"]["permits_future_database_apply_controlled_dry_run_package"] + is True + ) + assert result["details"]["accepts_plaintext_secret"] is False + assert result["details"]["reads_secret_in_preview"] is False + assert result["details"]["signature_material_included"] is False + assert result["details"]["secret_material_included"] is False + assert result["details"]["secret_material_required_in_preview"] is False + assert result["details"]["executes_authorization_evidence"] is False + assert result["details"]["executes_database_apply"] is False + assert result["details"]["executes_endpoint"] is False + assert result["details"]["executes_sql"] is False + assert result["details"]["ready_for_database_apply_now"] is False + assert result["details"]["database_apply_authorized"] is False + assert result["details"]["issues_database_apply_authorization"] is False + assert result["details"]["signs_database_apply_authorization"] is False + assert result["details"]["next_machine_action"] == ( + "continue_to_pchome_auto_policy_controlled_dry_run_package_lane" + ) + + def test_collect_ai_automation_smoke_persists_recent_history(tmp_path, monkeypatch): from services import ai_automation_smoke_service as smoke @@ -3193,6 +3383,7 @@ def test_collect_ai_automation_smoke_persists_recent_history(tmp_path, monkeypat monkeypatch.setattr(smoke, "_pchome_auto_policy_verifier_receipt_closeout_check", lambda: smoke._check("auto-policy verifier receipt closeout", "ok", "ok")) monkeypatch.setattr(smoke, "_pchome_auto_policy_authorization_evidence_execution_preflight_check", lambda: smoke._check("auto-policy authorization evidence execution preflight", "ok", "ok")) monkeypatch.setattr(smoke, "_pchome_auto_policy_authorization_evidence_execution_closeout_check", lambda: smoke._check("auto-policy authorization evidence execution closeout", "ok", "ok")) + monkeypatch.setattr(smoke, "_pchome_auto_policy_controlled_apply_final_preflight_check", lambda: smoke._check("auto-policy controlled apply final preflight", "ok", "ok")) monkeypatch.setattr(smoke, "_ai_surface_html_readback_check", lambda: smoke._check("surface", "ok", "ok")) monkeypatch.setattr(smoke, "_sitewide_ui_ux_agent_check", lambda: smoke._check("sitewide", "ok", "ok")) monkeypatch.setattr(smoke, "_sitewide_visual_qa_check", lambda: smoke._check("visual", "ok", "ok")) @@ -3251,7 +3442,7 @@ def test_scheduled_automation_health_summary_reads_history_without_side_effects( json.dumps({ "generated_at": datetime.now().isoformat(timespec="seconds"), "status": "ok", - "summary": {"ok": 27, "warning": 0, "critical": 0, "total": 27}, + "summary": {"ok": 28, "warning": 0, "critical": 0, "total": 28}, "checks": [ { "name": "PChome 受控落地 drift monitor", @@ -3282,6 +3473,7 @@ def test_scheduled_automation_health_summary_reads_history_without_side_effects( _auto_policy_verifier_receipt_closeout_history_check(), _auto_policy_authorization_evidence_execution_preflight_history_check(), _auto_policy_authorization_evidence_execution_closeout_history_check(), + _auto_policy_controlled_apply_final_preflight_history_check(), { "name": "AI surface HTML readback", "status": "ok", @@ -3343,7 +3535,7 @@ def test_scheduled_automation_health_summary_reads_history_without_side_effects( ) assert summary["policy"] == "read_only_ai_automation_scheduled_health_summary" assert summary["status"] == "ok" - assert summary["summary"]["total"] == 24 + assert summary["summary"]["total"] == 25 assert summary["summary"]["primary_human_gate_count"] == 0 assert summary["summary"]["writes_database_count"] == 0 assert pchome_family["status"] == "ok" @@ -4737,6 +4929,125 @@ def test_scheduled_automation_health_summary_reads_history_without_side_effects( authorization_evidence_closeout_family["details"]["database_apply_authorized"] is False ) + controlled_final_preflight_family = next( + item for item in summary["families"] + if item["key"] == "pchome_auto_policy_controlled_apply_final_preflight" + ) + assert controlled_final_preflight_family["status"] == "ok" + assert controlled_final_preflight_family["next_machine_action"] == ( + "continue_to_pchome_auto_policy_controlled_dry_run_package_lane" + ) + assert controlled_final_preflight_family["details"]["next_machine_action"] == ( + "continue_to_pchome_auto_policy_controlled_dry_run_package_lane" + ) + assert ( + controlled_final_preflight_family["details"][ + "controlled_apply_final_preflight_pass_count" + ] + == 12 + ) + assert ( + controlled_final_preflight_family["details"][ + "authorization_evidence_execution_closeout_ready_count" + ] + == 1 + ) + assert ( + controlled_final_preflight_family["details"][ + "database_apply_authorization_final_verifier_gate_ready_count" + ] + == 1 + ) + assert controlled_final_preflight_family["details"]["rollback_binding_count"] == 1 + assert ( + controlled_final_preflight_family["details"]["rollback_binding_field_count"] + == 8 + ) + assert ( + controlled_final_preflight_family["details"]["post_apply_verifier_binding_count"] + == 1 + ) + assert ( + controlled_final_preflight_family["details"][ + "post_apply_verifier_binding_field_count" + ] + == 8 + ) + assert ( + controlled_final_preflight_family["details"]["payload_source"] + == "local_contract_fixture" + ) + assert controlled_final_preflight_family["details"]["outbound_network"] is False + assert controlled_final_preflight_family["details"]["business_data_source"] is False + assert ( + controlled_final_preflight_family["details"]["primary_human_gate_count"] + == 0 + ) + assert controlled_final_preflight_family["details"]["reads_secret_count"] == 0 + assert controlled_final_preflight_family["details"]["writes_database_count"] == 0 + assert ( + controlled_final_preflight_family["details"][ + "signs_database_apply_authorization_count" + ] + == 0 + ) + assert ( + controlled_final_preflight_family["details"][ + "ready_for_future_database_apply_controlled_apply_final_preflight" + ] + is True + ) + assert ( + controlled_final_preflight_family["details"][ + "can_enter_future_database_apply_controlled_dry_run_package" + ] + is True + ) + assert ( + controlled_final_preflight_family["details"][ + "controlled_apply_final_preflight_ready" + ] + is True + ) + assert controlled_final_preflight_family["details"]["rollback_binding_ready"] is True + assert ( + controlled_final_preflight_family["details"]["post_apply_verifier_binding_ready"] + is True + ) + assert controlled_final_preflight_family["details"]["rollback_bound"] is True + assert controlled_final_preflight_family["details"]["post_apply_verifier_bound"] is True + assert controlled_final_preflight_family["details"]["dry_run_only"] is True + assert controlled_final_preflight_family["details"]["check_mode_only"] is True + assert ( + controlled_final_preflight_family["details"]["rollback_execution_authorized"] + is False + ) + assert controlled_final_preflight_family["details"]["rollback_executes_sql"] is False + assert controlled_final_preflight_family["details"]["rollback_writes_database"] is False + assert ( + controlled_final_preflight_family["details"][ + "post_apply_verifier_execution_authorized_in_preview" + ] + is False + ) + assert ( + controlled_final_preflight_family["details"][ + "permits_future_database_apply_controlled_dry_run_package" + ] + is True + ) + assert ( + controlled_final_preflight_family["details"]["executes_database_apply"] + is False + ) + assert ( + controlled_final_preflight_family["details"]["ready_for_database_apply_now"] + is False + ) + assert ( + controlled_final_preflight_family["details"]["database_apply_authorized"] + is False + ) surface_family = next( item for item in summary["families"] if item["key"] == "ai_surface_html_readback" @@ -4771,7 +5082,7 @@ def test_scheduled_automation_health_summary_can_use_current_smoke_without_recor current_smoke = { "generated_at": "2026-07-02T12:00:00", "status": "critical", - "summary": {"ok": 26, "warning": 0, "critical": 1, "total": 27}, + "summary": {"ok": 27, "warning": 0, "critical": 1, "total": 28}, "checks": [ { "name": "PChome 受控落地 drift monitor", @@ -4796,6 +5107,7 @@ def test_scheduled_automation_health_summary_can_use_current_smoke_without_recor _auto_policy_verifier_receipt_closeout_history_check(), _auto_policy_authorization_evidence_execution_preflight_history_check(), _auto_policy_authorization_evidence_execution_closeout_history_check(), + _auto_policy_controlled_apply_final_preflight_history_check(), { "name": "AI surface HTML readback", "status": "ok", @@ -4851,7 +5163,7 @@ def test_scheduled_automation_health_summary_falls_back_to_visual_qa_artifact(tm json.dumps({ "generated_at": datetime.now().isoformat(timespec="seconds"), "status": "ok", - "summary": {"ok": 25, "warning": 0, "critical": 0, "total": 25}, + "summary": {"ok": 26, "warning": 0, "critical": 0, "total": 26}, "checks": [], }, ensure_ascii=False) + "\n", encoding="utf-8", @@ -4942,6 +5254,11 @@ def test_scheduled_automation_health_summary_falls_back_to_visual_qa_artifact(tm "_pchome_auto_policy_authorization_evidence_execution_closeout_check", lambda: _auto_policy_authorization_evidence_execution_closeout_history_check(), ) + monkeypatch.setattr( + smoke, + "_pchome_auto_policy_controlled_apply_final_preflight_check", + lambda: _auto_policy_controlled_apply_final_preflight_history_check(), + ) monkeypatch.setattr( surface_service, "build_sitewide_visual_qa_readback", @@ -4990,7 +5307,7 @@ def test_scheduled_automation_health_summary_route_returns_compact_payload(tmp_p json.dumps({ "generated_at": datetime.now().isoformat(timespec="seconds"), "status": "ok", - "summary": {"ok": 27, "warning": 0, "critical": 0, "total": 27}, + "summary": {"ok": 28, "warning": 0, "critical": 0, "total": 28}, "checks": [], }, ensure_ascii=False) + "\n", encoding="utf-8", @@ -5081,6 +5398,11 @@ def test_scheduled_automation_health_summary_route_returns_compact_payload(tmp_p "_pchome_auto_policy_authorization_evidence_execution_closeout_check", lambda: _auto_policy_authorization_evidence_execution_closeout_history_check(), ) + monkeypatch.setattr( + smoke, + "_pchome_auto_policy_controlled_apply_final_preflight_check", + lambda: _auto_policy_controlled_apply_final_preflight_history_check(), + ) app = Flask(__name__) with app.test_request_context( @@ -5321,6 +5643,7 @@ def test_surface_html_readback_check_is_part_of_ai_smoke(monkeypatch): monkeypatch.setattr(smoke, "_pchome_auto_policy_verifier_receipt_closeout_check", lambda: smoke._check("auto-policy verifier receipt closeout", "ok", "ok")) monkeypatch.setattr(smoke, "_pchome_auto_policy_authorization_evidence_execution_preflight_check", lambda: smoke._check("auto-policy authorization evidence execution preflight", "ok", "ok")) monkeypatch.setattr(smoke, "_pchome_auto_policy_authorization_evidence_execution_closeout_check", lambda: smoke._check("auto-policy authorization evidence execution closeout", "ok", "ok")) + monkeypatch.setattr(smoke, "_pchome_auto_policy_controlled_apply_final_preflight_check", lambda: smoke._check("auto-policy controlled apply final preflight", "ok", "ok")) monkeypatch.setattr(smoke, "_sitewide_visual_qa_check", lambda: smoke._check( "Sitewide visual QA readback", "ok", @@ -5342,7 +5665,7 @@ def test_surface_html_readback_check_is_part_of_ai_smoke(monkeypatch): item for item in result["checks"] if item["name"] == "Sitewide visual QA readback" ) - assert result["summary"]["total"] == 27 + assert result["summary"]["total"] == 28 assert surface_check["status"] == "ok" assert surface_check["details"]["checked_surface_count"] == 10 assert sitewide_check["status"] == "ok"