diff --git a/config.py b/config.py index fc9f2de..3735b08 100644 --- a/config.py +++ b/config.py @@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.739" +SYSTEM_VERSION = "V10.740" 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 40aeb98..07564d3 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 已建立,GCP embedding 熔斷延後處理、110 proxy rescue 與 direct host health skip 已建立 -> **適用版本**: V10.739 +> **狀態**: 🟢 四 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 已建立,GCP embedding 熔斷延後處理、110 proxy rescue 與 direct host health skip 已建立 +> **適用版本**: V10.740 --- @@ -865,6 +865,7 @@ POSTGRES_HOST=momo-db | 2026-07-03 | PChome auto-policy signed receipt closeout 必須有 runtime monitoring | V10.737 起 `/api/ai-automation/smoke`、`/api/ai-automation/scheduled-health-summary` 與 `/metrics` 必須輸出 `PChome auto-policy signed receipt closeout` / `pchome_auto_policy_signed_receipt_closeout`;此 runtime check 自動驗證 detached receipt verification boundary、required external receipt evidence、acceptance gates、detached verification checks、operator-held secret boundary、post-apply verifier requirement 與 same-run production truth requirement,但明確標記 `reads_secret_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`、`external_signed_authorization_receipt_included=false`、`signed_authorization_receipt_included=false`、`signature_material_included=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、不執行 SQL、不寫 DB、不產生 signed receipt、不含 signature material、不簽發 apply authorization。 | | 2026-07-03 | PChome auto-policy signed receipt evidence intake 必須有 runtime monitoring | V10.738 起 `/api/ai-automation/smoke`、`/api/ai-automation/scheduled-health-summary` 與 `/metrics` 必須輸出 `PChome auto-policy signed receipt evidence intake` / `pchome_auto_policy_signed_receipt_evidence_intake`;此 runtime check 自動驗證 detached verification evidence schema、12 個 evidence fields、10 個 detached verification acceptance gates、10 個 required external receipt evidence、8 個 external receipt acceptance gates、10 個 detached receipt verification checks、operator-held secret boundary、post-apply verifier requirement 與 same-run production truth requirement,但明確標記 `reads_secret_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`、`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、不執行 SQL、不寫 DB、不產生 signed receipt、不含 signature material、不接受 plaintext secret、不簽發 apply authorization。 | | 2026-07-03 | PChome auto-policy detached verification evidence validation 必須有 runtime monitoring | V10.739 起 `/api/ai-automation/smoke`、`/api/ai-automation/scheduled-health-summary` 與 `/metrics` 必須輸出 `PChome auto-policy detached verification evidence validation` / `pchome_auto_policy_detached_verification_evidence_validation`;此 runtime check 自動驗證 verifier receipt closeout boundary、12 個 verifier receipt fields、10 個 verifier receipt acceptance gates、12 個 validation checks、10 個 required external receipt evidence、8 個 external receipt acceptance gates、10 個 detached receipt verification checks、post-apply verifier requirement 與 same-run production truth requirement,但明確標記 `reads_secret_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 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-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 c2ac9c2..234068e 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.739`,除非有明確版本 bump、部署與 readback。 +2. 版本不得搞錯;目前正式環境 `/health` 版本維持 `V10.740`,除非有明確版本 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.739`。 +- 正式環境版本真相 guard:`/health` 是最高真相,目前維持 `V10.740`。 - Gitea `dev` / `main` source truth 對齊。 - Retry exception controlled apply executor 已落地,目標表為 `pchome_product_matches`。 - 正式 DB 已 controlled apply 4 個 selectors: @@ -183,10 +183,17 @@ - `/api/ai-automation/scheduled-health-summary` 已輸出 `pchome_auto_policy_detached_verification_evidence_validation` family - `/metrics` 已輸出 `momo_ai_automation_scheduled_health_family_status{family="pchome_auto_policy_detached_verification_evidence_validation",...}` - 不執行 detached signature verification、不持久化 verifier receipt、不包含 external signed receipt、不包含 signed receipt、不包含 signature material、不接受 plaintext secret、不讀 secret、不執行 SQL、不寫 DB、不簽發 apply authorization,下一個機器動作是 verifier receipt closeout lane +- PChome auto-policy verifier receipt closeout runtime monitoring 已完成: + - `/api/ai-automation/smoke` 已加入 `PChome auto-policy verifier receipt closeout` + - 以本機 contract fixture + machine evidence adapter 自動推進 database apply authorization verifier handoff path + - 明確標記 `verifier_receipt_closeout_check_count=12`、`verifier_receipt_closeout_pass_count=12`、`detached_verification_evidence_validation_check_count=12`、`verifier_receipt_evidence_handoff_count=1`、`verifier_receipt_evidence_handoff_field_count=12`、`verifier_receipt_handoff_acceptance_gate_count=10`、`required_external_receipt_evidence_count=10`、`external_receipt_acceptance_gate_count=8`、`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_verifier_receipt_closeout` family + - `/metrics` 已輸出 `momo_ai_automation_scheduled_health_family_status{family="pchome_auto_policy_verifier_receipt_closeout",...}` + - 不執行 detached signature verification、不持久化 verifier receipt、不包含 external signed receipt、不包含 signed receipt、不包含 signature material、不接受 plaintext secret、不讀 secret、不執行 SQL、不寫 DB、不簽發 apply authorization,下一個機器動作是 database apply authorization verifier handoff lane 進行中 / 下一步,必須照順序: -1. 將 auto-policy detached verification evidence validation 往 verifier receipt closeout lane 推進。 +1. 將 auto-policy verifier receipt closeout 往 database apply authorization verifier handoff lane 推進。 完成標準: @@ -353,7 +360,7 @@ 進行中 / 下一步,必須照順序: -1. 將 auto-policy detached verification evidence validation 推進到 verifier receipt closeout lane。 +1. 將 auto-policy verifier receipt closeout 推進到 database apply authorization verifier handoff lane。 2. 對其他 safe mapping backlog lanes 套用 receipt / replay / drift verifier 模式。 3. 為每一類 controlled apply 自動產生 rollback evidence package。 4. 把 AI automation metrics 持續整合進既有 observability surfaces。 @@ -438,7 +445,8 @@ | P3.15 | PChome auto-policy signed receipt preflight monitoring | 已完成 | AI smoke + scheduled family + metrics family prove external signing receipt evidence boundary, 10 receipt evidence fields, 8 acceptance gates, no signed receipt, no signature material, no secret read, no DB write, no signing | P3.16 signed receipt closeout monitoring | | P3.16 | PChome auto-policy signed receipt closeout monitoring | 已完成 | AI smoke + scheduled family + metrics family prove detached receipt verification boundary, 10 receipt evidence fields, 8 acceptance gates, 10 detached checks, no detached verification execution, no signed receipt, no signature material, no secret read, no DB write, no signing | P3.17 signed receipt evidence intake monitoring | | P3.17 | PChome auto-policy signed receipt evidence intake monitoring | 已完成 | AI smoke + scheduled family + metrics family prove detached verification evidence schema, 12 evidence fields, 10 detached acceptance gates, no detached verification execution, no signed receipt, no signature material, no plaintext secret, no secret read, no DB write, no signing | P3.18 detached verification evidence validation monitoring | -| P3.18 | PChome auto-policy detached verification evidence validation monitoring | 已完成 | AI smoke + scheduled family + metrics family prove verifier receipt closeout boundary, 12 verifier receipt fields, 10 verifier 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.19 verifier receipt closeout lane | +| P3.18 | PChome auto-policy detached verification evidence validation monitoring | 已完成 | AI smoke + scheduled family + metrics family prove verifier receipt closeout boundary, 12 verifier receipt fields, 10 verifier 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.19 verifier receipt closeout monitoring | +| 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 database apply authorization verifier handoff 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 5396f8f..948098a 100644 --- a/services/ai_automation_smoke_service.py +++ b/services/ai_automation_smoke_service.py @@ -399,6 +399,23 @@ def build_scheduled_automation_health_summary( auto_policy_detached_validation_details = ( auto_policy_detached_validation.get("details") or {} ) + auto_policy_verifier_receipt_closeout = _find_check( + source_result, + "PChome auto-policy verifier receipt closeout", + ) + auto_policy_verifier_receipt_closeout_details = ( + auto_policy_verifier_receipt_closeout.get("details") or {} + ) + if ( + not auto_policy_verifier_receipt_closeout + or not auto_policy_verifier_receipt_closeout_details + ): + auto_policy_verifier_receipt_closeout = ( + _pchome_auto_policy_verifier_receipt_closeout_check() + ) + auto_policy_verifier_receipt_closeout_details = ( + auto_policy_verifier_receipt_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") @@ -2882,6 +2899,306 @@ def build_scheduled_automation_health_summary( "writes_database": False, }, }, + { + "key": "pchome_auto_policy_verifier_receipt_closeout", + "label": "PChome auto-policy verifier receipt closeout", + "status": auto_policy_verifier_receipt_closeout.get("status") or "warning", + "summary": ( + auto_policy_verifier_receipt_closeout.get("summary") + or "PChome auto-policy verifier receipt closeout has no latest check." + ), + "next_machine_action": auto_policy_verifier_receipt_closeout_details.get( + "next_machine_action" + ) + or ( + "continue_pchome_auto_policy_verifier_receipt_closeout_monitoring" + if auto_policy_verifier_receipt_closeout.get("status") == "ok" + else "refresh_pchome_auto_policy_verifier_receipt_closeout" + ), + "details": { + "policy": auto_policy_verifier_receipt_closeout_details.get("policy"), + "result": auto_policy_verifier_receipt_closeout_details.get("result"), + "verifier_receipt_closeout_ready_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "verifier_receipt_closeout_ready_count" + ) + or 0 + ), + "verifier_receipt_closeout_check_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "verifier_receipt_closeout_check_count" + ) + or 0 + ), + "verifier_receipt_closeout_pass_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "verifier_receipt_closeout_pass_count" + ) + or 0 + ), + "verifier_receipt_closeout_waiting_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "verifier_receipt_closeout_waiting_count" + ) + or 0 + ), + "detached_verification_evidence_validation_ready_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "detached_verification_evidence_validation_ready_count" + ) + or 0 + ), + "detached_verification_evidence_validation_check_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "detached_verification_evidence_validation_check_count" + ) + or 0 + ), + "signed_receipt_evidence_intake_check_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "signed_receipt_evidence_intake_check_count" + ) + or 0 + ), + "verifier_receipt_closeout_boundary_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "verifier_receipt_closeout_boundary_count" + ) + or 0 + ), + "verifier_receipt_evidence_handoff_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "verifier_receipt_evidence_handoff_count" + ) + or 0 + ), + "required_external_receipt_evidence_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "required_external_receipt_evidence_count" + ) + or 0 + ), + "external_receipt_acceptance_gate_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "external_receipt_acceptance_gate_count" + ) + or 0 + ), + "verifier_receipt_field_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "verifier_receipt_field_count" + ) + or 0 + ), + "verifier_receipt_acceptance_gate_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "verifier_receipt_acceptance_gate_count" + ) + or 0 + ), + "verifier_receipt_evidence_handoff_field_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "verifier_receipt_evidence_handoff_field_count" + ) + or 0 + ), + "verifier_receipt_handoff_acceptance_gate_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "verifier_receipt_handoff_acceptance_gate_count" + ) + or 0 + ), + "detached_verification_evidence_field_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "detached_verification_evidence_field_count" + ) + or 0 + ), + "detached_verification_acceptance_gate_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "detached_verification_acceptance_gate_count" + ) + or 0 + ), + "operator_held_secret_boundary_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "operator_held_secret_boundary_count" + ) + or 0 + ), + "post_apply_verifier_required_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "post_apply_verifier_required_count" + ) + or 0 + ), + "same_run_truth_required_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "same_run_truth_required_count" + ) + or 0 + ), + "reads_secret_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "reads_secret_count" + ) + or 0 + ), + "executes_script_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "executes_script_count" + ) + or 0 + ), + "executes_sql_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "executes_sql_count" + ) + or 0 + ), + "writes_database_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "writes_database_count" + ) + or 0 + ), + "signs_database_apply_authorization_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "signs_database_apply_authorization_count" + ) + or 0 + ), + "primary_human_gate_count": int( + auto_policy_verifier_receipt_closeout_details.get( + "primary_human_gate_count" + ) + or 0 + ), + "manual_review_mode": auto_policy_verifier_receipt_closeout_details.get( + "manual_review_mode" + ), + "payload_source": auto_policy_verifier_receipt_closeout_details.get( + "payload_source" + ), + "execute_fetch": bool( + auto_policy_verifier_receipt_closeout_details.get("execute_fetch") + ), + "outbound_network": bool( + auto_policy_verifier_receipt_closeout_details.get( + "outbound_network" + ) + ), + "business_data_source": bool( + auto_policy_verifier_receipt_closeout_details.get( + "business_data_source" + ) + ), + "secret_reference_mode": auto_policy_verifier_receipt_closeout_details.get( + "secret_reference_mode" + ), + "requires_detached_signature_verification": bool( + auto_policy_verifier_receipt_closeout_details.get( + "requires_detached_signature_verification" + ) + ), + "detached_signature_verification_performed": bool( + auto_policy_verifier_receipt_closeout_details.get( + "detached_signature_verification_performed" + ) + ), + "verifier_receipt_persisted": bool( + auto_policy_verifier_receipt_closeout_details.get( + "verifier_receipt_persisted" + ) + ), + "external_signed_authorization_receipt_required_in_future": bool( + auto_policy_verifier_receipt_closeout_details.get( + "external_signed_authorization_receipt_required_in_future" + ) + ), + "external_signed_authorization_receipt_included": bool( + auto_policy_verifier_receipt_closeout_details.get( + "external_signed_authorization_receipt_included" + ) + ), + "signed_authorization_receipt_included": bool( + auto_policy_verifier_receipt_closeout_details.get( + "signed_authorization_receipt_included" + ) + ), + "signature_material_included": bool( + auto_policy_verifier_receipt_closeout_details.get( + "signature_material_included" + ) + ), + "ready_for_database_apply_now": bool( + auto_policy_verifier_receipt_closeout_details.get( + "ready_for_database_apply_now" + ) + ), + "issues_database_apply_authorization": bool( + auto_policy_verifier_receipt_closeout_details.get( + "issues_database_apply_authorization" + ) + ), + "signs_database_apply_authorization": bool( + auto_policy_verifier_receipt_closeout_details.get( + "signs_database_apply_authorization" + ) + ), + "secret_material_included": bool( + auto_policy_verifier_receipt_closeout_details.get( + "secret_material_included" + ) + ), + "secret_material_required_in_preview": bool( + auto_policy_verifier_receipt_closeout_details.get( + "secret_material_required_in_preview" + ) + ), + "accepts_plaintext_secret": bool( + auto_policy_verifier_receipt_closeout_details.get( + "accepts_plaintext_secret" + ) + ), + "ready_for_future_verifier_receipt_closeout": bool( + auto_policy_verifier_receipt_closeout_details.get( + "ready_for_future_verifier_receipt_closeout" + ) + ), + "can_enter_future_database_apply_authorization_verifier_handoff": bool( + auto_policy_verifier_receipt_closeout_details.get( + "can_enter_future_database_apply_authorization_verifier_handoff" + ) + ), + "verifier_receipt_evidence_handoff_ready": bool( + auto_policy_verifier_receipt_closeout_details.get( + "verifier_receipt_evidence_handoff_ready" + ) + ), + "ready_for_future_verifier_receipt_evidence_handoff": bool( + auto_policy_verifier_receipt_closeout_details.get( + "ready_for_future_verifier_receipt_evidence_handoff" + ) + ), + "permits_future_database_apply_authorization_verifier_handoff": bool( + auto_policy_verifier_receipt_closeout_details.get( + "permits_future_database_apply_authorization_verifier_handoff" + ) + ), + "persists_verifier_receipt": bool( + auto_policy_verifier_receipt_closeout_details.get( + "persists_verifier_receipt" + ) + ), + "performs_detached_signature_verification": bool( + auto_policy_verifier_receipt_closeout_details.get( + "performs_detached_signature_verification" + ) + ), + "writes_database": False, + }, + }, { "key": "ai_surface_html_readback", "label": "AI surface HTML readback", @@ -7407,6 +7724,447 @@ def _pchome_auto_policy_detached_verification_evidence_validation_check() -> Dic ) +def _pchome_auto_policy_verifier_receipt_closeout_check() -> Dict[str, Any]: + """Read-only monitor for the future database apply verifier handoff.""" + 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_verifier_receipt_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 {} + future_closeout = closeout.get("future_verifier_receipt_closeout") or {} + handoff = closeout.get("verifier_receipt_evidence_handoff") or {} + operator_boundary = handoff.get("operator_held_secret_boundary_contract") or {} + contract = closeout.get("verifier_receipt_closeout_contract") or {} + safety = closeout.get("safety") or {} + handoff_fields = list(handoff.get("verifier_receipt_evidence_handoff_fields") or []) + handoff_gates = list( + handoff.get("verifier_receipt_handoff_acceptance_gates") or [] + ) + result = str(closeout.get("result") or "UNKNOWN") + + ready_count = int( + summary.get("authorization_verifier_receipt_closeout_ready_count") or 0 + ) + check_count = int(summary.get("verifier_receipt_closeout_check_count") or 0) + pass_count = int(summary.get("verifier_receipt_closeout_pass_count") or 0) + waiting_count = int(summary.get("verifier_receipt_closeout_waiting_count") or 0) + validation_ready_count = int( + summary.get("authorization_detached_verification_evidence_validation_ready_count") + or 0 + ) + validation_check_count = int( + summary.get("detached_verification_evidence_validation_check_count") or 0 + ) + intake_ready_count = int( + summary.get("authorization_signed_receipt_evidence_intake_ready_count") or 0 + ) + intake_check_count = int( + summary.get("signed_receipt_evidence_intake_check_count") or 0 + ) + boundary_count = int(summary.get("verifier_receipt_closeout_boundary_count") or 0) + handoff_count = int(summary.get("verifier_receipt_evidence_handoff_count") or 0) + required_evidence_count = int( + summary.get("required_external_receipt_evidence_count") or 0 + ) + acceptance_gate_count = int( + summary.get("external_receipt_acceptance_gate_count") or 0 + ) + verifier_field_count = int(summary.get("verifier_receipt_field_count") or 0) + verifier_acceptance_gate_count = int( + summary.get("verifier_receipt_acceptance_gate_count") or 0 + ) + handoff_field_count = int( + summary.get("verifier_receipt_evidence_handoff_field_count") or 0 + ) + handoff_acceptance_gate_count = int( + summary.get("verifier_receipt_handoff_acceptance_gate_count") or 0 + ) + evidence_field_count = int( + summary.get("detached_verification_evidence_field_count") or 0 + ) + evidence_acceptance_gate_count = int( + summary.get("detached_verification_acceptance_gate_count") or 0 + ) + secret_boundary_count = int(summary.get("operator_held_secret_boundary_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("updates_mapping") is not False, + safety.get("dispatches_telegram") is not False, + safety.get("llm_calls_in_gate") is not False, + future_closeout.get("ready_for_database_apply_now") is not False, + future_closeout.get("issues_database_apply_authorization") is not False, + future_closeout.get("signs_database_apply_authorization") is not False, + future_closeout.get("detached_signature_verification_performed") + is not False, + future_closeout.get("verifier_receipt_persisted") is not False, + future_closeout.get("external_signed_authorization_receipt_included") + is not False, + future_closeout.get("signed_authorization_receipt_included") is not False, + future_closeout.get("signature_material_included") is not False, + future_closeout.get("secret_material_included") is not False, + future_closeout.get("secret_material_required_in_preview") is not False, + future_closeout.get("reads_secret_in_preview") is not False, + handoff.get("detached_signature_verification_performed") is not False, + handoff.get("verifier_receipt_persisted") is not False, + handoff.get("external_signed_authorization_receipt_included") is not False, + handoff.get("signed_authorization_receipt_included") is not False, + handoff.get("signature_material_included") is not False, + handoff.get("secret_material_included") is not False, + handoff.get("secret_material_required_in_preview") is not False, + handoff.get("accepts_plaintext_secret") is not False, + handoff.get("reads_secret_in_preview") is not False, + handoff.get("executes_shell_in_preview") is not False, + handoff.get("executes_sql_in_preview") is not False, + handoff.get("writes_database_in_preview") is not False, + handoff.get("ready_for_database_apply_now") is not False, + handoff.get("issues_database_apply_authorization") is not False, + handoff.get("signs_database_apply_authorization") is not False, + operator_boundary.get("secret_material_included") is not False, + operator_boundary.get("secret_material_required_in_preview") is not False, + operator_boundary.get("reads_secret_in_preview") is not False, + operator_boundary.get("accepts_plaintext_secret") is not False, + operator_boundary.get("permits_secret_value_logging") 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("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("executes_in_preview") is not False, + contract.get("secret_material_required_in_preview") is not False, + ] + ) + closeout_contract_present = ( + ready_count == 1 + and validation_ready_count == 1 + and intake_ready_count == 1 + and check_count >= 12 + and pass_count == check_count + and waiting_count == 0 + and validation_check_count == 12 + and intake_check_count == 12 + and boundary_count == 1 + and handoff_count == 1 + and required_evidence_count == 10 + and acceptance_gate_count == 8 + and verifier_field_count == 12 + and verifier_acceptance_gate_count == 10 + and handoff_field_count == 12 + and handoff_acceptance_gate_count == 10 + and evidence_field_count == 12 + and evidence_acceptance_gate_count == 10 + and "verifier_receipt_sha256" in handoff_fields + and "verifier_receipt_not_persisted_by_preview" in handoff_gates + and secret_boundary_count == 1 + and verifier_required_count == 1 + and same_run_truth_count == 1 + and future_closeout.get("ready_for_future_verifier_receipt_closeout") + is True + and future_closeout.get( + "can_enter_future_database_apply_authorization_verifier_handoff" + ) + is True + and future_closeout.get("verifier_receipt_evidence_handoff_ready") + is True + and handoff.get("authorization_material_type") + == "verifier_receipt_evidence_handoff" + and handoff.get("ready_for_future_verifier_receipt_evidence_handoff") + is True + and handoff.get("requires_detached_signature_verification") is True + and handoff.get("detached_signature_verification_performed") is False + and handoff.get("verifier_receipt_persisted") is False + and handoff.get("external_signed_authorization_receipt_required_in_future") + is True + and handoff.get("external_signed_authorization_receipt_included") is False + and handoff.get("signed_authorization_receipt_included") is False + and handoff.get("signature_material_included") is False + and handoff.get("signer_key_id_reference_only") is True + and handoff.get("signature_algorithm_reference_only") is True + and handoff.get("accepts_plaintext_secret") is False + and handoff.get("hash_matches") is True + and handoff.get("requires_post_apply_verifier") is True + and handoff.get("requires_fresh_production_truth_in_same_run") is True + and operator_boundary.get("secret_reference_mode") + == "external_runtime_reference_only" + and contract.get("machine_verifiable") is True + and contract.get( + "permits_future_database_apply_authorization_verifier_handoff" + ) + is True + and contract.get("accepts_plaintext_secret") is False + and contract.get("performs_detached_signature_verification") is False + and contract.get("persists_verifier_receipt") is False + and safety.get("manual_review_mode") == "exception_only" + ) + + if risk_detected: + status = "critical" + summary_text = ( + "PChome auto-policy verifier receipt closeout 偵測到 secret、" + "signed material、verification execution、verifier receipt persist、SQL、DB write、簽發授權或人工主 gate 風險" + ) + next_machine_action = ( + "halt_pchome_auto_policy_verifier_receipt_closeout_and_inspect_risk" + ) + elif closeout_contract_present: + status = "ok" + summary_text = ( + "PChome auto-policy verifier receipt closeout 已自動完成 " + f"{pass_count}/{check_count} checks,verifier handoff 已綁定,verification/persist=0" + ) + next_machine_action = ( + "continue_to_pchome_auto_policy_database_apply_authorization_verifier_handoff_lane" + ) + else: + status = "warning" + summary_text = ( + "PChome auto-policy verifier receipt closeout 尚未達自動監控契約" + ) + next_machine_action = "refresh_pchome_auto_policy_verifier_receipt_closeout" + + return _check( + "PChome auto-policy verifier receipt closeout", + status, + summary_text, + { + "policy": closeout.get("policy"), + "result": result, + "source_policy": closeout.get("source_policy"), + "verifier_receipt_closeout_ready_count": ready_count, + "verifier_receipt_closeout_check_count": check_count, + "verifier_receipt_closeout_pass_count": pass_count, + "verifier_receipt_closeout_waiting_count": waiting_count, + "detached_verification_evidence_validation_ready_count": ( + validation_ready_count + ), + "detached_verification_evidence_validation_check_count": ( + validation_check_count + ), + "signed_receipt_evidence_intake_ready_count": intake_ready_count, + "signed_receipt_evidence_intake_check_count": intake_check_count, + "verifier_receipt_closeout_boundary_count": boundary_count, + "verifier_receipt_evidence_handoff_count": handoff_count, + "required_external_receipt_evidence_count": required_evidence_count, + "external_receipt_acceptance_gate_count": acceptance_gate_count, + "verifier_receipt_field_count": verifier_field_count, + "verifier_receipt_acceptance_gate_count": verifier_acceptance_gate_count, + "verifier_receipt_evidence_handoff_field_count": handoff_field_count, + "verifier_receipt_handoff_acceptance_gate_count": ( + handoff_acceptance_gate_count + ), + "detached_verification_evidence_field_count": evidence_field_count, + "detached_verification_acceptance_gate_count": ( + evidence_acceptance_gate_count + ), + "operator_held_secret_boundary_count": secret_boundary_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, + "secret_reference_mode": operator_boundary.get("secret_reference_mode"), + "requires_detached_signature_verification": bool( + handoff.get("requires_detached_signature_verification") + ), + "detached_signature_verification_performed": bool( + future_closeout.get("detached_signature_verification_performed") + or handoff.get("detached_signature_verification_performed") + or safety.get("performs_detached_signature_verification") + ), + "verifier_receipt_persisted": bool( + future_closeout.get("verifier_receipt_persisted") + or handoff.get("verifier_receipt_persisted") + or safety.get("persists_verifier_receipt") + ), + "external_signed_authorization_receipt_required_in_future": bool( + handoff.get( + "external_signed_authorization_receipt_required_in_future" + ) + ), + "external_signed_authorization_receipt_included": bool( + future_closeout.get( + "external_signed_authorization_receipt_included" + ) + or handoff.get("external_signed_authorization_receipt_included") + ), + "signed_authorization_receipt_included": bool( + future_closeout.get("signed_authorization_receipt_included") + or handoff.get("signed_authorization_receipt_included") + ), + "signature_material_included": bool( + future_closeout.get("signature_material_included") + or handoff.get("signature_material_included") + ), + "signer_key_id_reference_only": bool( + handoff.get("signer_key_id_reference_only") + ), + "signature_algorithm_reference_only": bool( + handoff.get("signature_algorithm_reference_only") + ), + "ready_for_database_apply_now": bool( + future_closeout.get("ready_for_database_apply_now") + or handoff.get("ready_for_database_apply_now") + or contract.get("ready_for_database_apply_now") + ), + "issues_database_apply_authorization": bool( + future_closeout.get("issues_database_apply_authorization") + or handoff.get("issues_database_apply_authorization") + or contract.get("issues_database_apply_authorization") + ), + "signs_database_apply_authorization": bool( + future_closeout.get("signs_database_apply_authorization") + or handoff.get("signs_database_apply_authorization") + or contract.get("signs_database_apply_authorization") + ), + "secret_material_included": bool( + future_closeout.get("secret_material_included") + or handoff.get("secret_material_included") + or operator_boundary.get("secret_material_included") + ), + "secret_material_required_in_preview": bool( + future_closeout.get("secret_material_required_in_preview") + or handoff.get("secret_material_required_in_preview") + or operator_boundary.get("secret_material_required_in_preview") + or contract.get("secret_material_required_in_preview") + ), + "accepts_plaintext_secret": bool( + handoff.get("accepts_plaintext_secret") + or contract.get("accepts_plaintext_secret") + or operator_boundary.get("accepts_plaintext_secret") + ), + "ready_for_future_verifier_receipt_closeout": bool( + future_closeout.get("ready_for_future_verifier_receipt_closeout") + ), + "can_enter_future_database_apply_authorization_verifier_handoff": bool( + future_closeout.get( + "can_enter_future_database_apply_authorization_verifier_handoff" + ) + ), + "verifier_receipt_evidence_handoff_ready": bool( + future_closeout.get("verifier_receipt_evidence_handoff_ready") + ), + "ready_for_future_verifier_receipt_evidence_handoff": bool( + handoff.get("ready_for_future_verifier_receipt_evidence_handoff") + ), + "permits_future_database_apply_authorization_verifier_handoff": bool( + contract.get( + "permits_future_database_apply_authorization_verifier_handoff" + ) + ), + "persists_verifier_receipt": bool( + contract.get("persists_verifier_receipt") + or safety.get("persists_verifier_receipt") + ), + "performs_detached_signature_verification": bool( + contract.get("performs_detached_signature_verification") + or safety.get("performs_detached_signature_verification") + ), + "requires_post_apply_verifier": bool( + handoff.get("requires_post_apply_verifier") + ), + "requires_fresh_production_truth": bool( + handoff.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 verifier receipt closeout", + "warning", + f"PChome auto-policy verifier receipt 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_verifier_receipt_closeout" + ), + }, + ) + + def _ai_surface_html_readback_check() -> Dict[str, Any]: """Read-only AI product surface guardrail readback.""" try: @@ -7648,6 +8406,7 @@ def collect_ai_automation_smoke(*, record_history: bool = True, history_limit: i _pchome_auto_policy_signed_receipt_closeout_check(), _pchome_auto_policy_signed_receipt_evidence_intake_check(), _pchome_auto_policy_detached_verification_evidence_validation_check(), + _pchome_auto_policy_verifier_receipt_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 be552e5..4615748 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": 18, + "ok": 19, "warning": 1, "critical": 0, - "total": 19, + "total": 20, "primary_human_gate_count": 0, "writes_database_count": 0, }, @@ -131,6 +131,7 @@ def test_system_metrics_exports_scheduled_health_summary(): {"key": "pchome_auto_policy_signed_receipt_closeout", "status": "ok"}, {"key": "pchome_auto_policy_signed_receipt_evidence_intake", "status": "ok"}, {"key": "pchome_auto_policy_detached_verification_evidence_validation", "status": "ok"}, + {"key": "pchome_auto_policy_verifier_receipt_closeout", "status": "ok"}, {"key": "sitewide_ui_ux_agent", "status": "ok"}, {"key": "sitewide_visual_qa", "status": "ok"}, ], @@ -138,7 +139,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"} 18.0' in output + assert 'momo_ai_automation_scheduled_health_summary_total{status="ok"} 19.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' @@ -204,6 +205,10 @@ def test_system_metrics_exports_scheduled_health_summary(): 'momo_ai_automation_scheduled_health_family_status{family="pchome_auto_policy_detached_verification_evidence_validation",status="ok"} 1.0' in output ) + assert ( + 'momo_ai_automation_scheduled_health_family_status{family="pchome_auto_policy_verifier_receipt_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 fb8ac54..e012f12 100644 --- a/tests/test_ai_automation_smoke_service.py +++ b/tests/test_ai_automation_smoke_service.py @@ -696,6 +696,82 @@ def _auto_policy_detached_verification_evidence_validation_history_check(status= } +def _auto_policy_verifier_receipt_closeout_history_check(status="ok"): + return { + "name": "PChome auto-policy verifier receipt closeout", + "status": status, + "summary": "PChome auto-policy verifier receipt closeout 已自動完成", + "details": { + "policy": ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_" + "verifier_receipt_closeout" + ), + "result": "DB_APPLY_AUTHORIZATION_VERIFIER_RECEIPT_CLOSEOUT_READY", + "verifier_receipt_closeout_ready_count": 1, + "verifier_receipt_closeout_check_count": 12, + "verifier_receipt_closeout_pass_count": 12, + "verifier_receipt_closeout_waiting_count": 0, + "detached_verification_evidence_validation_ready_count": 1, + "detached_verification_evidence_validation_check_count": 12, + "signed_receipt_evidence_intake_ready_count": 1, + "signed_receipt_evidence_intake_check_count": 12, + "verifier_receipt_closeout_boundary_count": 1, + "verifier_receipt_evidence_handoff_count": 1, + "required_external_receipt_evidence_count": 10, + "external_receipt_acceptance_gate_count": 8, + "verifier_receipt_field_count": 12, + "verifier_receipt_acceptance_gate_count": 10, + "verifier_receipt_evidence_handoff_field_count": 12, + "verifier_receipt_handoff_acceptance_gate_count": 10, + "detached_verification_evidence_field_count": 12, + "detached_verification_acceptance_gate_count": 10, + "operator_held_secret_boundary_count": 1, + "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, + "secret_reference_mode": "external_runtime_reference_only", + "requires_detached_signature_verification": True, + "detached_signature_verification_performed": False, + "verifier_receipt_persisted": False, + "external_signed_authorization_receipt_required_in_future": True, + "external_signed_authorization_receipt_included": False, + "signed_authorization_receipt_included": False, + "signature_material_included": 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, + "accepts_plaintext_secret": False, + "ready_for_future_verifier_receipt_closeout": True, + "can_enter_future_database_apply_authorization_verifier_handoff": True, + "verifier_receipt_evidence_handoff_ready": True, + "ready_for_future_verifier_receipt_evidence_handoff": True, + "permits_future_database_apply_authorization_verifier_handoff": True, + "persists_verifier_receipt": False, + "performs_detached_signature_verification": False, + "next_machine_action": ( + "continue_to_pchome_auto_policy_database_apply_authorization_verifier_handoff_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 @@ -745,6 +821,7 @@ def test_collect_ai_automation_smoke_uses_worst_status(monkeypatch): monkeypatch.setattr(smoke, "_pchome_auto_policy_signed_receipt_closeout_check", lambda: smoke._check("auto-policy signed receipt closeout", "ok", "ok")) monkeypatch.setattr(smoke, "_pchome_auto_policy_signed_receipt_evidence_intake_check", lambda: smoke._check("auto-policy signed receipt evidence intake", "ok", "ok")) monkeypatch.setattr(smoke, "_pchome_auto_policy_detached_verification_evidence_validation_check", lambda: smoke._check("auto-policy detached verification evidence validation", "ok", "ok")) + monkeypatch.setattr(smoke, "_pchome_auto_policy_verifier_receipt_closeout_check", lambda: smoke._check("auto-policy verifier receipt 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")) @@ -752,7 +829,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": 22, "warning": 1, "critical": 1, "total": 24} + assert result["summary"] == {"ok": 23, "warning": 1, "critical": 1, "total": 25} def test_pchome_controlled_apply_drift_monitor_reports_verified_zero_drift(monkeypatch): @@ -2596,6 +2673,87 @@ def test_pchome_auto_policy_detached_verification_evidence_validation_reports_bo ) +def test_pchome_auto_policy_verifier_receipt_closeout_reports_handoff_no_persist(): + from services import ai_automation_smoke_service as smoke + + result = smoke._pchome_auto_policy_verifier_receipt_closeout_check() + + assert result["status"] == "ok" + assert result["details"]["result"] == ( + "DB_APPLY_AUTHORIZATION_VERIFIER_RECEIPT_CLOSEOUT_READY" + ) + assert result["details"]["verifier_receipt_closeout_pass_count"] == 12 + assert result["details"]["verifier_receipt_closeout_check_count"] == 12 + assert ( + result["details"][ + "detached_verification_evidence_validation_check_count" + ] + == 12 + ) + assert result["details"]["signed_receipt_evidence_intake_check_count"] == 12 + assert result["details"]["verifier_receipt_closeout_boundary_count"] == 1 + assert result["details"]["verifier_receipt_evidence_handoff_count"] == 1 + assert result["details"]["verifier_receipt_field_count"] == 12 + assert result["details"]["verifier_receipt_acceptance_gate_count"] == 10 + assert result["details"]["verifier_receipt_evidence_handoff_field_count"] == 12 + assert result["details"]["verifier_receipt_handoff_acceptance_gate_count"] == 10 + assert result["details"]["required_external_receipt_evidence_count"] == 10 + assert result["details"]["external_receipt_acceptance_gate_count"] == 8 + assert result["details"]["detached_verification_evidence_field_count"] == 12 + assert result["details"]["detached_verification_acceptance_gate_count"] == 10 + 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_sql_count"] == 0 + assert result["details"]["writes_database_count"] == 0 + assert result["details"]["signs_database_apply_authorization_count"] == 0 + assert result["details"]["secret_reference_mode"] == "external_runtime_reference_only" + assert result["details"]["requires_detached_signature_verification"] is True + assert result["details"]["detached_signature_verification_performed"] is False + assert result["details"]["verifier_receipt_persisted"] is False + assert ( + result["details"]["external_signed_authorization_receipt_required_in_future"] + is True + ) + assert result["details"]["external_signed_authorization_receipt_included"] is False + assert result["details"]["signed_authorization_receipt_included"] is False + assert result["details"]["signature_material_included"] is False + assert result["details"]["ready_for_database_apply_now"] is False + 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"]["accepts_plaintext_secret"] is False + assert result["details"]["ready_for_future_verifier_receipt_closeout"] is True + assert ( + result["details"][ + "can_enter_future_database_apply_authorization_verifier_handoff" + ] + is True + ) + assert result["details"]["verifier_receipt_evidence_handoff_ready"] is True + assert ( + result["details"]["ready_for_future_verifier_receipt_evidence_handoff"] + is True + ) + assert ( + result["details"][ + "permits_future_database_apply_authorization_verifier_handoff" + ] + is True + ) + assert result["details"]["persists_verifier_receipt"] is False + assert result["details"]["performs_detached_signature_verification"] is False + assert result["details"]["next_machine_action"] == ( + "continue_to_pchome_auto_policy_database_apply_authorization_verifier_handoff_lane" + ) + + def test_collect_ai_automation_smoke_persists_recent_history(tmp_path, monkeypatch): from services import ai_automation_smoke_service as smoke @@ -2623,6 +2781,7 @@ def test_collect_ai_automation_smoke_persists_recent_history(tmp_path, monkeypat monkeypatch.setattr(smoke, "_pchome_auto_policy_signed_receipt_closeout_check", lambda: smoke._check("auto-policy signed receipt closeout", "ok", "ok")) monkeypatch.setattr(smoke, "_pchome_auto_policy_signed_receipt_evidence_intake_check", lambda: smoke._check("auto-policy signed receipt evidence intake", "ok", "ok")) monkeypatch.setattr(smoke, "_pchome_auto_policy_detached_verification_evidence_validation_check", lambda: smoke._check("auto-policy detached verification evidence validation", "ok", "ok")) + monkeypatch.setattr(smoke, "_pchome_auto_policy_verifier_receipt_closeout_check", lambda: smoke._check("auto-policy verifier receipt 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")) @@ -2681,7 +2840,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": 24, "warning": 0, "critical": 0, "total": 24}, + "summary": {"ok": 25, "warning": 0, "critical": 0, "total": 25}, "checks": [ { "name": "PChome 受控落地 drift monitor", @@ -2709,6 +2868,7 @@ def test_scheduled_automation_health_summary_reads_history_without_side_effects( _auto_policy_signed_receipt_closeout_history_check(), _auto_policy_signed_receipt_evidence_intake_history_check(), _auto_policy_detached_verification_evidence_validation_history_check(), + _auto_policy_verifier_receipt_closeout_history_check(), { "name": "AI surface HTML readback", "status": "ok", @@ -2770,7 +2930,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"] == 21 + assert summary["summary"]["total"] == 22 assert summary["summary"]["primary_human_gate_count"] == 0 assert summary["summary"]["writes_database_count"] == 0 assert pchome_family["status"] == "ok" @@ -3677,6 +3837,155 @@ def test_scheduled_automation_health_summary_reads_history_without_side_effects( ] is False ) + verifier_receipt_closeout_family = next( + item for item in summary["families"] + if item["key"] == "pchome_auto_policy_verifier_receipt_closeout" + ) + assert verifier_receipt_closeout_family["status"] == "ok" + assert ( + verifier_receipt_closeout_family["details"][ + "verifier_receipt_closeout_pass_count" + ] + == 12 + ) + assert ( + verifier_receipt_closeout_family["details"][ + "detached_verification_evidence_validation_check_count" + ] + == 12 + ) + assert ( + verifier_receipt_closeout_family["details"][ + "verifier_receipt_evidence_handoff_count" + ] + == 1 + ) + assert ( + verifier_receipt_closeout_family["details"][ + "verifier_receipt_evidence_handoff_field_count" + ] + == 12 + ) + assert ( + verifier_receipt_closeout_family["details"][ + "verifier_receipt_handoff_acceptance_gate_count" + ] + == 10 + ) + assert ( + verifier_receipt_closeout_family["details"][ + "required_external_receipt_evidence_count" + ] + == 10 + ) + assert ( + verifier_receipt_closeout_family["details"][ + "external_receipt_acceptance_gate_count" + ] + == 8 + ) + assert ( + verifier_receipt_closeout_family["details"]["payload_source"] + == "local_contract_fixture" + ) + assert verifier_receipt_closeout_family["details"]["outbound_network"] is False + assert verifier_receipt_closeout_family["details"]["business_data_source"] is False + assert ( + verifier_receipt_closeout_family["details"]["primary_human_gate_count"] + == 0 + ) + assert verifier_receipt_closeout_family["details"]["reads_secret_count"] == 0 + assert verifier_receipt_closeout_family["details"]["writes_database_count"] == 0 + assert ( + verifier_receipt_closeout_family["details"][ + "signs_database_apply_authorization_count" + ] + == 0 + ) + assert ( + verifier_receipt_closeout_family["details"][ + "requires_detached_signature_verification" + ] + is True + ) + assert ( + verifier_receipt_closeout_family["details"][ + "detached_signature_verification_performed" + ] + is False + ) + assert ( + verifier_receipt_closeout_family["details"]["verifier_receipt_persisted"] + is False + ) + assert ( + verifier_receipt_closeout_family["details"][ + "external_signed_authorization_receipt_included" + ] + is False + ) + assert ( + verifier_receipt_closeout_family["details"][ + "signed_authorization_receipt_included" + ] + is False + ) + assert ( + verifier_receipt_closeout_family["details"]["signature_material_included"] + is False + ) + assert ( + verifier_receipt_closeout_family["details"]["signs_database_apply_authorization"] + is False + ) + assert ( + verifier_receipt_closeout_family["details"]["secret_material_included"] + is False + ) + assert ( + verifier_receipt_closeout_family["details"]["accepts_plaintext_secret"] + is False + ) + assert ( + verifier_receipt_closeout_family["details"][ + "ready_for_future_verifier_receipt_closeout" + ] + is True + ) + assert ( + verifier_receipt_closeout_family["details"][ + "can_enter_future_database_apply_authorization_verifier_handoff" + ] + is True + ) + assert ( + verifier_receipt_closeout_family["details"][ + "verifier_receipt_evidence_handoff_ready" + ] + is True + ) + assert ( + verifier_receipt_closeout_family["details"][ + "ready_for_future_verifier_receipt_evidence_handoff" + ] + is True + ) + assert ( + verifier_receipt_closeout_family["details"][ + "permits_future_database_apply_authorization_verifier_handoff" + ] + is True + ) + assert ( + verifier_receipt_closeout_family["details"]["persists_verifier_receipt"] + is False + ) + assert ( + verifier_receipt_closeout_family["details"][ + "performs_detached_signature_verification" + ] + is False + ) surface_family = next( item for item in summary["families"] if item["key"] == "ai_surface_html_readback" @@ -3711,7 +4020,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": 23, "warning": 0, "critical": 1, "total": 24}, + "summary": {"ok": 24, "warning": 0, "critical": 1, "total": 25}, "checks": [ { "name": "PChome 受控落地 drift monitor", @@ -3733,6 +4042,7 @@ def test_scheduled_automation_health_summary_can_use_current_smoke_without_recor _auto_policy_signed_receipt_closeout_history_check(), _auto_policy_signed_receipt_evidence_intake_history_check(), _auto_policy_detached_verification_evidence_validation_history_check(), + _auto_policy_verifier_receipt_closeout_history_check(), { "name": "AI surface HTML readback", "status": "ok", @@ -3788,7 +4098,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": 22, "warning": 0, "critical": 0, "total": 22}, + "summary": {"ok": 23, "warning": 0, "critical": 0, "total": 23}, "checks": [], }, ensure_ascii=False) + "\n", encoding="utf-8", @@ -3864,6 +4174,11 @@ def test_scheduled_automation_health_summary_falls_back_to_visual_qa_artifact(tm "_pchome_auto_policy_detached_verification_evidence_validation_check", lambda: _auto_policy_detached_verification_evidence_validation_history_check(), ) + monkeypatch.setattr( + smoke, + "_pchome_auto_policy_verifier_receipt_closeout_check", + lambda: _auto_policy_verifier_receipt_closeout_history_check(), + ) monkeypatch.setattr( surface_service, "build_sitewide_visual_qa_readback", @@ -3912,7 +4227,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": 24, "warning": 0, "critical": 0, "total": 24}, + "summary": {"ok": 25, "warning": 0, "critical": 0, "total": 25}, "checks": [], }, ensure_ascii=False) + "\n", encoding="utf-8", @@ -3988,6 +4303,11 @@ def test_scheduled_automation_health_summary_route_returns_compact_payload(tmp_p "_pchome_auto_policy_detached_verification_evidence_validation_check", lambda: _auto_policy_detached_verification_evidence_validation_history_check(), ) + monkeypatch.setattr( + smoke, + "_pchome_auto_policy_verifier_receipt_closeout_check", + lambda: _auto_policy_verifier_receipt_closeout_history_check(), + ) app = Flask(__name__) with app.test_request_context( @@ -4224,6 +4544,8 @@ def test_surface_html_readback_check_is_part_of_ai_smoke(monkeypatch): monkeypatch.setattr(smoke, "_pchome_auto_policy_signed_receipt_preflight_check", lambda: smoke._check("auto-policy signed receipt preflight", "ok", "ok")) monkeypatch.setattr(smoke, "_pchome_auto_policy_signed_receipt_closeout_check", lambda: smoke._check("auto-policy signed receipt closeout", "ok", "ok")) monkeypatch.setattr(smoke, "_pchome_auto_policy_signed_receipt_evidence_intake_check", lambda: smoke._check("auto-policy signed receipt evidence intake", "ok", "ok")) + monkeypatch.setattr(smoke, "_pchome_auto_policy_detached_verification_evidence_validation_check", lambda: smoke._check("auto-policy detached verification evidence validation", "ok", "ok")) + monkeypatch.setattr(smoke, "_pchome_auto_policy_verifier_receipt_closeout_check", lambda: smoke._check("auto-policy verifier receipt closeout", "ok", "ok")) monkeypatch.setattr(smoke, "_sitewide_visual_qa_check", lambda: smoke._check( "Sitewide visual QA readback", "ok", @@ -4245,7 +4567,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"] == 24 + assert result["summary"]["total"] == 25 assert surface_check["status"] == "ok" assert surface_check["details"]["checked_surface_count"] == 10 assert sitewide_check["status"] == "ok"