diff --git a/config.py b/config.py index c0f72c0..31dca61 100644 --- a/config.py +++ b/config.py @@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.732" +SYSTEM_VERSION = "V10.733" 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 b5a8ab8..d4fa2c9 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 已建立,GCP embedding 熔斷延後處理、110 proxy rescue 與 direct host health skip 已建立 -> **適用版本**: V10.732 +> **狀態**: 🟢 四 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 已建立,GCP embedding 熔斷延後處理、110 proxy rescue 與 direct host health skip 已建立 +> **適用版本**: V10.733 --- @@ -248,6 +248,7 @@ SQL漏斗(~300筆) - `/api/ai-automation/smoke` 必須包含 `PChome auto-policy signing decision preflight`:以本機 deterministic contract fixture 與 machine evidence adapter 推進 no-write signing decision preflight ready path,並明確回報 `signing_decision_input_requirement_count=10`、`signing_decision_rejection_reason_count=11`、`payload_source=local_contract_fixture`、`outbound_network=false`、`business_data_source=false`、`writes_database_count=0`、`signs_database_apply_authorization_count=0`、`primary_human_gate_count=0`、`ready_for_database_apply_now=false`、`issues_database_apply_authorization=false`、`signs_database_apply_authorization=false`、`secret_material_required_in_preview=false`。此 check 不讀 secret、不執行 shell/SQL、不寫 DB、不簽發 apply authorization;`/api/ai-automation/scheduled-health-summary` 需輸出 `pchome_auto_policy_signing_decision_preflight` family,`/metrics` 需暴露對應 `momo_ai_automation_scheduled_health_family_status`。 - `/api/ai-automation/smoke` 必須包含 `PChome auto-policy signing decision closeout`:以本機 deterministic contract fixture 與 machine evidence adapter 推進 no-write signing decision closeout ready path,並明確回報 `signing_decision_closeout_check_count=12`、`signing_decision_input_requirement_count=10`、`signing_decision_rejection_reason_count=11`、`payload_source=local_contract_fixture`、`outbound_network=false`、`business_data_source=false`、`writes_database_count=0`、`signs_database_apply_authorization_count=0`、`primary_human_gate_count=0`、`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`。此 check 不讀 secret、不執行 shell/SQL、不寫 DB、不簽發 apply authorization;`/api/ai-automation/scheduled-health-summary` 需輸出 `pchome_auto_policy_signing_decision_closeout` family,`/metrics` 需暴露對應 `momo_ai_automation_scheduled_health_family_status`。 - `/api/ai-automation/smoke` 必須包含 `PChome auto-policy signing issuer guard`:以本機 deterministic contract fixture 與 machine evidence adapter 推進 no-write signing issuer guard ready path,並明確回報 `signing_issuer_guard_check_count=12`、`signing_decision_closeout_check_count=12`、`signing_decision_input_requirement_count=10`、`signing_decision_rejection_reason_count=11`、`payload_source=local_contract_fixture`、`outbound_network=false`、`business_data_source=false`、`writes_database_count=0`、`signs_database_apply_authorization_count=0`、`primary_human_gate_count=0`、`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`、`ready_for_future_signable_request_boundary=true`。此 check 不讀 secret、不執行 shell/SQL、不寫 DB、不簽發 apply authorization;`/api/ai-automation/scheduled-health-summary` 需輸出 `pchome_auto_policy_signing_issuer_guard` family,`/metrics` 需暴露對應 `momo_ai_automation_scheduled_health_family_status`。 +- `/api/ai-automation/smoke` 必須包含 `PChome auto-policy signing issuer closeout`:以本機 deterministic contract fixture 與 machine evidence adapter 推進 no-write final signable request package ready path,並明確回報 `signing_issuer_closeout_check_count=12`、`signing_issuer_guard_check_count=12`、`signing_decision_input_requirement_count=10`、`signing_decision_rejection_reason_count=11`、`payload_source=local_contract_fixture`、`outbound_network=false`、`business_data_source=false`、`writes_database_count=0`、`signs_database_apply_authorization_count=0`、`primary_human_gate_count=0`、`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`、`ready_for_future_final_signable_request_package=true`。此 check 不讀 secret、不執行 shell/SQL、不寫 DB、不簽發 apply authorization;`/api/ai-automation/scheduled-health-summary` 需輸出 `pchome_auto_policy_signing_issuer_closeout` family,`/metrics` 需暴露對應 `momo_ai_automation_scheduled_health_family_status`。 - 健康檢查 API 會將最近檢查結果保存到 JSONL,頁面顯示最近狀態趨勢。 - 健康檢查歷史支援 JSONL 匯出、清理與每日「正常 / 注意 / 嚴重」摘要。 - 健康檢查每日摘要支援手動 Telegram 推播,並由 `momo-scheduler` 每日 09:10 呼叫 `run_ai_smoke_daily_summary_task()`。 @@ -856,6 +857,7 @@ POSTGRES_HOST=momo-db | 2026-07-03 | PChome auto-policy signing decision preflight 必須有 runtime monitoring | V10.730 起 `/api/ai-automation/smoke`、`/api/ai-automation/scheduled-health-summary` 與 `/metrics` 必須輸出 `PChome auto-policy signing decision preflight` / `pchome_auto_policy_signing_decision_preflight`;此 runtime check 自動驗證 signing decision preflight ready path、signing decision input requirements 與 rejection policy,但明確標記 `writes_database_count=0`、`signs_database_apply_authorization_count=0`、`primary_human_gate_count=0`、`ready_for_database_apply_now=false`、`issues_database_apply_authorization=false`、`signs_database_apply_authorization=false`、`secret_material_required_in_preview=false`,不讀 secret、不執行 SQL、不寫 DB、不簽發 apply authorization。 | | 2026-07-03 | PChome auto-policy signing decision closeout 必須有 runtime monitoring | V10.731 起 `/api/ai-automation/smoke`、`/api/ai-automation/scheduled-health-summary` 與 `/metrics` 必須輸出 `PChome auto-policy signing decision closeout` / `pchome_auto_policy_signing_decision_closeout`;此 runtime check 自動驗證 signing decision closeout ready path、unsigned signing decision package、post-apply verifier requirement 與 same-run production truth requirement,但明確標記 `writes_database_count=0`、`signs_database_apply_authorization_count=0`、`primary_human_gate_count=0`、`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、不執行 SQL、不寫 DB、不簽發 apply authorization。 | | 2026-07-03 | PChome auto-policy signing issuer guard 必須有 runtime monitoring | V10.732 起 `/api/ai-automation/smoke`、`/api/ai-automation/scheduled-health-summary` 與 `/metrics` 必須輸出 `PChome auto-policy signing issuer guard` / `pchome_auto_policy_signing_issuer_guard`;此 runtime check 自動驗證 signing issuer guard ready path、future signable request boundary、post-apply verifier requirement 與 same-run production truth requirement,但明確標記 `writes_database_count=0`、`signs_database_apply_authorization_count=0`、`primary_human_gate_count=0`、`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、不執行 SQL、不寫 DB、不簽發 apply authorization。 | +| 2026-07-03 | PChome auto-policy signing issuer closeout 必須有 runtime monitoring | V10.733 起 `/api/ai-automation/smoke`、`/api/ai-automation/scheduled-health-summary` 與 `/metrics` 必須輸出 `PChome auto-policy signing issuer closeout` / `pchome_auto_policy_signing_issuer_closeout`;此 runtime check 自動驗證 final signable request package、signing issuer guard source chain、post-apply verifier requirement 與 same-run production truth requirement,但明確標記 `writes_database_count=0`、`signs_database_apply_authorization_count=0`、`primary_human_gate_count=0`、`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、不執行 SQL、不寫 DB、不簽發 apply authorization。 | | 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 af87d33..555b03b 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.732`,除非有明確版本 bump、部署與 readback。 +2. 版本不得搞錯;目前正式環境 `/health` 版本維持 `V10.733`,除非有明確版本 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.732`。 +- 正式環境版本真相 guard:`/health` 是最高真相,目前維持 `V10.733`。 - Gitea `dev` / `main` source truth 對齊。 - Retry exception controlled apply executor 已落地,目標表為 `pchome_product_matches`。 - 正式 DB 已 controlled apply 4 個 selectors: @@ -134,10 +134,17 @@ - `/api/ai-automation/scheduled-health-summary` 已輸出 `pchome_auto_policy_signing_issuer_guard` family - `/metrics` 已輸出 `momo_ai_automation_scheduled_health_family_status{family="pchome_auto_policy_signing_issuer_guard",...}` - 不讀 secret、不執行 SQL、不寫 DB、不簽發 apply authorization,下一個機器動作是 signing issuer closeout lane +- PChome auto-policy signing issuer closeout runtime monitoring 已完成: + - `/api/ai-automation/smoke` 已加入 `PChome auto-policy signing issuer closeout` + - 以本機 contract fixture + machine evidence adapter 自動推進 final signable request package closeout path + - 明確標記 `signing_issuer_closeout_check_count=12`、`signing_issuer_guard_check_count=12`、`signing_decision_input_requirement_count=10`、`signing_decision_rejection_reason_count=11`、`writes_database_count=0`、`signs_database_apply_authorization_count=0`、`primary_human_gate_count=0` + - `/api/ai-automation/scheduled-health-summary` 已輸出 `pchome_auto_policy_signing_issuer_closeout` family + - `/metrics` 已輸出 `momo_ai_automation_scheduled_health_family_status{family="pchome_auto_policy_signing_issuer_closeout",...}` + - 不讀 secret、不執行 SQL、不寫 DB、不簽發 apply authorization,下一個機器動作是 signing execution preflight lane 進行中 / 下一步,必須照順序: -1. 將 auto-policy signing issuer guard 往 signing issuer closeout lane 推進。 +1. 將 auto-policy signing issuer closeout 往 signing execution preflight lane 推進。 完成標準: @@ -304,7 +311,7 @@ 進行中 / 下一步,必須照順序: -1. 將 auto-policy signing issuer guard 推進到 signing issuer closeout lane。 +1. 將 auto-policy signing issuer closeout 推進到 signing execution preflight lane。 2. 對其他 safe mapping backlog lanes 套用 receipt / replay / drift verifier 模式。 3. 為每一類 controlled apply 自動產生 rollback evidence package。 4. 把 AI automation metrics 持續整合進既有 observability surfaces。 @@ -382,7 +389,8 @@ | P3.8 | PChome auto-policy authorization issuer gate monitoring | 已完成 | AI smoke + scheduled family + metrics family prove issuer gate ready, nonsecret envelope bound, no DB write, no signing, no apply authorization | P3.9 signing decision preflight monitoring | | P3.9 | PChome auto-policy signing decision preflight monitoring | 已完成 | AI smoke + scheduled family + metrics family prove signing decision preflight ready, signing inputs bound, no DB write, no signing, no apply authorization | P3.10 signing decision closeout monitoring | | P3.10 | PChome auto-policy signing decision closeout monitoring | 已完成 | AI smoke + scheduled family + metrics family prove signing decision closeout ready, unsigned package bound, no DB write, no signing, no apply authorization | P3.11 signing issuer guard monitoring | -| P3.11 | PChome auto-policy signing issuer guard monitoring | 已完成 | AI smoke + scheduled family + metrics family prove signing issuer guard ready, future signable boundary bound, no DB write, no signing, no apply authorization | P3.12 signing issuer closeout lane | +| P3.11 | PChome auto-policy signing issuer guard monitoring | 已完成 | AI smoke + scheduled family + metrics family prove signing issuer guard ready, future signable boundary bound, no DB write, no signing, no apply authorization | P3.12 signing issuer closeout monitoring | +| P3.12 | PChome auto-policy signing issuer closeout monitoring | 已完成 | AI smoke + scheduled family + metrics family prove final signable request package ready, no DB write, no signing, no apply authorization | P3.13 signing execution preflight lane | | 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 d95d803..d0d03f5 100644 --- a/services/ai_automation_smoke_service.py +++ b/services/ai_automation_smoke_service.py @@ -280,6 +280,23 @@ def build_scheduled_automation_health_summary( auto_policy_signing_issuer_guard_details = ( auto_policy_signing_issuer_guard.get("details") or {} ) + auto_policy_signing_issuer_closeout = _find_check( + source_result, + "PChome auto-policy signing issuer closeout", + ) + auto_policy_signing_issuer_closeout_details = ( + auto_policy_signing_issuer_closeout.get("details") or {} + ) + if ( + not auto_policy_signing_issuer_closeout + or not auto_policy_signing_issuer_closeout_details + ): + auto_policy_signing_issuer_closeout = ( + _pchome_auto_policy_signing_issuer_closeout_check() + ) + auto_policy_signing_issuer_closeout_details = ( + auto_policy_signing_issuer_closeout.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") @@ -1057,6 +1074,178 @@ def build_scheduled_automation_health_summary( "writes_database": False, }, }, + { + "key": "pchome_auto_policy_signing_issuer_closeout", + "label": "PChome auto-policy signing issuer closeout", + "status": auto_policy_signing_issuer_closeout.get("status") or "warning", + "summary": ( + auto_policy_signing_issuer_closeout.get("summary") + or "PChome auto-policy signing issuer closeout has no latest check." + ), + "next_machine_action": auto_policy_signing_issuer_closeout_details.get( + "next_machine_action" + ) + or ( + "continue_pchome_auto_policy_signing_issuer_closeout_monitoring" + if auto_policy_signing_issuer_closeout.get("status") == "ok" + else "refresh_pchome_auto_policy_signing_issuer_closeout" + ), + "details": { + "policy": auto_policy_signing_issuer_closeout_details.get("policy"), + "result": auto_policy_signing_issuer_closeout_details.get("result"), + "signing_issuer_closeout_ready_count": int( + auto_policy_signing_issuer_closeout_details.get( + "signing_issuer_closeout_ready_count" + ) + or 0 + ), + "signing_issuer_closeout_check_count": int( + auto_policy_signing_issuer_closeout_details.get( + "signing_issuer_closeout_check_count" + ) + or 0 + ), + "signing_issuer_closeout_pass_count": int( + auto_policy_signing_issuer_closeout_details.get( + "signing_issuer_closeout_pass_count" + ) + or 0 + ), + "signing_issuer_closeout_waiting_count": int( + auto_policy_signing_issuer_closeout_details.get( + "signing_issuer_closeout_waiting_count" + ) + or 0 + ), + "signing_issuer_guard_ready_count": int( + auto_policy_signing_issuer_closeout_details.get( + "signing_issuer_guard_ready_count" + ) + or 0 + ), + "signing_issuer_guard_check_count": int( + auto_policy_signing_issuer_closeout_details.get( + "signing_issuer_guard_check_count" + ) + or 0 + ), + "signing_decision_input_requirement_count": int( + auto_policy_signing_issuer_closeout_details.get( + "signing_decision_input_requirement_count" + ) + or 0 + ), + "signing_decision_rejection_reason_count": int( + auto_policy_signing_issuer_closeout_details.get( + "signing_decision_rejection_reason_count" + ) + or 0 + ), + "required_issuer_evidence_count": int( + auto_policy_signing_issuer_closeout_details.get( + "required_issuer_evidence_count" + ) + or 0 + ), + "nonsecret_authorization_claim_count": int( + auto_policy_signing_issuer_closeout_details.get( + "nonsecret_authorization_claim_count" + ) + or 0 + ), + "post_apply_verifier_required_count": int( + auto_policy_signing_issuer_closeout_details.get( + "post_apply_verifier_required_count" + ) + or 0 + ), + "same_run_truth_required_count": int( + auto_policy_signing_issuer_closeout_details.get( + "same_run_truth_required_count" + ) + or 0 + ), + "reads_secret_count": int( + auto_policy_signing_issuer_closeout_details.get("reads_secret_count") + or 0 + ), + "executes_sql_count": int( + auto_policy_signing_issuer_closeout_details.get("executes_sql_count") + or 0 + ), + "writes_database_count": int( + auto_policy_signing_issuer_closeout_details.get( + "writes_database_count" + ) + or 0 + ), + "signs_database_apply_authorization_count": int( + auto_policy_signing_issuer_closeout_details.get( + "signs_database_apply_authorization_count" + ) + or 0 + ), + "primary_human_gate_count": int( + auto_policy_signing_issuer_closeout_details.get( + "primary_human_gate_count" + ) + or 0 + ), + "manual_review_mode": auto_policy_signing_issuer_closeout_details.get( + "manual_review_mode" + ), + "payload_source": auto_policy_signing_issuer_closeout_details.get( + "payload_source" + ), + "execute_fetch": bool( + auto_policy_signing_issuer_closeout_details.get("execute_fetch") + ), + "outbound_network": bool( + auto_policy_signing_issuer_closeout_details.get("outbound_network") + ), + "business_data_source": bool( + auto_policy_signing_issuer_closeout_details.get( + "business_data_source" + ) + ), + "ready_for_database_apply_now": bool( + auto_policy_signing_issuer_closeout_details.get( + "ready_for_database_apply_now" + ) + ), + "issues_database_apply_authorization": bool( + auto_policy_signing_issuer_closeout_details.get( + "issues_database_apply_authorization" + ) + ), + "signs_database_apply_authorization": bool( + auto_policy_signing_issuer_closeout_details.get( + "signs_database_apply_authorization" + ) + ), + "secret_material_included": bool( + auto_policy_signing_issuer_closeout_details.get( + "secret_material_included" + ) + ), + "secret_material_required_in_preview": bool( + auto_policy_signing_issuer_closeout_details.get( + "secret_material_required_in_preview" + ) + ), + "ready_for_future_final_signable_request_package": bool( + auto_policy_signing_issuer_closeout_details.get( + "ready_for_future_final_signable_request_package" + ) + ), + "permits_future_final_signable_request_package_lane": bool( + auto_policy_signing_issuer_closeout_details.get( + "permits_future_final_signable_request_package_lane" + ) + ), + "writes_database": False, + }, + }, { "key": "ai_surface_html_readback", "label": "AI surface HTML readback", @@ -3048,6 +3237,241 @@ def _pchome_auto_policy_signing_issuer_guard_check() -> Dict[str, Any]: ) +def _pchome_auto_policy_signing_issuer_closeout_check() -> Dict[str, Any]: + """Read-only monitor for the final signable request closeout package.""" + 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() + closeout = ( + backlog.build_pchome_auto_policy_db_apply_authorization_signing_issuer_closeout( + 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 = closeout.get("summary") or {} + issuer_closeout = closeout.get("future_authorization_signing_issuer_closeout") or {} + package = closeout.get("final_signable_request_package") or {} + contract = closeout.get("signing_issuer_closeout_contract") or {} + safety = closeout.get("safety") or {} + result = str(closeout.get("result") or "UNKNOWN") + + closeout_ready_count = int( + summary.get("authorization_signing_issuer_closeout_ready_count") or 0 + ) + check_count = int(summary.get("signing_issuer_closeout_check_count") or 0) + pass_count = int(summary.get("signing_issuer_closeout_pass_count") or 0) + waiting_count = int(summary.get("signing_issuer_closeout_waiting_count") or 0) + guard_ready_count = int( + summary.get("authorization_signing_issuer_guard_ready_count") or 0 + ) + guard_check_count = int(summary.get("signing_issuer_guard_check_count") or 0) + input_requirement_count = int( + summary.get("signing_decision_input_requirement_count") or 0 + ) + rejection_reason_count = int( + summary.get("signing_decision_rejection_reason_count") or 0 + ) + required_evidence_count = int(summary.get("required_issuer_evidence_count") or 0) + nonsecret_claim_count = int(summary.get("nonsecret_authorization_claim_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) + reads_secret_count = int(summary.get("reads_secret_count") or 0) + executes_script_count = int(summary.get("executes_script_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 = ( + reads_secret_count + + executes_script_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("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, + issuer_closeout.get("ready_for_database_apply_now") is not False, + issuer_closeout.get("issues_database_apply_authorization") is not False, + issuer_closeout.get("signs_database_apply_authorization") is not False, + package.get("ready_for_database_apply_now") is not False, + package.get("issues_database_apply_authorization") is not False, + package.get("signs_database_apply_authorization") is not False, + package.get("secret_material_included") is not False, + package.get("secret_material_required_in_preview") is not False, + package.get("reads_secret_in_preview") is not False, + package.get("executes_sql_in_preview") is not False, + package.get("writes_database_in_preview") is not False, + contract.get("issues_database_apply_authorization") 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("secret_material_required_in_preview") is not False, + ] + ) + closeout_contract_present = ( + closeout_ready_count == 1 + and guard_ready_count == 1 + and check_count >= 12 + and pass_count == check_count + and waiting_count == 0 + and guard_check_count == 12 + and input_requirement_count == 10 + and rejection_reason_count == 11 + and required_evidence_count == 9 + and nonsecret_claim_count == 8 + and verifier_required_count == 1 + and same_run_truth_count == 1 + and issuer_closeout.get("ready_for_future_signing_issuer_closeout") is True + and ( + issuer_closeout.get("can_enter_future_final_signable_request_package_lane") + is True + ) + and package.get("authorization_material_type") == "final_signable_request_package" + and package.get("ready_for_future_final_signable_request_package") is True + and package.get("hash_matches") is True + and package.get("requires_post_apply_verifier") is True + and package.get("requires_fresh_production_truth_in_same_run") is True + and contract.get("machine_verifiable") is True + and contract.get("permits_future_final_signable_request_package_lane") is True + and safety.get("manual_review_mode") == "exception_only" + ) + + if risk_detected: + status = "critical" + summary_text = ( + "PChome auto-policy signing issuer closeout 偵測到 secret、SQL、DB write、" + "簽發授權或人工主 gate 風險" + ) + next_machine_action = ( + "halt_pchome_auto_policy_signing_issuer_closeout_and_inspect_risk" + ) + elif closeout_contract_present: + status = "ok" + summary_text = ( + "PChome auto-policy signing issuer closeout 已自動完成 " + f"{pass_count}/{check_count} checks,final signable package 已綁定,authorization signing=0" + ) + next_machine_action = "continue_to_pchome_auto_policy_signing_execution_preflight_lane" + else: + status = "warning" + summary_text = "PChome auto-policy signing issuer closeout 尚未達自動監控契約" + next_machine_action = "refresh_pchome_auto_policy_signing_issuer_closeout" + + return _check( + "PChome auto-policy signing issuer closeout", + status, + summary_text, + { + "policy": closeout.get("policy"), + "result": result, + "source_policy": closeout.get("source_policy"), + "signing_issuer_closeout_ready_count": closeout_ready_count, + "signing_issuer_closeout_check_count": check_count, + "signing_issuer_closeout_pass_count": pass_count, + "signing_issuer_closeout_waiting_count": waiting_count, + "signing_issuer_guard_ready_count": guard_ready_count, + "signing_issuer_guard_check_count": guard_check_count, + "signing_decision_input_requirement_count": input_requirement_count, + "signing_decision_rejection_reason_count": rejection_reason_count, + "required_issuer_evidence_count": required_evidence_count, + "nonsecret_authorization_claim_count": nonsecret_claim_count, + "post_apply_verifier_required_count": verifier_required_count, + "same_run_truth_required_count": same_run_truth_count, + "reads_secret_count": reads_secret_count, + "executes_script_count": executes_script_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_database_apply_now": bool( + issuer_closeout.get("ready_for_database_apply_now") + or package.get("ready_for_database_apply_now") + or contract.get("ready_for_database_apply_now") + ), + "issues_database_apply_authorization": bool( + issuer_closeout.get("issues_database_apply_authorization") + or package.get("issues_database_apply_authorization") + or contract.get("issues_database_apply_authorization") + ), + "signs_database_apply_authorization": bool( + issuer_closeout.get("signs_database_apply_authorization") + or package.get("signs_database_apply_authorization") + or contract.get("signs_database_apply_authorization") + ), + "secret_material_included": bool(package.get("secret_material_included")), + "secret_material_required_in_preview": bool( + package.get("secret_material_required_in_preview") + or contract.get("secret_material_required_in_preview") + ), + "ready_for_future_final_signable_request_package": bool( + package.get("ready_for_future_final_signable_request_package") + ), + "permits_future_final_signable_request_package_lane": bool( + contract.get("permits_future_final_signable_request_package_lane") + ), + "requires_post_apply_verifier": bool( + package.get("requires_post_apply_verifier") + ), + "requires_fresh_production_truth": bool( + package.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 signing issuer closeout", + "warning", + f"PChome auto-policy signing issuer closeout 例行監控暫時無法讀取:{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_signing_issuer_closeout", + }, + ) + + def _ai_surface_html_readback_check() -> Dict[str, Any]: """Read-only AI product surface guardrail readback.""" try: @@ -3282,6 +3706,7 @@ def collect_ai_automation_smoke(*, record_history: bool = True, history_limit: i _pchome_auto_policy_signing_decision_preflight_check(), _pchome_auto_policy_signing_decision_closeout_check(), _pchome_auto_policy_signing_issuer_guard_check(), + _pchome_auto_policy_signing_issuer_closeout_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 ebfec5b..dd4db44 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": 11, + "ok": 12, "warning": 1, "critical": 0, - "total": 12, + "total": 13, "primary_human_gate_count": 0, "writes_database_count": 0, }, @@ -124,6 +124,7 @@ def test_system_metrics_exports_scheduled_health_summary(): {"key": "pchome_auto_policy_signing_decision_preflight", "status": "ok"}, {"key": "pchome_auto_policy_signing_decision_closeout", "status": "ok"}, {"key": "pchome_auto_policy_signing_issuer_guard", "status": "ok"}, + {"key": "pchome_auto_policy_signing_issuer_closeout", "status": "ok"}, {"key": "sitewide_ui_ux_agent", "status": "ok"}, {"key": "sitewide_visual_qa", "status": "ok"}, ], @@ -131,7 +132,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"} 11.0' in output + assert 'momo_ai_automation_scheduled_health_summary_total{status="ok"} 12.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' @@ -169,6 +170,10 @@ def test_system_metrics_exports_scheduled_health_summary(): 'momo_ai_automation_scheduled_health_family_status{family="pchome_auto_policy_signing_issuer_guard",status="ok"} 1.0' in output ) + assert ( + 'momo_ai_automation_scheduled_health_family_status{family="pchome_auto_policy_signing_issuer_closeout",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 078e6a5..62810cd 100644 --- a/tests/test_ai_automation_smoke_service.py +++ b/tests/test_ai_automation_smoke_service.py @@ -269,6 +269,53 @@ def _auto_policy_signing_issuer_guard_history_check(status="ok"): } +def _auto_policy_signing_issuer_closeout_history_check(status="ok"): + return { + "name": "PChome auto-policy signing issuer closeout", + "status": status, + "summary": "PChome auto-policy signing issuer closeout 已自動完成", + "details": { + "policy": ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_" + "signing_issuer_closeout" + ), + "result": "DB_APPLY_AUTHORIZATION_SIGNING_ISSUER_CLOSEOUT_READY", + "signing_issuer_closeout_ready_count": 1, + "signing_issuer_closeout_check_count": 12, + "signing_issuer_closeout_pass_count": 12, + "signing_issuer_closeout_waiting_count": 0, + "signing_issuer_guard_ready_count": 1, + "signing_issuer_guard_check_count": 12, + "signing_decision_input_requirement_count": 10, + "signing_decision_rejection_reason_count": 11, + "required_issuer_evidence_count": 9, + "nonsecret_authorization_claim_count": 8, + "post_apply_verifier_required_count": 1, + "same_run_truth_required_count": 1, + "reads_secret_count": 0, + "executes_sql_count": 0, + "writes_database_count": 0, + "signs_database_apply_authorization_count": 0, + "primary_human_gate_count": 0, + "manual_review_mode": "exception_only", + "payload_source": "local_contract_fixture", + "execute_fetch": True, + "outbound_network": False, + "business_data_source": 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, + "ready_for_future_final_signable_request_package": True, + "permits_future_final_signable_request_package_lane": True, + "next_machine_action": ( + "continue_to_pchome_auto_policy_signing_execution_preflight_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 @@ -311,6 +358,7 @@ def test_collect_ai_automation_smoke_uses_worst_status(monkeypatch): monkeypatch.setattr(smoke, "_pchome_auto_policy_signing_decision_preflight_check", lambda: smoke._check("auto-policy signing preflight", "ok", "ok")) monkeypatch.setattr(smoke, "_pchome_auto_policy_signing_decision_closeout_check", lambda: smoke._check("auto-policy signing closeout", "ok", "ok")) monkeypatch.setattr(smoke, "_pchome_auto_policy_signing_issuer_guard_check", lambda: smoke._check("auto-policy signing issuer guard", "ok", "ok")) + monkeypatch.setattr(smoke, "_pchome_auto_policy_signing_issuer_closeout_check", lambda: smoke._check("auto-policy signing issuer closeout", "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")) @@ -318,7 +366,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": 15, "warning": 1, "critical": 1, "total": 17} + assert result["summary"] == {"ok": 16, "warning": 1, "critical": 1, "total": 18} def test_pchome_controlled_apply_drift_monitor_reports_verified_zero_drift(monkeypatch): @@ -993,6 +1041,121 @@ def test_pchome_auto_policy_signing_issuer_guard_reports_signable_no_write_bound ) +def test_pchome_auto_policy_signing_issuer_closeout_reports_final_signable_no_write_package(monkeypatch): + from services import ai_automation_smoke_service as smoke + from services import pchome_mapping_backlog_service as backlog + + captured = {} + + def fake_closeout(*_args, **kwargs): + captured.update(kwargs) + return { + "policy": ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_" + "signing_issuer_closeout" + ), + "result": "DB_APPLY_AUTHORIZATION_SIGNING_ISSUER_CLOSEOUT_READY", + "source_policy": ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_" + "signing_issuer_guard" + ), + "summary": { + "authorization_signing_issuer_closeout_ready_count": 1, + "signing_issuer_closeout_check_count": 12, + "signing_issuer_closeout_pass_count": 12, + "signing_issuer_closeout_waiting_count": 0, + "authorization_signing_issuer_guard_ready_count": 1, + "signing_issuer_guard_check_count": 12, + "signing_decision_input_requirement_count": 10, + "signing_decision_rejection_reason_count": 11, + "required_issuer_evidence_count": 9, + "nonsecret_authorization_claim_count": 8, + "post_apply_verifier_required_count": 1, + "same_run_truth_required_count": 1, + "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, + "manual_review_required_count": 0, + }, + "future_authorization_signing_issuer_closeout": { + "ready_for_future_signing_issuer_closeout": True, + "can_enter_future_final_signable_request_package_lane": True, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + }, + "final_signable_request_package": { + "authorization_material_type": "final_signable_request_package", + "ready_for_future_final_signable_request_package": True, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "hash_matches": True, + "requires_post_apply_verifier": True, + "requires_fresh_production_truth_in_same_run": True, + "secret_material_included": False, + "secret_material_required_in_preview": False, + "reads_secret_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + }, + "signing_issuer_closeout_contract": { + "machine_verifiable": True, + "permits_future_final_signable_request_package_lane": True, + "issues_database_apply_authorization": False, + "ready_for_database_apply_now": False, + "signs_database_apply_authorization": False, + "writes_database": False, + "secret_material_required_in_preview": False, + }, + "safety": { + "reads_secret_in_preview": False, + "executes_script": False, + "executes_endpoint": False, + "executes_migration": False, + "executes_sql": False, + "writes_database": False, + "signs_database_apply_authorization": False, + "manual_review_mode": "exception_only", + }, + } + + monkeypatch.setattr( + backlog, + "build_pchome_auto_policy_db_apply_authorization_signing_issuer_closeout", + fake_closeout, + ) + + result = smoke._pchome_auto_policy_signing_issuer_closeout_check() + + assert result["status"] == "ok" + assert captured["execute_fetch"] is True + assert captured["http_get"] is smoke._pchome_auto_policy_machine_fetch_evidence_get + assert result["details"]["signing_issuer_closeout_pass_count"] == 12 + assert result["details"]["signing_issuer_guard_check_count"] == 12 + assert result["details"]["signing_decision_input_requirement_count"] == 10 + assert result["details"]["signing_decision_rejection_reason_count"] == 11 + 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"]["writes_database_count"] == 0 + assert result["details"]["signs_database_apply_authorization_count"] == 0 + assert result["details"]["issues_database_apply_authorization"] is False + assert result["details"]["signs_database_apply_authorization"] is False + assert result["details"]["secret_material_included"] is False + assert result["details"]["secret_material_required_in_preview"] is False + assert result["details"]["ready_for_future_final_signable_request_package"] is True + assert result["details"]["permits_future_final_signable_request_package_lane"] is True + assert result["details"]["next_machine_action"] == ( + "continue_to_pchome_auto_policy_signing_execution_preflight_lane" + ) + + def test_collect_ai_automation_smoke_persists_recent_history(tmp_path, monkeypatch): from services import ai_automation_smoke_service as smoke @@ -1013,6 +1176,7 @@ def test_collect_ai_automation_smoke_persists_recent_history(tmp_path, monkeypat monkeypatch.setattr(smoke, "_pchome_auto_policy_signing_decision_preflight_check", lambda: smoke._check("auto-policy signing preflight", "ok", "ok")) monkeypatch.setattr(smoke, "_pchome_auto_policy_signing_decision_closeout_check", lambda: smoke._check("auto-policy signing closeout", "ok", "ok")) monkeypatch.setattr(smoke, "_pchome_auto_policy_signing_issuer_guard_check", lambda: smoke._check("auto-policy signing issuer guard", "ok", "ok")) + monkeypatch.setattr(smoke, "_pchome_auto_policy_signing_issuer_closeout_check", lambda: smoke._check("auto-policy signing issuer closeout", "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")) @@ -1071,7 +1235,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": 17, "warning": 0, "critical": 0, "total": 17}, + "summary": {"ok": 18, "warning": 0, "critical": 0, "total": 18}, "checks": [ { "name": "PChome 受控落地 drift monitor", @@ -1092,6 +1256,7 @@ def test_scheduled_automation_health_summary_reads_history_without_side_effects( _auto_policy_signing_decision_preflight_history_check(), _auto_policy_signing_decision_closeout_history_check(), _auto_policy_signing_issuer_guard_history_check(), + _auto_policy_signing_issuer_closeout_history_check(), { "name": "AI surface HTML readback", "status": "ok", @@ -1153,7 +1318,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"] == 14 + assert summary["summary"]["total"] == 15 assert summary["summary"]["primary_human_gate_count"] == 0 assert summary["summary"]["writes_database_count"] == 0 assert pchome_family["status"] == "ok" @@ -1259,6 +1424,60 @@ def test_scheduled_automation_health_summary_reads_history_without_side_effects( assert signing_issuer_guard_family["details"]["secret_material_included"] is False assert signing_issuer_guard_family["details"]["secret_material_required_in_preview"] is False assert signing_issuer_guard_family["details"]["ready_for_future_signable_request_boundary"] is True + signing_issuer_closeout_family = next( + item for item in summary["families"] + if item["key"] == "pchome_auto_policy_signing_issuer_closeout" + ) + assert signing_issuer_closeout_family["status"] == "ok" + assert ( + signing_issuer_closeout_family["details"]["signing_issuer_closeout_pass_count"] + == 12 + ) + assert ( + signing_issuer_closeout_family["details"]["signing_issuer_guard_check_count"] + == 12 + ) + assert ( + signing_issuer_closeout_family["details"][ + "signing_decision_input_requirement_count" + ] + == 10 + ) + assert ( + signing_issuer_closeout_family["details"][ + "signing_decision_rejection_reason_count" + ] + == 11 + ) + assert ( + signing_issuer_closeout_family["details"]["payload_source"] + == "local_contract_fixture" + ) + assert signing_issuer_closeout_family["details"]["outbound_network"] is False + assert signing_issuer_closeout_family["details"]["business_data_source"] is False + assert signing_issuer_closeout_family["details"]["primary_human_gate_count"] == 0 + assert signing_issuer_closeout_family["details"]["writes_database_count"] == 0 + assert ( + signing_issuer_closeout_family["details"][ + "signs_database_apply_authorization_count" + ] + == 0 + ) + assert ( + signing_issuer_closeout_family["details"]["signs_database_apply_authorization"] + is False + ) + assert signing_issuer_closeout_family["details"]["secret_material_included"] is False + assert ( + signing_issuer_closeout_family["details"]["secret_material_required_in_preview"] + is False + ) + assert ( + signing_issuer_closeout_family["details"][ + "ready_for_future_final_signable_request_package" + ] + is True + ) surface_family = next( item for item in summary["families"] if item["key"] == "ai_surface_html_readback" @@ -1293,7 +1512,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": 16, "warning": 0, "critical": 1, "total": 17}, + "summary": {"ok": 17, "warning": 0, "critical": 1, "total": 18}, "checks": [ { "name": "PChome 受控落地 drift monitor", @@ -1308,6 +1527,7 @@ def test_scheduled_automation_health_summary_can_use_current_smoke_without_recor _auto_policy_signing_decision_preflight_history_check(), _auto_policy_signing_decision_closeout_history_check(), _auto_policy_signing_issuer_guard_history_check(), + _auto_policy_signing_issuer_closeout_history_check(), { "name": "AI surface HTML readback", "status": "ok", @@ -1363,7 +1583,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": 15, "warning": 0, "critical": 0, "total": 15}, + "summary": {"ok": 16, "warning": 0, "critical": 0, "total": 16}, "checks": [], }, ensure_ascii=False) + "\n", encoding="utf-8", @@ -1404,6 +1624,11 @@ def test_scheduled_automation_health_summary_falls_back_to_visual_qa_artifact(tm "_pchome_auto_policy_signing_issuer_guard_check", lambda: _auto_policy_signing_issuer_guard_history_check(), ) + monkeypatch.setattr( + smoke, + "_pchome_auto_policy_signing_issuer_closeout_check", + lambda: _auto_policy_signing_issuer_closeout_history_check(), + ) monkeypatch.setattr( surface_service, "build_sitewide_visual_qa_readback", @@ -1452,7 +1677,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": 16, "warning": 0, "critical": 0, "total": 16}, + "summary": {"ok": 17, "warning": 0, "critical": 0, "total": 17}, "checks": [], }, ensure_ascii=False) + "\n", encoding="utf-8", @@ -1493,6 +1718,11 @@ def test_scheduled_automation_health_summary_route_returns_compact_payload(tmp_p "_pchome_auto_policy_signing_issuer_guard_check", lambda: _auto_policy_signing_issuer_guard_history_check(), ) + monkeypatch.setattr( + smoke, + "_pchome_auto_policy_signing_issuer_closeout_check", + lambda: _auto_policy_signing_issuer_closeout_history_check(), + ) app = Flask(__name__) with app.test_request_context( @@ -1723,6 +1953,7 @@ def test_surface_html_readback_check_is_part_of_ai_smoke(monkeypatch): monkeypatch.setattr(smoke, "_pchome_auto_policy_signing_decision_preflight_check", lambda: smoke._check("auto-policy signing preflight", "ok", "ok")) monkeypatch.setattr(smoke, "_pchome_auto_policy_signing_decision_closeout_check", lambda: smoke._check("auto-policy signing closeout", "ok", "ok")) monkeypatch.setattr(smoke, "_pchome_auto_policy_signing_issuer_guard_check", lambda: smoke._check("auto-policy signing issuer guard", "ok", "ok")) + monkeypatch.setattr(smoke, "_pchome_auto_policy_signing_issuer_closeout_check", lambda: smoke._check("auto-policy signing issuer closeout", "ok", "ok")) monkeypatch.setattr(smoke, "_sitewide_visual_qa_check", lambda: smoke._check( "Sitewide visual QA readback", "ok", @@ -1744,7 +1975,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"] == 17 + assert result["summary"]["total"] == 18 assert surface_check["status"] == "ok" assert surface_check["details"]["checked_surface_count"] == 10 assert sitewide_check["status"] == "ok"