From 4c0f0e0d286647174993033a12f42503d8d63aae Mon Sep 17 00:00:00 2001 From: ogt Date: Fri, 3 Jul 2026 11:21:44 +0800 Subject: [PATCH] feat(smoke): monitor pchome receipt evidence intake --- config.py | 2 +- docs/AI_INTELLIGENCE_MODULE_SOT.md | 5 +- .../pchome_ai_automation_priority_backlog.md | 18 +- services/ai_automation_smoke_service.py | 752 ++++++++++++++++++ tests/test_ai_automation_metrics.py | 11 +- tests/test_ai_automation_smoke_service.py | 703 +++++++++++++--- 6 files changed, 1361 insertions(+), 130 deletions(-) diff --git a/config.py b/config.py index 3259293..b8d2dc2 100644 --- a/config.py +++ b/config.py @@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.737" +SYSTEM_VERSION = "V10.738" 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 4a94064..1bddf9f 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 已建立,GCP embedding 熔斷延後處理、110 proxy rescue 與 direct host health skip 已建立 -> **適用版本**: V10.737 +> **狀態**: 🟢 四 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 已建立,GCP embedding 熔斷延後處理、110 proxy rescue 與 direct host health skip 已建立 +> **適用版本**: V10.738 --- @@ -863,6 +863,7 @@ POSTGRES_HOST=momo-db | 2026-07-03 | PChome auto-policy signing execution closeout 必須有 runtime monitoring | V10.735 起 `/api/ai-automation/smoke`、`/api/ai-automation/scheduled-health-summary` 與 `/metrics` 必須輸出 `PChome auto-policy signing execution closeout` / `pchome_auto_policy_signing_execution_closeout`;此 runtime check 自動驗證 unsigned signed-authorization receipt boundary、operator-held secret boundary、nonsecret signing inputs、rollback boundary、abort conditions、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`、`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、不執行 SQL、不寫 DB、不產生 signed receipt、不含 signature material、不簽發 apply authorization。 | | 2026-07-03 | PChome auto-policy signed receipt preflight 必須有 runtime monitoring | V10.736 起 `/api/ai-automation/smoke`、`/api/ai-automation/scheduled-health-summary` 與 `/metrics` 必須輸出 `PChome auto-policy signed receipt preflight` / `pchome_auto_policy_signed_receipt_preflight`;此 runtime check 自動驗證 external signing receipt evidence boundary、required external receipt evidence、acceptance gates、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`、`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、不執行 SQL、不寫 DB、不產生 signed receipt、不含 signature material、不簽發 apply authorization。 | | 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-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 fd10765..ae88d33 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.737`,除非有明確版本 bump、部署與 readback。 +2. 版本不得搞錯;目前正式環境 `/health` 版本維持 `V10.738`,除非有明確版本 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.737`。 +- 正式環境版本真相 guard:`/health` 是最高真相,目前維持 `V10.738`。 - Gitea `dev` / `main` source truth 對齊。 - Retry exception controlled apply executor 已落地,目標表為 `pchome_product_matches`。 - 正式 DB 已 controlled apply 4 個 selectors: @@ -169,10 +169,17 @@ - `/api/ai-automation/scheduled-health-summary` 已輸出 `pchome_auto_policy_signed_receipt_closeout` family - `/metrics` 已輸出 `momo_ai_automation_scheduled_health_family_status{family="pchome_auto_policy_signed_receipt_closeout",...}` - 不執行 detached signature verification、不包含 external signed receipt、不包含 signed receipt、不包含 signature material、不讀 secret、不執行 SQL、不寫 DB、不簽發 apply authorization,下一個機器動作是 signed receipt evidence intake lane +- PChome auto-policy signed receipt evidence intake runtime monitoring 已完成: + - `/api/ai-automation/smoke` 已加入 `PChome auto-policy signed receipt evidence intake` + - 以本機 contract fixture + machine evidence adapter 自動推進 detached verification evidence schema intake path + - 明確標記 `signed_receipt_evidence_intake_check_count=12`、`signed_receipt_closeout_check_count=12`、`detached_verification_evidence_schema_count=1`、`required_external_receipt_evidence_count=10`、`external_receipt_acceptance_gate_count=8`、`detached_receipt_verification_check_count=10`、`detached_verification_evidence_field_count=12`、`detached_verification_acceptance_gate_count=10`、`operator_held_secret_boundary_count=1`、`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_signed_receipt_evidence_intake` family + - `/metrics` 已輸出 `momo_ai_automation_scheduled_health_family_status{family="pchome_auto_policy_signed_receipt_evidence_intake",...}` + - 不執行 detached signature verification、不包含 external signed receipt、不包含 signed receipt、不包含 signature material、不接受 plaintext secret、不讀 secret、不執行 SQL、不寫 DB、不簽發 apply authorization,下一個機器動作是 detached verification evidence validation lane 進行中 / 下一步,必須照順序: -1. 將 auto-policy signed receipt closeout 往 signed receipt evidence intake lane 推進。 +1. 將 auto-policy signed receipt evidence intake 往 detached verification evidence validation lane 推進。 完成標準: @@ -339,7 +346,7 @@ 進行中 / 下一步,必須照順序: -1. 將 auto-policy signed receipt closeout 推進到 signed receipt evidence intake lane。 +1. 將 auto-policy signed receipt evidence intake 推進到 detached verification evidence validation lane。 2. 對其他 safe mapping backlog lanes 套用 receipt / replay / drift verifier 模式。 3. 為每一類 controlled apply 自動產生 rollback evidence package。 4. 把 AI automation metrics 持續整合進既有 observability surfaces。 @@ -422,7 +429,8 @@ | P3.13 | PChome auto-policy signing execution preflight monitoring | 已完成 | AI smoke + scheduled family + metrics family prove operator-held secret boundary, 10 nonsecret inputs, 8 abort conditions, 4 rollback boundaries, no secret read, no DB write, no signing | P3.14 signing execution closeout monitoring | | P3.14 | PChome auto-policy signing execution closeout monitoring | 已完成 | AI smoke + scheduled family + metrics family prove unsigned signed-authorization receipt boundary, no signed receipt, no signature material, no secret read, no DB write, no signing | P3.15 signed receipt preflight monitoring | | 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 lane | +| 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 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 5ad77ab..169c8ca 100644 --- a/services/ai_automation_smoke_service.py +++ b/services/ai_automation_smoke_service.py @@ -365,6 +365,23 @@ def build_scheduled_automation_health_summary( auto_policy_signed_receipt_closeout_details = ( auto_policy_signed_receipt_closeout.get("details") or {} ) + auto_policy_signed_receipt_evidence_intake = _find_check( + source_result, + "PChome auto-policy signed receipt evidence intake", + ) + auto_policy_signed_receipt_evidence_intake_details = ( + auto_policy_signed_receipt_evidence_intake.get("details") or {} + ) + if ( + not auto_policy_signed_receipt_evidence_intake + or not auto_policy_signed_receipt_evidence_intake_details + ): + auto_policy_signed_receipt_evidence_intake = ( + _pchome_auto_policy_signed_receipt_evidence_intake_check() + ) + auto_policy_signed_receipt_evidence_intake_details = ( + auto_policy_signed_receipt_evidence_intake.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") @@ -2260,6 +2277,308 @@ def build_scheduled_automation_health_summary( "writes_database": False, }, }, + { + "key": "pchome_auto_policy_signed_receipt_evidence_intake", + "label": "PChome auto-policy signed receipt evidence intake", + "status": auto_policy_signed_receipt_evidence_intake.get("status") or "warning", + "summary": ( + auto_policy_signed_receipt_evidence_intake.get("summary") + or "PChome auto-policy signed receipt evidence intake has no latest check." + ), + "next_machine_action": auto_policy_signed_receipt_evidence_intake_details.get( + "next_machine_action" + ) + or ( + "continue_pchome_auto_policy_signed_receipt_evidence_intake_monitoring" + if auto_policy_signed_receipt_evidence_intake.get("status") == "ok" + else "refresh_pchome_auto_policy_signed_receipt_evidence_intake" + ), + "details": { + "policy": auto_policy_signed_receipt_evidence_intake_details.get("policy"), + "result": auto_policy_signed_receipt_evidence_intake_details.get("result"), + "signed_receipt_evidence_intake_ready_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "signed_receipt_evidence_intake_ready_count" + ) + or 0 + ), + "signed_receipt_evidence_intake_check_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "signed_receipt_evidence_intake_check_count" + ) + or 0 + ), + "signed_receipt_evidence_intake_pass_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "signed_receipt_evidence_intake_pass_count" + ) + or 0 + ), + "signed_receipt_evidence_intake_waiting_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "signed_receipt_evidence_intake_waiting_count" + ) + or 0 + ), + "signed_receipt_closeout_ready_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "signed_receipt_closeout_ready_count" + ) + or 0 + ), + "signed_receipt_closeout_check_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "signed_receipt_closeout_check_count" + ) + or 0 + ), + "signed_receipt_preflight_ready_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "signed_receipt_preflight_ready_count" + ) + or 0 + ), + "signed_receipt_preflight_check_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "signed_receipt_preflight_check_count" + ) + or 0 + ), + "external_signing_receipt_evidence_boundary_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "external_signing_receipt_evidence_boundary_count" + ) + or 0 + ), + "detached_receipt_verification_boundary_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "detached_receipt_verification_boundary_count" + ) + or 0 + ), + "detached_verification_evidence_schema_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "detached_verification_evidence_schema_count" + ) + or 0 + ), + "required_external_receipt_evidence_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "required_external_receipt_evidence_count" + ) + or 0 + ), + "external_receipt_acceptance_gate_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "external_receipt_acceptance_gate_count" + ) + or 0 + ), + "detached_receipt_verification_check_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "detached_receipt_verification_check_count" + ) + or 0 + ), + "detached_verification_evidence_field_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "detached_verification_evidence_field_count" + ) + or 0 + ), + "detached_verification_acceptance_gate_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "detached_verification_acceptance_gate_count" + ) + or 0 + ), + "operator_held_secret_boundary_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "operator_held_secret_boundary_count" + ) + or 0 + ), + "rollback_boundary_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "rollback_boundary_count" + ) + or 0 + ), + "post_apply_verifier_required_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "post_apply_verifier_required_count" + ) + or 0 + ), + "same_run_truth_required_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "same_run_truth_required_count" + ) + or 0 + ), + "reads_secret_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "reads_secret_count" + ) + or 0 + ), + "executes_script_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "executes_script_count" + ) + or 0 + ), + "executes_sql_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "executes_sql_count" + ) + or 0 + ), + "writes_database_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "writes_database_count" + ) + or 0 + ), + "signs_database_apply_authorization_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "signs_database_apply_authorization_count" + ) + or 0 + ), + "primary_human_gate_count": int( + auto_policy_signed_receipt_evidence_intake_details.get( + "primary_human_gate_count" + ) + or 0 + ), + "manual_review_mode": auto_policy_signed_receipt_evidence_intake_details.get( + "manual_review_mode" + ), + "payload_source": auto_policy_signed_receipt_evidence_intake_details.get( + "payload_source" + ), + "execute_fetch": bool( + auto_policy_signed_receipt_evidence_intake_details.get( + "execute_fetch" + ) + ), + "outbound_network": bool( + auto_policy_signed_receipt_evidence_intake_details.get( + "outbound_network" + ) + ), + "business_data_source": bool( + auto_policy_signed_receipt_evidence_intake_details.get( + "business_data_source" + ) + ), + "secret_reference_mode": auto_policy_signed_receipt_evidence_intake_details.get( + "secret_reference_mode" + ), + "requires_detached_signature_verification": bool( + auto_policy_signed_receipt_evidence_intake_details.get( + "requires_detached_signature_verification" + ) + ), + "detached_signature_verification_performed": bool( + auto_policy_signed_receipt_evidence_intake_details.get( + "detached_signature_verification_performed" + ) + ), + "external_signed_authorization_receipt_required_in_future": bool( + auto_policy_signed_receipt_evidence_intake_details.get( + "external_signed_authorization_receipt_required_in_future" + ) + ), + "external_signed_authorization_receipt_included": bool( + auto_policy_signed_receipt_evidence_intake_details.get( + "external_signed_authorization_receipt_included" + ) + ), + "signed_authorization_receipt_included": bool( + auto_policy_signed_receipt_evidence_intake_details.get( + "signed_authorization_receipt_included" + ) + ), + "signature_material_included": bool( + auto_policy_signed_receipt_evidence_intake_details.get( + "signature_material_included" + ) + ), + "signer_key_id_reference_only": bool( + auto_policy_signed_receipt_evidence_intake_details.get( + "signer_key_id_reference_only" + ) + ), + "signature_algorithm_reference_only": bool( + auto_policy_signed_receipt_evidence_intake_details.get( + "signature_algorithm_reference_only" + ) + ), + "ready_for_database_apply_now": bool( + auto_policy_signed_receipt_evidence_intake_details.get( + "ready_for_database_apply_now" + ) + ), + "issues_database_apply_authorization": bool( + auto_policy_signed_receipt_evidence_intake_details.get( + "issues_database_apply_authorization" + ) + ), + "signs_database_apply_authorization": bool( + auto_policy_signed_receipt_evidence_intake_details.get( + "signs_database_apply_authorization" + ) + ), + "secret_material_included": bool( + auto_policy_signed_receipt_evidence_intake_details.get( + "secret_material_included" + ) + ), + "secret_material_required_in_preview": bool( + auto_policy_signed_receipt_evidence_intake_details.get( + "secret_material_required_in_preview" + ) + ), + "accepts_plaintext_secret": bool( + auto_policy_signed_receipt_evidence_intake_details.get( + "accepts_plaintext_secret" + ) + ), + "ready_for_future_signed_authorization_receipt_evidence_intake": bool( + auto_policy_signed_receipt_evidence_intake_details.get( + "ready_for_future_signed_authorization_receipt_evidence_intake" + ) + ), + "can_enter_future_detached_verification_evidence_validation": bool( + auto_policy_signed_receipt_evidence_intake_details.get( + "can_enter_future_detached_verification_evidence_validation" + ) + ), + "external_signed_authorization_receipt_evidence_schema_ready": bool( + auto_policy_signed_receipt_evidence_intake_details.get( + "external_signed_authorization_receipt_evidence_schema_ready" + ) + ), + "ready_for_future_detached_verification_evidence_schema": bool( + auto_policy_signed_receipt_evidence_intake_details.get( + "ready_for_future_detached_verification_evidence_schema" + ) + ), + "permits_future_detached_verification_evidence_validation": bool( + auto_policy_signed_receipt_evidence_intake_details.get( + "permits_future_detached_verification_evidence_validation" + ) + ), + "performs_detached_signature_verification": bool( + auto_policy_signed_receipt_evidence_intake_details.get( + "performs_detached_signature_verification" + ) + ), + "writes_database": False, + }, + }, { "key": "ai_surface_html_readback", "label": "AI surface HTML readback", @@ -5914,6 +6233,438 @@ def _pchome_auto_policy_signed_receipt_closeout_check() -> Dict[str, Any]: ) +def _pchome_auto_policy_signed_receipt_evidence_intake_check() -> Dict[str, Any]: + """Read-only monitor for the future detached verification evidence intake schema.""" + 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() + intake = ( + backlog.build_pchome_auto_policy_db_apply_authorization_signed_receipt_evidence_intake( + 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 = intake.get("summary") or {} + future_intake = ( + intake.get("future_signed_authorization_receipt_evidence_intake") or {} + ) + schema = intake.get("detached_verification_evidence_schema") or {} + operator_boundary = ( + schema.get("operator_held_secret_boundary_contract") or {} + ) + contract = intake.get("signed_receipt_evidence_intake_contract") or {} + safety = intake.get("safety") or {} + evidence_fields = list(schema.get("detached_verification_evidence_fields") or []) + evidence_gates = list( + schema.get("detached_verification_acceptance_gates") or [] + ) + result = str(intake.get("result") or "UNKNOWN") + + ready_count = int( + summary.get("authorization_signed_receipt_evidence_intake_ready_count") + or 0 + ) + check_count = int(summary.get("signed_receipt_evidence_intake_check_count") or 0) + pass_count = int(summary.get("signed_receipt_evidence_intake_pass_count") or 0) + waiting_count = int( + summary.get("signed_receipt_evidence_intake_waiting_count") or 0 + ) + closeout_ready_count = int( + summary.get("authorization_signed_receipt_closeout_ready_count") or 0 + ) + closeout_check_count = int(summary.get("signed_receipt_closeout_check_count") or 0) + preflight_ready_count = int( + summary.get("authorization_signed_receipt_preflight_ready_count") or 0 + ) + preflight_check_count = int(summary.get("signed_receipt_preflight_check_count") or 0) + evidence_boundary_count = int( + summary.get("external_signing_receipt_evidence_boundary_count") or 0 + ) + detached_boundary_count = int( + summary.get("detached_receipt_verification_boundary_count") or 0 + ) + schema_count = int(summary.get("detached_verification_evidence_schema_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 + ) + detached_check_count = int( + summary.get("detached_receipt_verification_check_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) + rollback_boundary_count = int(summary.get("rollback_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_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_intake.get("ready_for_database_apply_now") is not False, + future_intake.get("issues_database_apply_authorization") is not False, + future_intake.get("signs_database_apply_authorization") is not False, + future_intake.get("detached_signature_verification_performed") + is not False, + future_intake.get("external_signed_authorization_receipt_included") + is not False, + future_intake.get("signed_authorization_receipt_included") is not False, + future_intake.get("signature_material_included") is not False, + future_intake.get("secret_material_included") is not False, + future_intake.get("secret_material_required_in_preview") is not False, + future_intake.get("reads_secret_in_preview") is not False, + schema.get("detached_signature_verification_performed") is not False, + schema.get("external_signed_authorization_receipt_included") is not False, + schema.get("signed_authorization_receipt_included") is not False, + schema.get("signature_material_included") is not False, + schema.get("secret_material_included") is not False, + schema.get("secret_material_required_in_preview") is not False, + schema.get("accepts_plaintext_secret") is not False, + schema.get("reads_secret_in_preview") is not False, + schema.get("executes_shell_in_preview") is not False, + schema.get("executes_sql_in_preview") is not False, + schema.get("writes_database_in_preview") is not False, + schema.get("ready_for_database_apply_now") is not False, + schema.get("issues_database_apply_authorization") is not False, + schema.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("detached_signature_verification_performed") 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, + ] + ) + intake_contract_present = ( + ready_count == 1 + and closeout_ready_count == 1 + and preflight_ready_count == 1 + and check_count >= 12 + and pass_count == check_count + and waiting_count == 0 + and closeout_check_count == 12 + and preflight_check_count == 12 + and evidence_boundary_count == 1 + and detached_boundary_count == 1 + and schema_count == 1 + and required_evidence_count == 10 + and acceptance_gate_count == 8 + and detached_check_count == 10 + and evidence_field_count == 12 + and evidence_acceptance_gate_count == 10 + and secret_boundary_count == 1 + and rollback_boundary_count == 4 + and verifier_required_count == 1 + and same_run_truth_count == 1 + and future_intake.get( + "ready_for_future_signed_authorization_receipt_evidence_intake" + ) + is True + and future_intake.get( + "can_enter_future_detached_verification_evidence_validation" + ) + is True + and future_intake.get( + "external_signed_authorization_receipt_evidence_schema_ready" + ) + is True + and schema.get("authorization_material_type") + == "detached_verification_evidence_schema" + and schema.get("ready_for_future_detached_verification_evidence_schema") + is True + and "verifier_receipt_sha256" in evidence_fields + and "detached_signature_verification_status_is_passed" in evidence_gates + and schema.get("requires_detached_signature_verification") is True + and schema.get("detached_signature_verification_performed") is False + and schema.get("external_signed_authorization_receipt_required_in_future") + is True + and schema.get("external_signed_authorization_receipt_included") is False + and schema.get("signed_authorization_receipt_included") is False + and schema.get("signature_material_included") is False + and schema.get("signer_key_id_reference_only") is True + and schema.get("signature_algorithm_reference_only") is True + and schema.get("accepts_plaintext_secret") is False + and schema.get("hash_matches") is True + and schema.get("requires_post_apply_verifier") is True + and schema.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_detached_verification_evidence_validation") + is True + and contract.get("accepts_plaintext_secret") is False + and contract.get("detached_signature_verification_performed") is False + and safety.get("manual_review_mode") == "exception_only" + ) + + if risk_detected: + status = "critical" + summary_text = ( + "PChome auto-policy signed receipt evidence intake 偵測到 secret、" + "signed material、verification execution、SQL、DB write、簽發授權或人工主 gate 風險" + ) + next_machine_action = ( + "halt_pchome_auto_policy_signed_receipt_evidence_intake_and_inspect_risk" + ) + elif intake_contract_present: + status = "ok" + summary_text = ( + "PChome auto-policy signed receipt evidence intake 已自動完成 " + f"{pass_count}/{check_count} checks,detached evidence schema 已綁定,signed receipt payload=0" + ) + next_machine_action = ( + "continue_to_pchome_auto_policy_detached_verification_evidence_validation_lane" + ) + else: + status = "warning" + summary_text = ( + "PChome auto-policy signed receipt evidence intake 尚未達自動監控契約" + ) + next_machine_action = ( + "refresh_pchome_auto_policy_signed_receipt_evidence_intake" + ) + + return _check( + "PChome auto-policy signed receipt evidence intake", + status, + summary_text, + { + "policy": intake.get("policy"), + "result": result, + "source_policy": intake.get("source_policy"), + "signed_receipt_evidence_intake_ready_count": ready_count, + "signed_receipt_evidence_intake_check_count": check_count, + "signed_receipt_evidence_intake_pass_count": pass_count, + "signed_receipt_evidence_intake_waiting_count": waiting_count, + "signed_receipt_closeout_ready_count": closeout_ready_count, + "signed_receipt_closeout_check_count": closeout_check_count, + "signed_receipt_preflight_ready_count": preflight_ready_count, + "signed_receipt_preflight_check_count": preflight_check_count, + "external_signing_receipt_evidence_boundary_count": ( + evidence_boundary_count + ), + "detached_receipt_verification_boundary_count": detached_boundary_count, + "detached_verification_evidence_schema_count": schema_count, + "required_external_receipt_evidence_count": required_evidence_count, + "external_receipt_acceptance_gate_count": acceptance_gate_count, + "detached_receipt_verification_check_count": detached_check_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, + "rollback_boundary_count": rollback_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( + schema.get("requires_detached_signature_verification") + ), + "detached_signature_verification_performed": bool( + schema.get("detached_signature_verification_performed") + or future_intake.get("detached_signature_verification_performed") + or safety.get("performs_detached_signature_verification") + ), + "external_signed_authorization_receipt_required_in_future": bool( + schema.get( + "external_signed_authorization_receipt_required_in_future" + ) + ), + "external_signed_authorization_receipt_included": bool( + future_intake.get( + "external_signed_authorization_receipt_included" + ) + or schema.get("external_signed_authorization_receipt_included") + ), + "signed_authorization_receipt_included": bool( + future_intake.get("signed_authorization_receipt_included") + or schema.get("signed_authorization_receipt_included") + ), + "signature_material_included": bool( + future_intake.get("signature_material_included") + or schema.get("signature_material_included") + ), + "signer_key_id_reference_only": bool( + schema.get("signer_key_id_reference_only") + ), + "signature_algorithm_reference_only": bool( + schema.get("signature_algorithm_reference_only") + ), + "ready_for_database_apply_now": bool( + future_intake.get("ready_for_database_apply_now") + or schema.get("ready_for_database_apply_now") + or contract.get("ready_for_database_apply_now") + ), + "issues_database_apply_authorization": bool( + future_intake.get("issues_database_apply_authorization") + or schema.get("issues_database_apply_authorization") + or contract.get("issues_database_apply_authorization") + ), + "signs_database_apply_authorization": bool( + future_intake.get("signs_database_apply_authorization") + or schema.get("signs_database_apply_authorization") + or contract.get("signs_database_apply_authorization") + ), + "secret_material_included": bool( + future_intake.get("secret_material_included") + or schema.get("secret_material_included") + or operator_boundary.get("secret_material_included") + ), + "secret_material_required_in_preview": bool( + future_intake.get("secret_material_required_in_preview") + or schema.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( + schema.get("accepts_plaintext_secret") + or contract.get("accepts_plaintext_secret") + or operator_boundary.get("accepts_plaintext_secret") + ), + "ready_for_future_signed_authorization_receipt_evidence_intake": bool( + future_intake.get( + "ready_for_future_signed_authorization_receipt_evidence_intake" + ) + ), + "can_enter_future_detached_verification_evidence_validation": bool( + future_intake.get( + "can_enter_future_detached_verification_evidence_validation" + ) + ), + "external_signed_authorization_receipt_evidence_schema_ready": bool( + future_intake.get( + "external_signed_authorization_receipt_evidence_schema_ready" + ) + ), + "ready_for_future_detached_verification_evidence_schema": bool( + schema.get("ready_for_future_detached_verification_evidence_schema") + ), + "permits_future_detached_verification_evidence_validation": bool( + contract.get( + "permits_future_detached_verification_evidence_validation" + ) + ), + "performs_detached_signature_verification": bool( + safety.get("performs_detached_signature_verification") + ), + "requires_post_apply_verifier": bool( + schema.get("requires_post_apply_verifier") + ), + "requires_fresh_production_truth": bool( + schema.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 signed receipt evidence intake", + "warning", + ( + "PChome auto-policy signed receipt evidence intake " + f"例行監控暫時無法讀取:{exc}" + ), + { + "writes_database": False, + "writes_database_count": 0, + "signs_database_apply_authorization_count": 0, + "primary_human_gate_count": 0, + "execute_fetch": True, + "outbound_network": False, + "business_data_source": False, + "payload_source": "local_contract_fixture", + "next_machine_action": ( + "refresh_pchome_auto_policy_signed_receipt_evidence_intake" + ), + }, + ) + + def _ai_surface_html_readback_check() -> Dict[str, Any]: """Read-only AI product surface guardrail readback.""" try: @@ -6153,6 +6904,7 @@ def collect_ai_automation_smoke(*, record_history: bool = True, history_limit: i _pchome_auto_policy_signing_execution_closeout_check(), _pchome_auto_policy_signed_receipt_preflight_check(), _pchome_auto_policy_signed_receipt_closeout_check(), + _pchome_auto_policy_signed_receipt_evidence_intake_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 52b3a5e..37bdea0 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": 16, + "ok": 17, "warning": 1, "critical": 0, - "total": 17, + "total": 18, "primary_human_gate_count": 0, "writes_database_count": 0, }, @@ -129,6 +129,7 @@ def test_system_metrics_exports_scheduled_health_summary(): {"key": "pchome_auto_policy_signing_execution_closeout", "status": "ok"}, {"key": "pchome_auto_policy_signed_receipt_preflight", "status": "ok"}, {"key": "pchome_auto_policy_signed_receipt_closeout", "status": "ok"}, + {"key": "pchome_auto_policy_signed_receipt_evidence_intake", "status": "ok"}, {"key": "sitewide_ui_ux_agent", "status": "ok"}, {"key": "sitewide_visual_qa", "status": "ok"}, ], @@ -136,7 +137,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"} 16.0' in output + assert 'momo_ai_automation_scheduled_health_summary_total{status="ok"} 17.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' @@ -194,6 +195,10 @@ def test_system_metrics_exports_scheduled_health_summary(): 'momo_ai_automation_scheduled_health_family_status{family="pchome_auto_policy_signed_receipt_closeout",status="ok"} 1.0' in output ) + assert ( + 'momo_ai_automation_scheduled_health_family_status{family="pchome_auto_policy_signed_receipt_evidence_intake",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 4a3045c..56a39dd 100644 --- a/tests/test_ai_automation_smoke_service.py +++ b/tests/test_ai_automation_smoke_service.py @@ -553,6 +553,76 @@ def _auto_policy_signed_receipt_closeout_history_check(status="ok"): } +def _auto_policy_signed_receipt_evidence_intake_history_check(status="ok"): + return { + "name": "PChome auto-policy signed receipt evidence intake", + "status": status, + "summary": "PChome auto-policy signed receipt evidence intake 已自動完成", + "details": { + "policy": ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_" + "signed_receipt_evidence_intake" + ), + "result": "DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_EVIDENCE_INTAKE_READY", + "signed_receipt_evidence_intake_ready_count": 1, + "signed_receipt_evidence_intake_check_count": 12, + "signed_receipt_evidence_intake_pass_count": 12, + "signed_receipt_evidence_intake_waiting_count": 0, + "signed_receipt_closeout_ready_count": 1, + "signed_receipt_closeout_check_count": 12, + "signed_receipt_preflight_ready_count": 1, + "signed_receipt_preflight_check_count": 12, + "external_signing_receipt_evidence_boundary_count": 1, + "detached_receipt_verification_boundary_count": 1, + "detached_verification_evidence_schema_count": 1, + "required_external_receipt_evidence_count": 10, + "external_receipt_acceptance_gate_count": 8, + "detached_receipt_verification_check_count": 10, + "detached_verification_evidence_field_count": 12, + "detached_verification_acceptance_gate_count": 10, + "operator_held_secret_boundary_count": 1, + "rollback_boundary_count": 4, + "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_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, + "external_signed_authorization_receipt_required_in_future": True, + "external_signed_authorization_receipt_included": False, + "signed_authorization_receipt_included": False, + "signature_material_included": False, + "signer_key_id_reference_only": True, + "signature_algorithm_reference_only": True, + "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_signed_authorization_receipt_evidence_intake": True, + "can_enter_future_detached_verification_evidence_validation": True, + "external_signed_authorization_receipt_evidence_schema_ready": True, + "ready_for_future_detached_verification_evidence_schema": True, + "permits_future_detached_verification_evidence_validation": True, + "performs_detached_signature_verification": False, + "next_machine_action": ( + "continue_to_pchome_auto_policy_detached_verification_evidence_validation_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 @@ -600,6 +670,7 @@ def test_collect_ai_automation_smoke_uses_worst_status(monkeypatch): monkeypatch.setattr(smoke, "_pchome_auto_policy_signing_execution_closeout_check", lambda: smoke._check("auto-policy signing execution closeout", "ok", "ok")) 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, "_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")) @@ -607,7 +678,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": 20, "warning": 1, "critical": 1, "total": 22} + assert result["summary"] == {"ok": 21, "warning": 1, "critical": 1, "total": 23} def test_pchome_controlled_apply_drift_monitor_reports_verified_zero_drift(monkeypatch): @@ -2132,6 +2203,253 @@ def test_pchome_auto_policy_signed_receipt_closeout_reports_detached_verificatio ) +def test_pchome_auto_policy_signed_receipt_evidence_intake_reports_schema_no_verification(monkeypatch): + from services import ai_automation_smoke_service as smoke + from services import pchome_mapping_backlog_service as backlog + + captured = {} + + def fake_intake(*_args, **kwargs): + captured.update(kwargs) + return { + "policy": ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_" + "signed_receipt_evidence_intake" + ), + "result": "DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_EVIDENCE_INTAKE_READY", + "source_policy": ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_" + "signed_receipt_closeout" + ), + "summary": { + "authorization_signed_receipt_evidence_intake_ready_count": 1, + "signed_receipt_evidence_intake_check_count": 12, + "signed_receipt_evidence_intake_pass_count": 12, + "signed_receipt_evidence_intake_waiting_count": 0, + "authorization_signed_receipt_closeout_ready_count": 1, + "signed_receipt_closeout_check_count": 12, + "authorization_signed_receipt_preflight_ready_count": 1, + "signed_receipt_preflight_check_count": 12, + "external_signing_receipt_evidence_boundary_count": 1, + "detached_receipt_verification_boundary_count": 1, + "detached_verification_evidence_schema_count": 1, + "required_external_receipt_evidence_count": 10, + "external_receipt_acceptance_gate_count": 8, + "detached_receipt_verification_check_count": 10, + "detached_verification_evidence_field_count": 12, + "detached_verification_acceptance_gate_count": 10, + "operator_held_secret_boundary_count": 1, + "rollback_boundary_count": 4, + "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, + }, + "future_signed_authorization_receipt_evidence_intake": { + "ready_for_future_signed_authorization_receipt_evidence_intake": True, + "can_enter_future_detached_verification_evidence_validation": True, + "external_signed_authorization_receipt_evidence_schema_ready": True, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "detached_signature_verification_performed": False, + "external_signed_authorization_receipt_included": False, + "signed_authorization_receipt_included": False, + "signature_material_included": False, + "secret_material_included": False, + "secret_material_required_in_preview": False, + "reads_secret_in_preview": False, + "manual_review_mode": "exception_only", + }, + "detached_verification_evidence_schema": { + "authorization_material_type": "detached_verification_evidence_schema", + "ready_for_future_detached_verification_evidence_schema": True, + "required_external_receipt_evidence_count": 10, + "external_receipt_acceptance_gate_count": 8, + "detached_receipt_verification_check_count": 10, + "detached_verification_evidence_fields": [ + "external_receipt_id", + "source_signed_receipt_preflight_id", + "source_external_receipt_evidence_boundary_id", + "source_signed_receipt_closeout_id", + "source_detached_receipt_verification_boundary_id", + "payload_sha256", + "receipt_sha256", + "signer_key_id_reference", + "signature_algorithm_reference", + "detached_signature_verification_status", + "verified_at_utc", + "verifier_receipt_sha256", + ], + "detached_verification_evidence_field_count": 12, + "detached_verification_acceptance_gates": [ + "production_truth_matches_intake_run", + "source_detached_boundary_id_matches", + "source_external_receipt_evidence_boundary_id_matches", + "source_signed_receipt_preflight_id_matches", + "payload_hash_matches_final_signable_request_package", + "receipt_hash_is_present_and_nonempty", + "signer_key_id_is_reference_only", + "signature_algorithm_is_reference_only", + "detached_signature_verification_status_is_passed", + "no_secret_signature_or_signed_receipt_body_in_ai_payload", + ], + "detached_verification_acceptance_gate_count": 10, + "requires_detached_signature_verification": True, + "detached_signature_verification_performed": False, + "external_signed_authorization_receipt_required_in_future": True, + "external_signed_authorization_receipt_included": False, + "signed_authorization_receipt_included": False, + "signature_material_included": False, + "signer_key_id_reference_only": True, + "signature_algorithm_reference_only": True, + "secret_material_included": False, + "secret_material_required_in_preview": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "executes_shell_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "target_file": "migrations/045_pchome_auto_policy_evidence_receipts.sql", + "hash_matches": True, + "requires_post_apply_verifier": True, + "requires_fresh_production_truth_in_same_run": True, + "operator_held_secret_boundary_contract": { + "secret_reference_mode": "external_runtime_reference_only", + "secret_material_included": False, + "secret_material_required_in_preview": False, + "reads_secret_in_preview": False, + "accepts_plaintext_secret": False, + "permits_secret_value_logging": False, + }, + }, + "signed_receipt_evidence_intake_contract": { + "machine_verifiable": True, + "permits_future_detached_verification_evidence_validation": True, + "accepts_plaintext_secret": False, + "detached_signature_verification_performed": False, + "issues_database_apply_authorization": False, + "ready_for_database_apply_now": False, + "signs_database_apply_authorization": False, + "writes_database": False, + "executes_in_preview": False, + "secret_material_required_in_preview": False, + "manual_review_mode": "exception_only", + }, + "safety": { + "reads_secret_in_preview": False, + "writes_file": False, + "writes_script_in_preview": False, + "writes_artifact_in_preview": False, + "executes_script": False, + "executes_endpoint": False, + "executes_migration": False, + "executes_sql": False, + "writes_database": False, + "signs_database_apply_authorization": False, + "performs_detached_signature_verification": False, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + } + + monkeypatch.setattr( + backlog, + "build_pchome_auto_policy_db_apply_authorization_signed_receipt_evidence_intake", + fake_intake, + ) + + result = smoke._pchome_auto_policy_signed_receipt_evidence_intake_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"]["signed_receipt_evidence_intake_pass_count"] == 12 + assert result["details"]["signed_receipt_closeout_check_count"] == 12 + assert result["details"]["detached_verification_evidence_schema_count"] == 1 + assert result["details"]["required_external_receipt_evidence_count"] == 10 + assert result["details"]["external_receipt_acceptance_gate_count"] == 8 + assert result["details"]["detached_receipt_verification_check_count"] == 10 + 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"]["reads_secret_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"]["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"]["signer_key_id_reference_only"] is True + assert result["details"]["signature_algorithm_reference_only"] is True + 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_signed_authorization_receipt_evidence_intake" + ] + is True + ) + assert ( + result["details"][ + "can_enter_future_detached_verification_evidence_validation" + ] + is True + ) + assert ( + result["details"][ + "external_signed_authorization_receipt_evidence_schema_ready" + ] + is True + ) + assert ( + result["details"]["ready_for_future_detached_verification_evidence_schema"] + is True + ) + assert ( + result["details"][ + "permits_future_detached_verification_evidence_validation" + ] + is True + ) + assert result["details"]["performs_detached_signature_verification"] is False + assert result["details"]["next_machine_action"] == ( + "continue_to_pchome_auto_policy_detached_verification_evidence_validation_lane" + ) + + def test_collect_ai_automation_smoke_persists_recent_history(tmp_path, monkeypatch): from services import ai_automation_smoke_service as smoke @@ -2157,6 +2475,7 @@ def test_collect_ai_automation_smoke_persists_recent_history(tmp_path, monkeypat monkeypatch.setattr(smoke, "_pchome_auto_policy_signing_execution_closeout_check", lambda: smoke._check("auto-policy signing execution closeout", "ok", "ok")) 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, "_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")) @@ -2215,7 +2534,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": 22, "warning": 0, "critical": 0, "total": 22}, + "summary": {"ok": 23, "warning": 0, "critical": 0, "total": 23}, "checks": [ { "name": "PChome 受控落地 drift monitor", @@ -2241,6 +2560,7 @@ def test_scheduled_automation_health_summary_reads_history_without_side_effects( _auto_policy_signing_execution_closeout_history_check(), _auto_policy_signed_receipt_preflight_history_check(), _auto_policy_signed_receipt_closeout_history_check(), + _auto_policy_signed_receipt_evidence_intake_history_check(), { "name": "AI surface HTML readback", "status": "ok", @@ -2302,7 +2622,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"] == 19 + assert summary["summary"]["total"] == 20 assert summary["summary"]["primary_human_gate_count"] == 0 assert summary["summary"]["writes_database_count"] == 0 assert pchome_family["status"] == "ok" @@ -2919,6 +3239,139 @@ def test_scheduled_automation_health_summary_reads_history_without_side_effects( ] is True ) + signed_receipt_intake_family = next( + item for item in summary["families"] + if item["key"] == "pchome_auto_policy_signed_receipt_evidence_intake" + ) + assert signed_receipt_intake_family["status"] == "ok" + assert ( + signed_receipt_intake_family["details"][ + "signed_receipt_evidence_intake_pass_count" + ] + == 12 + ) + assert ( + signed_receipt_intake_family["details"][ + "signed_receipt_closeout_check_count" + ] + == 12 + ) + assert ( + signed_receipt_intake_family["details"][ + "detached_verification_evidence_schema_count" + ] + == 1 + ) + assert ( + signed_receipt_intake_family["details"][ + "required_external_receipt_evidence_count" + ] + == 10 + ) + assert ( + signed_receipt_intake_family["details"][ + "external_receipt_acceptance_gate_count" + ] + == 8 + ) + assert ( + signed_receipt_intake_family["details"][ + "detached_receipt_verification_check_count" + ] + == 10 + ) + assert ( + signed_receipt_intake_family["details"][ + "detached_verification_evidence_field_count" + ] + == 12 + ) + assert ( + signed_receipt_intake_family["details"][ + "detached_verification_acceptance_gate_count" + ] + == 10 + ) + assert ( + signed_receipt_intake_family["details"]["payload_source"] + == "local_contract_fixture" + ) + assert signed_receipt_intake_family["details"]["outbound_network"] is False + assert signed_receipt_intake_family["details"]["business_data_source"] is False + assert signed_receipt_intake_family["details"]["primary_human_gate_count"] == 0 + assert signed_receipt_intake_family["details"]["reads_secret_count"] == 0 + assert signed_receipt_intake_family["details"]["writes_database_count"] == 0 + assert ( + signed_receipt_intake_family["details"][ + "signs_database_apply_authorization_count" + ] + == 0 + ) + assert ( + signed_receipt_intake_family["details"]["secret_reference_mode"] + == "external_runtime_reference_only" + ) + assert ( + signed_receipt_intake_family["details"][ + "requires_detached_signature_verification" + ] + is True + ) + assert ( + signed_receipt_intake_family["details"][ + "detached_signature_verification_performed" + ] + is False + ) + assert ( + signed_receipt_intake_family["details"][ + "external_signed_authorization_receipt_included" + ] + is False + ) + assert ( + signed_receipt_intake_family["details"][ + "signed_authorization_receipt_included" + ] + is False + ) + assert ( + signed_receipt_intake_family["details"]["signature_material_included"] + is False + ) + assert ( + signed_receipt_intake_family["details"]["signs_database_apply_authorization"] + is False + ) + assert ( + signed_receipt_intake_family["details"]["secret_material_included"] + is False + ) + assert signed_receipt_intake_family["details"]["accepts_plaintext_secret"] is False + assert ( + signed_receipt_intake_family["details"][ + "ready_for_future_signed_authorization_receipt_evidence_intake" + ] + is True + ) + assert ( + signed_receipt_intake_family["details"][ + "ready_for_future_detached_verification_evidence_schema" + ] + is True + ) + assert ( + signed_receipt_intake_family["details"][ + "permits_future_detached_verification_evidence_validation" + ] + is True + ) + assert ( + signed_receipt_intake_family["details"][ + "performs_detached_signature_verification" + ] + is False + ) surface_family = next( item for item in summary["families"] if item["key"] == "ai_surface_html_readback" @@ -2953,7 +3406,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": 21, "warning": 0, "critical": 1, "total": 22}, + "summary": {"ok": 22, "warning": 0, "critical": 1, "total": 23}, "checks": [ { "name": "PChome 受控落地 drift monitor", @@ -2973,6 +3426,7 @@ def test_scheduled_automation_health_summary_can_use_current_smoke_without_recor _auto_policy_signing_execution_closeout_history_check(), _auto_policy_signed_receipt_preflight_history_check(), _auto_policy_signed_receipt_closeout_history_check(), + _auto_policy_signed_receipt_evidence_intake_history_check(), { "name": "AI surface HTML readback", "status": "ok", @@ -3023,120 +3477,6 @@ def test_scheduled_automation_health_summary_falls_back_to_visual_qa_artifact(tm from services import ai_automation_smoke_service as smoke from services import ai_surface_html_readback_service as surface_service - history_path = tmp_path / "smoke_history.jsonl" - history_path.write_text( - json.dumps({ - "generated_at": datetime.now().isoformat(timespec="seconds"), - "status": "ok", - "summary": {"ok": 20, "warning": 0, "critical": 0, "total": 20}, - "checks": [], - }, ensure_ascii=False) + "\n", - encoding="utf-8", - ) - monkeypatch.setattr(smoke, "_HISTORY_PATH", str(history_path)) - monkeypatch.setattr( - smoke, - "_pchome_auto_policy_authorization_guard_check", - lambda: _auto_policy_guard_history_check(), - ) - monkeypatch.setattr( - smoke, - "_pchome_auto_policy_decision_preflight_check", - lambda: _auto_policy_decision_preflight_history_check(), - ) - monkeypatch.setattr( - smoke, - "_pchome_auto_policy_decision_closeout_check", - lambda: _auto_policy_decision_closeout_history_check(), - ) - monkeypatch.setattr( - smoke, - "_pchome_auto_policy_authorization_issuer_gate_check", - lambda: _auto_policy_authorization_issuer_gate_history_check(), - ) - monkeypatch.setattr( - smoke, - "_pchome_auto_policy_signing_decision_preflight_check", - lambda: _auto_policy_signing_decision_preflight_history_check(), - ) - monkeypatch.setattr( - smoke, - "_pchome_auto_policy_signing_decision_closeout_check", - lambda: _auto_policy_signing_decision_closeout_history_check(), - ) - monkeypatch.setattr( - smoke, - "_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( - smoke, - "_pchome_auto_policy_signing_execution_preflight_check", - lambda: _auto_policy_signing_execution_preflight_history_check(), - ) - monkeypatch.setattr( - smoke, - "_pchome_auto_policy_signing_execution_closeout_check", - lambda: _auto_policy_signing_execution_closeout_history_check(), - ) - monkeypatch.setattr( - smoke, - "_pchome_auto_policy_signed_receipt_preflight_check", - lambda: _auto_policy_signed_receipt_preflight_history_check(), - ) - monkeypatch.setattr( - smoke, - "_pchome_auto_policy_signed_receipt_closeout_check", - lambda: _auto_policy_signed_receipt_closeout_history_check(), - ) - monkeypatch.setattr( - surface_service, - "build_sitewide_visual_qa_readback", - lambda **_kwargs: { - "policy": "read_only_sitewide_visual_qa_readback_v1", - "status": "ok", - "artifact_path": "data/ai_automation/sitewide_visual_qa_latest.json", - "artifact_exists": True, - "artifact_generated_at": "2026-07-03T07:40:00", - "next_machine_action": "keep_sitewide_visual_qa_monitoring", - "summary": { - "result_count": 147, - "route_count": 49, - "viewport_count": 3, - "pass_count": 147, - "failed_count": 0, - "overflow_issue_count": 0, - "visual_offender_count": 0, - "stale": False, - }, - }, - ) - - summary = smoke.build_scheduled_automation_health_summary( - history_limit=5, - freshness_max_age_hours=9999, - ) - - visual_family = next( - item for item in summary["families"] - if item["key"] == "sitewide_visual_qa" - ) - assert visual_family["status"] == "ok" - assert visual_family["details"]["route_count"] == 49 - assert visual_family["details"]["failed_count"] == 0 - - -def test_scheduled_automation_health_summary_route_returns_compact_payload(tmp_path, monkeypatch): - from datetime import datetime - from flask import Flask - from routes import system_public_routes as routes - from services import ai_automation_smoke_service as smoke - history_path = tmp_path / "smoke_history.jsonl" history_path.write_text( json.dumps({ @@ -3208,6 +3548,130 @@ def test_scheduled_automation_health_summary_route_returns_compact_payload(tmp_p "_pchome_auto_policy_signed_receipt_closeout_check", lambda: _auto_policy_signed_receipt_closeout_history_check(), ) + monkeypatch.setattr( + smoke, + "_pchome_auto_policy_signed_receipt_evidence_intake_check", + lambda: _auto_policy_signed_receipt_evidence_intake_history_check(), + ) + monkeypatch.setattr( + surface_service, + "build_sitewide_visual_qa_readback", + lambda **_kwargs: { + "policy": "read_only_sitewide_visual_qa_readback_v1", + "status": "ok", + "artifact_path": "data/ai_automation/sitewide_visual_qa_latest.json", + "artifact_exists": True, + "artifact_generated_at": "2026-07-03T07:40:00", + "next_machine_action": "keep_sitewide_visual_qa_monitoring", + "summary": { + "result_count": 147, + "route_count": 49, + "viewport_count": 3, + "pass_count": 147, + "failed_count": 0, + "overflow_issue_count": 0, + "visual_offender_count": 0, + "stale": False, + }, + }, + ) + + summary = smoke.build_scheduled_automation_health_summary( + history_limit=5, + freshness_max_age_hours=9999, + ) + + visual_family = next( + item for item in summary["families"] + if item["key"] == "sitewide_visual_qa" + ) + assert visual_family["status"] == "ok" + assert visual_family["details"]["route_count"] == 49 + assert visual_family["details"]["failed_count"] == 0 + + +def test_scheduled_automation_health_summary_route_returns_compact_payload(tmp_path, monkeypatch): + from datetime import datetime + from flask import Flask + from routes import system_public_routes as routes + from services import ai_automation_smoke_service as smoke + + history_path = tmp_path / "smoke_history.jsonl" + history_path.write_text( + json.dumps({ + "generated_at": datetime.now().isoformat(timespec="seconds"), + "status": "ok", + "summary": {"ok": 22, "warning": 0, "critical": 0, "total": 22}, + "checks": [], + }, ensure_ascii=False) + "\n", + encoding="utf-8", + ) + monkeypatch.setattr(smoke, "_HISTORY_PATH", str(history_path)) + monkeypatch.setattr( + smoke, + "_pchome_auto_policy_authorization_guard_check", + lambda: _auto_policy_guard_history_check(), + ) + monkeypatch.setattr( + smoke, + "_pchome_auto_policy_decision_preflight_check", + lambda: _auto_policy_decision_preflight_history_check(), + ) + monkeypatch.setattr( + smoke, + "_pchome_auto_policy_decision_closeout_check", + lambda: _auto_policy_decision_closeout_history_check(), + ) + monkeypatch.setattr( + smoke, + "_pchome_auto_policy_authorization_issuer_gate_check", + lambda: _auto_policy_authorization_issuer_gate_history_check(), + ) + monkeypatch.setattr( + smoke, + "_pchome_auto_policy_signing_decision_preflight_check", + lambda: _auto_policy_signing_decision_preflight_history_check(), + ) + monkeypatch.setattr( + smoke, + "_pchome_auto_policy_signing_decision_closeout_check", + lambda: _auto_policy_signing_decision_closeout_history_check(), + ) + monkeypatch.setattr( + smoke, + "_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( + smoke, + "_pchome_auto_policy_signing_execution_preflight_check", + lambda: _auto_policy_signing_execution_preflight_history_check(), + ) + monkeypatch.setattr( + smoke, + "_pchome_auto_policy_signing_execution_closeout_check", + lambda: _auto_policy_signing_execution_closeout_history_check(), + ) + monkeypatch.setattr( + smoke, + "_pchome_auto_policy_signed_receipt_preflight_check", + lambda: _auto_policy_signed_receipt_preflight_history_check(), + ) + monkeypatch.setattr( + smoke, + "_pchome_auto_policy_signed_receipt_closeout_check", + lambda: _auto_policy_signed_receipt_closeout_history_check(), + ) + monkeypatch.setattr( + smoke, + "_pchome_auto_policy_signed_receipt_evidence_intake_check", + lambda: _auto_policy_signed_receipt_evidence_intake_history_check(), + ) app = Flask(__name__) with app.test_request_context( @@ -3443,6 +3907,7 @@ def test_surface_html_readback_check_is_part_of_ai_smoke(monkeypatch): monkeypatch.setattr(smoke, "_pchome_auto_policy_signing_execution_closeout_check", lambda: smoke._check("auto-policy signing execution closeout", "ok", "ok")) 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, "_sitewide_visual_qa_check", lambda: smoke._check( "Sitewide visual QA readback", "ok", @@ -3464,7 +3929,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"] == 22 + assert result["summary"]["total"] == 23 assert surface_check["status"] == "ok" assert surface_check["details"]["checked_surface_count"] == 10 assert sitewide_check["status"] == "ok"