From 71a9ca4f3db6999fd8e7e6acc3b2c563d28e2279 Mon Sep 17 00:00:00 2001 From: ogt Date: Wed, 1 Jul 2026 13:22:16 +0800 Subject: [PATCH] Add PChome AI controlled dry-run closeout chain --- TODO_NEXT_STEPS.txt | 2254 +- docs/AI_INTELLIGENCE_MODULE_SOT.md | 122 +- routes/README.md | 13 +- routes/ai_routes.py | 3779 +- scripts/ops/check_production_version_truth.py | 176 + services/ai_automation_debt_service.py | 427 + services/ai_exception_contract.py | 53 + .../ai_controlled_route_aliases.py | 425 + services/pchome_mapping_backlog_service.py | 39967 ++++++++++++++++ tests/test_ai_automation_debt_service.py | 49 + tests/test_pchome_mapping_backlog_report.py | 20616 ++++++++ tests/test_production_version_truth.py | 68 + 12 files changed, 67807 insertions(+), 142 deletions(-) create mode 100755 scripts/ops/check_production_version_truth.py create mode 100644 services/ai_automation_debt_service.py create mode 100644 services/ai_exception_contract.py create mode 100644 services/market_intel/ai_controlled_route_aliases.py create mode 100644 services/pchome_mapping_backlog_service.py create mode 100644 tests/test_ai_automation_debt_service.py create mode 100644 tests/test_pchome_mapping_backlog_report.py create mode 100644 tests/test_production_version_truth.py diff --git a/TODO_NEXT_STEPS.txt b/TODO_NEXT_STEPS.txt index 09208a8..3764722 100644 --- a/TODO_NEXT_STEPS.txt +++ b/TODO_NEXT_STEPS.txt @@ -1,4 +1,2078 @@ +================================================================================ + Production Version Truth Guard (2026-06-27) [ACTIVE] +================================================================================ + +正式環境 `/health` 是最新版本真相;local/Gitea/iCloud 只能作為 source candidate。 +開工、部署前、handoff 或任何版本宣稱前先跑: + + python scripts/ops/check_production_version_truth.py + +目前 protected baseline:production `https://mo.wooo.work/health` = `V10.725`、 +clean workspace `/Users/ogt/codex-workspaces/ewoooc-dev` = `V10.725`、 +origin/main = `f3e412c`。舊 iCloud checkout 的 local-only `V10.726` 不是正式版本。 + +================================================================================ + 產品完整工作優先順序鎖 (2026-06-29) [ACTIVE] +================================================================================ + +完整工作目標不是單一 API,而是把正式產品做成「PChome 業績成長自動化作戰系統」: +AI 自動化要能自己找缺口、補證據、產生候選、形成可驗證決策與可回滾落地包; +產品網站要像主流專業 SaaS 一樣,首屏直接呈現狀態、價值、下一步與可信證據。 + +P0. 正式版本真相與正式缺口盤點 + - production `/health`、mapping backlog、業績日、資料新鮮度與缺口數必須先確認。 + - 不得把本機、iCloud、未部署 source-ready 誤認為正式最新版本。 + +P1. AI 自動化營收閉環:先補 PChome 商品對應 + - 優先處理 `needs_mapping_count=16` 中的 direct mapping 缺口。 + - 目標是自動產生 MOMO 搜尋詞、候選包、身份錨點、接受門檻與 no-write receipt。 + - 不退回人工逐筆找商品;人工只處理 exception / machine-verifiable decision。 + +P2. AI 自動化候選確認與證據閉環 + - review candidate 要轉成 decision envelope / candidate decision package。 + - source-ready: direct mapping candidate decision package 會把 read-only 搜尋候選轉成 P2 decision envelope,分流到 no-write receipt 或 exception-only machine review。 + - source-ready: `/api/ai/pchome-growth/ai-automation-readiness` 已聚合缺口偵測、同款搜尋包、候選決策包、證據收據與受控落地,並明確回傳 `primary_human_gate_count=0`、`automation_policy.primary_flow=ai_controlled`;例外也必須進 AI machine-verifiable auto-resolution。 + - image / availability / price / unit basis 要用 controlled fetch、parser、merge receipt 自動補齊。 + - 所有 AI Agent 要保留 guardrails、confidence、trace、資料品質與可回滾下一步。 + +P3. 主流專業產品網站與營運 UI + - 首頁 / Dashboard 必須是「業績成長指揮台」,直接顯示缺口、價值、下一步、可執行 package。 + - source-ready: 首頁 command center 已加入 AI 自動化作戰流水線,顯示同款搜尋包、候選決策包、證據收據與受控落地狀態。 + - UI 不得外露 raw API、endpoint、DB 欄位、模型名、工程錯誤;要用營運語言。 + - 商品列必須有商品圖、平台 ID、賣場連結、價格證據、可信度與下一步。 + - 參考外部專業做法:Google Merchant / Product structured data / Baymard product comparison UX。 + +P4. 外部來源與資料治理 + - MOMO 是已接入來源;Shopee、Lazada、Amazon、Google Merchant / Shopping、TikTok Shop、LINE 購物、Rakuten、Yahoo、露天、品牌官網 / Shopify、Meta Commerce、Coupang 必須至少有來源契約或待接入狀態。 + - 外部資料必須正規化成 offer evidence:平台商品 ID、商品名、賣場連結、圖片、價格、庫存、時間、可信度。 + - source-ready: growth API 的 source_scope 會從 source readiness 自動列出完整 paused sources;source readiness 已回傳 offer evidence contract,包含商品 ID、名稱、連結、圖片、價格、庫存、時間與可信度。 + +P5. no-write persistence / verifier / apply readiness + - 只有在 P1-P4 的產品缺口與證據閉環穩定後,才推 no-write persistence、verifier、apply readiness。 + - 綠燈只代表 ready for controlled apply,不代表已部署或已寫正式 DB。 + +P6. DB apply authorization / signing / closeout + - 這是最後的受控 apply 安全鏈,不得搶在 P1-P4 前面。 + - 預設不讀 secret、不簽發 authorization、不執行 SQL、不寫 DB,直到 controlled apply 條件完整。 + +================================================================================ + PChome Mapping Backlog Read-Only Report (2026-06-28) [ACTIVE] +================================================================================ + +正式 PChome growth API 目前顯示 top 20 商品中 mapped_count=4、needs_mapping_count=16、 +mapping_rate=20.0%;下一個 P0 是先補商品對應,再確認已找到 MOMO 候選的覆核項。 +開工順序: + + python scripts/ops/check_production_version_truth.py + python scripts/ops/report_pchome_mapping_backlog.py --json + +同一口徑已抽到 `services/pchome_mapping_backlog_service.py`,並提供登入後只讀 API +`/api/ai/pchome-growth/mapping-backlog` 與只讀 operator preview +`/api/ai/pchome-growth/mapping-backlog/operator-preview`,以及只讀 evidence enrichment +preview `/api/ai/pchome-growth/mapping-backlog/evidence-enrichment-preview`、只讀 evidence +source preview `/api/ai/pchome-growth/mapping-backlog/evidence-source-preview`。此 reporter / +API 只做 GET readback / run package preview / evidence task preview / source wiring preview, +不執行 backfill POST、不抓外站、不寫 DB、不派 Telegram。Operator +preview 已把 Google Merchant Center product data、Google Product / Merchant structured +data、Baymard ecommerce product/search UX 轉成 evidence checklist,並加入 Ollama-first +AI automation plan;每個 mapping target 也會輸出 `evidence_completeness`(product URL、 +present/missing fields、blocking missing fields、auto_accept_ready、ai_exception_required、 +本地 deterministic `unit_package_basis` parser preview)。production-derived evidence preview +目前剩餘主要缺口是 image / availability,另有 1 筆 review candidate price payload 缺口; +source preview 已列出 image / availability 的 future GET fetch gate 與 price payload mapping +probe,並完成 check-mode HTML fixture parser,可解析 JSON-LD `Product.image`、 +`Offer.availability` 與 `og:image` fallback;preview/parser 不呼叫 LLM、不允許 Gemini、 +不寫 DB。Controlled read-only fetch gate 已接上 +`/api/ai/pchome-growth/mapping-backlog/evidence-fetch-gate`,預設只產生 planned receipts; +`execute_fetch=1` 才對 allowlist domain `24h.pchome.com.tw` 做最多 12 筆 GET,含 timeout、 +512KB HTML cap 與 no-write receipt。本機 production-derived smoke 已以 1 筆 GET 讀到 +`Product.image` 與 `Offer.availability=InStock`,仍不寫 DB。Evidence merge preview 已接上 +`/api/ai/pchome-growth/mapping-backlog/evidence-merge-preview`;production-derived 1 筆 smoke +已讓 `DDBH3K-A900K0KWV-002` 的 `image/availability` 從 missing fields 移除,summary +`merge_ready_count=1`、`writes_database_count=0`。自動化策略已改為 automation-first: +review candidate 的 `pchome_price` 納入 price evidence;production-derived batch=12 已達 +`AUTO_ACCEPT_EVIDENCE_MERGE=10/10`、`ai_exception_required_count=0`、 +`remaining_blocker_count=0`、`writes_database_count=0`。下一個 P0 是 auto-policy receipt +persistence gate / no-write UI,而不是人工逐筆審核。Auto-policy receipt gate 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-receipt-gate`,Dashboard 也顯示 no-write +自動收據指標;production-derived batch=12 已達 `READY_FOR_AUTO_PERSISTENCE=10/10`、 +`persists_receipt_count=0`、`writes_database_count=0`、`ai_exception_required_count=0`。 +Controlled persistence dry-run gate 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-persistence-gate`,只接受 auto-policy +receipt gate 的 ready receipts,輸出 idempotency key、payload hash、schema migration +contract、transaction preview、rollback plan 與 post-write verifier;目前仍是 no-write / +dry-run-only。production-derived batch=12 smoke 已達 `PERSISTENCE_DRY_RUN_READY`、 +`dry_run_ready_count=10/10`、`ai_exception_required_count=0`、`writes_database_count=0`、 +`persists_receipt_count=0`。Schema migration preview gate 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-schema-migration-preview`,輸出 +`external_offer_evidence_receipts` DDL preview、rollback preview、prewrite snapshot +contract、future apply verifier 與 exception-only failure routing;目前仍是 no-write / +no-SQL-execute preview。production-derived batch=12 smoke 已達 +`SCHEMA_MIGRATION_PREVIEW_READY`、`dry_run_ready_count=10/10`、`schema_statement_count=13`、 +`future_verifier_count=5`、`current_preview_apply_allowed=false`、 +`executes_migration_count=0`、`writes_database_count=0`。Migration file preview gate 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-file-preview`,輸出 +`migrations/045_pchome_auto_policy_evidence_receipts.sql` forward SQL preview、forward hash、 +rollback preview、future apply endpoint request contract、preflight sequence、abort +conditions 與 rollback contract;目前仍是 no-file-write / no-endpoint-execute / +no-SQL-execute preview。production-derived batch=12 smoke 已達 +`MIGRATION_FILE_PREVIEW_READY`、`dry_run_ready_count=10/10`、 +`migration_file_line_count=31`、`apply_endpoint_contract_ready_count=1`、 +`forbidden_forward_tokens_absent=true`、`writes_file_count=0`、 +`executes_endpoint_count=0`、`writes_database_count=0`。下一個 P0 是真正 migration file +生成前的 apply-readiness closeout,不是人工逐筆 UI。Apply-readiness closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-apply-readiness-closeout`,輸出 9 項 +readiness checks、4 項 future-apply blockers、migration file hash summary 與 apply endpoint +summary;closeout ready 只代表可提出 migration file generation request,不代表 DB apply。 +production-derived batch=12 smoke 已達 `APPLY_READINESS_CLOSEOUT_READY`、 +`readiness_pass_count=9/9`、`current_preview_ready_count=1`、 +`ready_for_migration_file_generation_request=true`、`ready_for_database_apply=false`、 +`future_apply_blocker_count=4`、`writes_file_count=0`、`executes_endpoint_count=0`、 +`writes_database_count=0`。目前仍是 no-file-write / no-endpoint-execute / +no-SQL-execute preview。Migration file generation request 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-file-generation-request`, +打包 request id、目標檔 `migrations/045_pchome_auto_policy_evidence_receipts.sql`、 +expected SHA256、4 項 required artifacts 與 3 步 file generation plan;request API 本身 +仍不寫檔、不執行 endpoint、不寫 DB。migration file 已在本地 repo 生成,hash 與 request +expected SHA256 對齊。production-derived batch=12 smoke 已達 +`FILE_GENERATION_REQUEST_READY`、`request_ready_count=1`、`dry_run_ready_count=10/10`、 +`required_artifact_count=4`、`file_generation_step_count=3`、 +`ready_to_generate_file=true`、`ready_for_database_apply=false`、`writes_file_count=0`、 +`executes_endpoint_count=0`、`writes_database_count=0`、`generated_file_exists=true`、 +`generated_file_hash_matches_request=true`。下一個 P0 是 migration apply gate preview / +readiness,只讀檢查 `045` migration 檔、hash、production truth、prewrite snapshot contract +與 post-apply verifier;不是直接 DB apply。Migration apply gate preview 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-apply-gate-preview`,讀本地 +`045` migration 檔並比對 request hash、additive-only、future apply endpoint contract、 +prewrite snapshot requirement 與 post-apply verifier requirement;目前仍是 no-SQL-execute / +no-DB-write preview。production-derived batch=12 smoke 已達 +`MIGRATION_APPLY_GATE_PREVIEW_READY`、`apply_gate_pass_count=9/9`、 +`apply_preview_ready_count=1`、`generated_file_hash_matches_count=1`、 +`ready_for_explicit_db_apply_request=true`、`ready_for_database_apply_now=false`、 +`future_apply_blocker_count=3`、`executes_migration_count=0`、`writes_database_count=0`。 +Explicit DB apply request gate preview 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-request-gate-preview`,輸出 +DB apply request id、future `psql "$DATABASE_URL" ...` command preview、5 項 required +artifacts、5 步 apply sequence、abort conditions、runtime readback 與 rollback gate preview; +目前仍是 no-secret-read / no-SQL-execute / no-DB-write preview。production-derived +batch=12 smoke 已達 `DB_APPLY_REQUEST_GATE_READY`、`request_ready_count=1`、 +`required_artifact_count=5`、`apply_sequence_step_count=5`、`abort_condition_count=6`、 +`generated_file_hash_matches_count=1`、`ready_for_explicit_db_apply_request=true`、 +`ready_for_database_apply_now=false`、`reads_secret_in_preview=false`、 +`executes_migration_count=0`、`writes_database_count=0`。DB apply execution preflight 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-execution-preflight`,輸出 +prewrite snapshot plan、post-apply readback plan、rollback artifact plan、required artifacts +與 abort conditions;目前仍是 no-secret-read / no-SQL-execute / no-DB-write preview。 +production-derived batch=12 smoke 已達 `DB_APPLY_EXECUTION_PREFLIGHT_READY`、 +`preflight_ready_count=1`、`request_ready_count=1`、`required_artifact_count=6`、 +`snapshot_plan_count=5`、`readback_plan_count=6`、`rollback_artifact_count=1`、 +`generated_file_hash_matches_count=1`、`ready_for_preflight_artifact_generation=true`、 +`ready_for_database_apply_now=false`、`reads_secret_in_preview=false`、 +`executes_sql_count=0`、`executes_migration_count=0`、`writes_database_count=0`。DB apply +authorization package 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-package`,輸出 +authorization checks、freshness requirements、machine apply manifest 與 verifier bundle; +目前仍是 no-secret-read / no-SQL-execute / no-DB-write preview。production-derived +batch=12 smoke 已達 `DB_APPLY_AUTHORIZATION_PACKAGE_READY`、 +`authorization_check_count=11`、`authorization_pass_count=11`、 +`authorization_waiting_count=0`、`authorization_package_ready_count=1`、 +`freshness_requirement_count=5`、`manifest_step_count=6`、`verifier_bundle_count=3`、 +`ready_for_explicit_apply_authorization_request=true`、`ready_for_database_apply_now=false`、 +`reads_secret_in_preview=false`、`executes_sql_count=0`、`executes_migration_count=0`、 +`writes_database_count=0`。DB apply verifier artifact preview 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-verifier-artifact-preview`,輸出 +prewrite snapshot / post-apply readback / rollback artifact schema、generation plan 與 +verifier manifest;目前仍是 no-file-write / no-secret-read / no-SQL-execute / +no-DB-write preview。production-derived batch=12 smoke 已達 +`DB_APPLY_VERIFIER_ARTIFACT_PREVIEW_READY`、`artifact_preview_ready_count=1`、 +`authorization_package_ready_count=1`、`artifact_schema_count=3`、 +`artifact_generation_step_count=5`、`verifier_check_count=15`、`writes_artifact_count=0`、 +`ready_for_future_artifact_generation=true`、`ready_to_write_artifacts_now=false`、 +`ready_for_database_apply_now=false`、`reads_secret_in_preview=false`、 +`executes_sql_count=0`、`executes_migration_count=0`、`writes_database_count=0`。DB apply +final handoff package 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-final-handoff-package`,輸出 +final handoff sections、future runbook、command previews、abort gates 與 source proof +manifest;目前仍是 no-file-write / no-secret-read / no-SQL-execute / no-DB-write +preview。production-derived batch=12 smoke 已達 `DB_APPLY_FINAL_HANDOFF_PACKAGE_READY`、 +`final_handoff_ready_count=1`、`artifact_preview_ready_count=1`、 +`handoff_section_count=6`、`final_runbook_step_count=7`、`command_preview_count=3`、 +`abort_gate_count=10`、`source_endpoint_count=4`、 +`ready_for_explicit_db_apply_handoff=true`、`ready_for_database_apply_now=false`、 +`writes_artifact_count=0`、`reads_secret_in_preview=false`、`executes_sql_count=0`、 +`executes_migration_count=0`、`writes_database_count=0`。DB apply controlled dry-run shell +preview 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-shell-preview`, +輸出 shell phases、script preview、check-mode contract 與 rollback hooks;目前仍是 +no-script-write / no-secret-read / no-shell-execute / no-SQL-execute / no-DB-write preview。 +production-derived batch=12 smoke 已達 +`DB_APPLY_CONTROLLED_DRY_RUN_SHELL_PREVIEW_READY`、 +`dry_run_shell_preview_ready_count=1`、`final_handoff_ready_count=1`、 +`shell_phase_count=9`、`shell_script_line_count=10`、 +`check_mode_required_check_count=6`、`rollback_hook_count=3`、 +`writes_script_count=0`、`executes_script_count=0`、 +`ready_for_future_shell_script_generation=true`、`ready_to_write_script_now=false`、 +`ready_to_execute_shell_now=false`、`ready_for_database_apply_now=false`、 +`reads_secret_in_preview=false`、`executes_sql_count=0`、`executes_migration_count=0`、 +`writes_database_count=0`。DB apply controlled dry-run shell closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-shell-closeout`, +輸出 closeout checks、future apply boundaries 與 explicit authorization boundary;目前仍是 +no-script-write / no-secret-read / no-shell-execute / no-SQL-execute / no-DB-write preview。 +production-derived batch=12 smoke 已達 +`DB_APPLY_CONTROLLED_DRY_RUN_SHELL_CLOSEOUT_READY`、`closeout_ready_count=1`、 +`closeout_check_count=13`、`closeout_pass_count=13`、`closeout_waiting_count=0`、 +`dry_run_shell_preview_ready_count=1`、`future_apply_boundary_count=6`、 +`ready_for_explicit_apply_authorization_boundary=true`、`ready_for_database_apply_now=false`、 +`writes_script_count=0`、`executes_script_count=0`、`reads_secret_in_preview=false`、 +`executes_sql_count=0`、`executes_migration_count=0`、`writes_database_count=0`。DB apply +authorization request intake 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-request-intake`, +輸出 authorization request intake、request payload schema、acceptance gates、 +required request evidence 與 rejection reasons;目前仍是 request-intake-only / +no-secret-read / no-shell-execute / no-SQL-execute / no-DB-write preview。production-derived +batch=12 smoke 已達 `DB_APPLY_AUTHORIZATION_REQUEST_INTAKE_READY`、 +`authorization_request_intake_ready_count=1`、`required_request_evidence_count=7`、 +`request_payload_required_field_count=10`、`authorization_acceptance_gate_count=11`、 +`authorization_acceptance_pass_count=11`、`authorization_acceptance_waiting_count=0`、 +`rejection_reason_count=10`、`closeout_ready_count=1`、`future_apply_boundary_count=6`、 +`accepts_authorization_request=true`、`issues_database_apply_authorization=false`、 +`ready_for_database_apply_now=false`、`reads_secret_in_preview=false`、 +`executes_script_count=0`、`executes_sql_count=0`、`executes_migration_count=0`、 +`writes_database_count=0`。DB apply authorization request closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-request-closeout`, +輸出 final exact request package、machine request manifest 與 closeout checks;目前仍是 +final-request-package-only / no-secret-read / no-shell-execute / no-SQL-execute / +no-DB-write preview。production-derived batch=12 smoke 已達 +`DB_APPLY_AUTHORIZATION_REQUEST_CLOSEOUT_READY`、 +`authorization_request_closeout_ready_count=1`、`closeout_check_count=12`、 +`closeout_pass_count=12`、`closeout_waiting_count=0`、 +`authorization_request_intake_ready_count=1`、`exact_request_payload_field_count=10`、 +`machine_request_manifest_step_count=6`、`required_request_evidence_count=7`、 +`authorization_acceptance_gate_count=11`、`rejection_reason_count=10`、 +`ready_for_exact_authorization_request_package=true`、 +`issues_database_apply_authorization=false`、`ready_for_database_apply_now=false`、 +`reads_secret_in_preview=false`、`executes_script_count=0`、`executes_sql_count=0`、 +`executes_migration_count=0`、`writes_database_count=0`。下一個 P0 是 future DB apply +authorization lane guard,仍不是直接 DB apply。DB apply authorization lane guard 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-lane-guard`, +輸出 future authorization lane guard、lane transfer contract、lane entry requirements +與 lane guard checks;目前仍是 lane-guard-only / no-secret-read / no-shell-execute / +no-SQL-execute / no-DB-write preview。production-derived batch=12 smoke 已達 +`DB_APPLY_AUTHORIZATION_LANE_GUARD_READY`、`authorization_lane_guard_ready_count=1`、 +`lane_guard_check_count=12`、`lane_guard_pass_count=12`、`lane_guard_waiting_count=0`、 +`authorization_request_closeout_ready_count=1`、`exact_request_payload_field_count=10`、 +`machine_request_manifest_step_count=6`、`lane_entry_requirement_count=6`、 +`required_request_evidence_count=7`、`authorization_acceptance_gate_count=11`、 +`rejection_reason_count=10`、`ready_for_future_authorization_lane_entry=true`、 +`issues_database_apply_authorization=false`、`ready_for_database_apply_now=false`、 +`reads_secret_in_preview=false`、`executes_script_count=0`、`executes_sql_count=0`、 +`executes_migration_count=0`、`writes_database_count=0`。下一個 P0 是 future DB apply +authorization decision preflight,仍不是直接 DB apply。DB apply authorization decision +preflight 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-decision-preflight`, +輸出 future authorization decision preflight、decision input requirements、decision rejection +policy 與 preflight checks;目前仍是 decision-preflight-only / no-secret-read / +no-shell-execute / no-SQL-execute / no-DB-write preview。production-derived batch=12 smoke +已達 `DB_APPLY_AUTHORIZATION_DECISION_PREFLIGHT_READY`、 +`authorization_decision_preflight_ready_count=1`、`decision_preflight_check_count=12`、 +`decision_preflight_pass_count=12`、`decision_preflight_waiting_count=0`、 +`authorization_lane_guard_ready_count=1`、`decision_input_requirement_count=8`、 +`decision_rejection_reason_count=10`、`lane_entry_requirement_count=6`、 +`exact_request_payload_field_count=10`、`machine_request_manifest_step_count=6`、 +`allows_authorization_decision_in_future_lane=true`、 +`issues_database_apply_authorization=false`、`ready_for_database_apply_now=false`、 +`reads_secret_in_preview=false`、`executes_script_count=0`、`executes_sql_count=0`、 +`executes_migration_count=0`、`writes_database_count=0`。DB apply authorization decision +closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-decision-closeout`, +輸出 future authorization decision closeout、future authorization decision package、 +decision closeout contract 與 closeout checks;目前仍是 decision-closeout-only / +no-secret-read / no-shell-execute / no-SQL-execute / no-DB-write preview。production-derived +batch=12 smoke 已達 `DB_APPLY_AUTHORIZATION_DECISION_CLOSEOUT_READY`、 +`authorization_decision_closeout_ready_count=1`、`decision_closeout_check_count=12`、 +`decision_closeout_pass_count=12`、`decision_closeout_waiting_count=0`、 +`authorization_decision_preflight_ready_count=1`、`decision_input_requirement_count=8`、 +`decision_rejection_reason_count=10`、`post_apply_verifier_required_count=1`、 +`same_run_truth_required_count=1`、`permits_future_authorization_decision_lane=true`、 +`issues_database_apply_authorization=false`、`ready_for_database_apply_now=false`、 +`reads_secret_in_preview=false`、`executes_script_count=0`、`executes_sql_count=0`、 +`executes_migration_count=0`、`writes_database_count=0`。DB apply authorization issuer +gate 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-issuer-gate`, +輸出 future authorization issuer gate、final nonsecret authorization envelope、issuer gate +contract 與 issuer gate checks;目前仍是 issuer-gate-only / nonsecret-envelope-only / +no-secret-read / no-shell-execute / no-SQL-execute / no-DB-write preview。production-derived +batch=12 smoke 已達 `DB_APPLY_AUTHORIZATION_ISSUER_GATE_READY`、 +`authorization_issuer_gate_ready_count=1`、`issuer_gate_check_count=12`、 +`issuer_gate_pass_count=12`、`issuer_gate_waiting_count=0`、 +`authorization_decision_closeout_ready_count=1`、`decision_closeout_check_count=12`、 +`required_issuer_evidence_count=9`、`nonsecret_authorization_claim_count=8`、 +`post_apply_verifier_required_count=1`、`same_run_truth_required_count=1`、 +`permits_future_authorization_issuer_lane=true`、`issues_database_apply_authorization=false`、 +`signs_database_apply_authorization=false`、`ready_for_database_apply_now=false`、 +`secret_material_included=false`、`reads_secret_in_preview=false`、 +`executes_script_count=0`、`executes_sql_count=0`、`executes_migration_count=0`、 +`writes_database_count=0`。DB apply authorization signing-decision preflight 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-decision-preflight`, +輸出 future authorization signing decision preflight、signing decision preflight envelope、 +signing decision input requirements、rejection policy 與 preflight checks;目前仍是 +signing-decision-preflight-only / no-secret-read / no-signing / no-shell-execute / +no-SQL-execute / no-DB-write preview。production-derived batch=12 smoke 已達 +`DB_APPLY_AUTHORIZATION_SIGNING_DECISION_PREFLIGHT_READY`、 +`authorization_signing_decision_preflight_ready_count=1`、 +`signing_decision_preflight_check_count=12`、`signing_decision_preflight_pass_count=12`、 +`signing_decision_preflight_waiting_count=0`、`authorization_issuer_gate_ready_count=1`、 +`issuer_gate_check_count=12`、`required_issuer_evidence_count=9`、 +`nonsecret_authorization_claim_count=8`、`signing_decision_input_requirement_count=10`、 +`signing_decision_rejection_reason_count=11`、 +`allows_future_authorization_signing_decision_lane=true`、 +`issues_database_apply_authorization=false`、`signs_database_apply_authorization=false`、 +`ready_for_database_apply_now=false`、`secret_material_required_in_preview=false`、 +`reads_secret_in_preview=false`、`executes_script_count=0`、`executes_sql_count=0`、 +`executes_migration_count=0`、`writes_database_count=0`。DB apply authorization +signing-decision closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-decision-closeout`, +輸出 future authorization signing decision closeout、unsigned signing decision package、 +signing decision closeout contract 與 closeout checks;目前仍是 +signing-decision-closeout-only / unsigned-package-only / no-secret-read / no-signing / +no-shell-execute / no-SQL-execute / no-DB-write preview。production-derived batch=12 smoke +已達 `DB_APPLY_AUTHORIZATION_SIGNING_DECISION_CLOSEOUT_READY`、 +`authorization_signing_decision_closeout_ready_count=1`、 +`signing_decision_closeout_check_count=12`、`signing_decision_closeout_pass_count=12`、 +`signing_decision_closeout_waiting_count=0`、 +`authorization_signing_decision_preflight_ready_count=1`、 +`signing_decision_input_requirement_count=10`、 +`signing_decision_rejection_reason_count=11`、`required_issuer_evidence_count=9`、 +`nonsecret_authorization_claim_count=8`、 +`permits_future_unsigned_signing_decision_package_lane=true`、 +`issues_database_apply_authorization=false`、`signs_database_apply_authorization=false`、 +`ready_for_database_apply_now=false`、`secret_material_included=false`、 +`secret_material_required_in_preview=false`、`reads_secret_in_preview=false`、 +`executes_script_count=0`、`executes_sql_count=0`、`executes_migration_count=0`、 +`writes_database_count=0`。DB apply authorization signing-issuer guard 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-issuer-guard`, +輸出 future authorization signing issuer guard、signable request boundary、signing issuer +guard contract 與 guard checks;目前仍是 signing-issuer-guard-only / +signable-boundary-only / no-secret-read / no-signing / no-shell-execute / no-SQL-execute / +no-DB-write preview。production-derived batch=12 smoke 已達 +`DB_APPLY_AUTHORIZATION_SIGNING_ISSUER_GUARD_READY`、 +`authorization_signing_issuer_guard_ready_count=1`、 +`signing_issuer_guard_check_count=12`、`signing_issuer_guard_pass_count=12`、 +`signing_issuer_guard_waiting_count=0`、 +`authorization_signing_decision_closeout_ready_count=1`、 +`signing_decision_closeout_check_count=12`、 +`signing_decision_input_requirement_count=10`、 +`signing_decision_rejection_reason_count=11`、`required_issuer_evidence_count=9`、 +`nonsecret_authorization_claim_count=8`、 +`permits_future_authorization_signing_issuer_lane=true`、 +`issues_database_apply_authorization=false`、`signs_database_apply_authorization=false`、 +`ready_for_database_apply_now=false`、`secret_material_included=false`、 +`secret_material_required_in_preview=false`、`reads_secret_in_preview=false`、 +`executes_script_count=0`、`executes_sql_count=0`、`executes_migration_count=0`、 +`writes_database_count=0`。下一個 P0 是 future DB apply authorization signing-issuer +closeout / final signable request package,仍不是直接 DB apply。 +DB apply authorization signing-issuer closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-issuer-closeout`, +輸出 future signing issuer closeout、final signable request package、closeout contract +與 12 項 closeout checks;目前仍是 signing-issuer-closeout-only / +final-signable-request-package-only / no-secret-read / no-signing / no-shell-execute / +no-SQL-execute / no-DB-write preview。production-derived batch=12 smoke 已達 +`DB_APPLY_AUTHORIZATION_SIGNING_ISSUER_CLOSEOUT_READY`、 +`authorization_signing_issuer_closeout_ready_count=1`、 +`signing_issuer_closeout_check_count=12`、`signing_issuer_closeout_pass_count=12`、 +`signing_issuer_closeout_waiting_count=0`、 +`authorization_signing_issuer_guard_ready_count=1`、 +`signing_issuer_guard_check_count=12`、`signing_decision_input_requirement_count=10`、 +`signing_decision_rejection_reason_count=11`、`required_issuer_evidence_count=9`、 +`nonsecret_authorization_claim_count=8`、`post_apply_verifier_required_count=1`、 +`same_run_truth_required_count=1`、`signs_database_apply_authorization_count=0`、 +`ready_for_database_apply_now=false`、`secret_material_included=false`、 +`secret_material_required_in_preview=false`、`reads_secret_count=0`、 +`executes_script_count=0`、`executes_sql_count=0`、`executes_migration_count=0`、 +`writes_database_count=0`。下一個 P0 是 future explicit authorization signing execution +preflight / operator-held secret boundary,仍不是直接 DB apply。 +DB apply authorization signing execution preflight 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-execution-preflight`, +輸出 future authorization signing execution preflight、signing execution preflight package、 +operator-held secret boundary contract、nonsecret signing inputs、command-shape preview、 +rollback boundary 與 12 項 preflight checks;目前仍是 signing-execution-preflight-only / +external-secret-reference-only / no-secret-read / no-signing / no-shell-execute / +no-SQL-execute / no-DB-write preview。production-derived batch=12 smoke 已達 +`DB_APPLY_AUTHORIZATION_SIGNING_EXECUTION_PREFLIGHT_READY`、 +`authorization_signing_execution_preflight_ready_count=1`、 +`signing_execution_preflight_check_count=12`、`signing_execution_preflight_pass_count=12`、 +`signing_execution_preflight_waiting_count=0`、 +`authorization_signing_issuer_closeout_ready_count=1`、 +`final_signable_request_package_ready_count=1`、`operator_held_secret_boundary_count=1`、 +`signing_execution_input_requirement_count=10`、`signing_execution_abort_condition_count=8`、 +`rollback_boundary_count=4`、`signs_database_apply_authorization_count=0`、 +`ready_for_database_apply_now=false`、`secret_material_included=false`、 +`secret_material_required_in_preview=false`、`reads_secret_count=0`、 +`executes_script_count=0`、`executes_sql_count=0`、`executes_migration_count=0`、 +`writes_database_count=0`。下一個 P0 是 future explicit authorization signing execution +closeout / unsigned signed-authorization receipt boundary,仍不是直接 DB apply。 +DB apply authorization signing execution closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-execution-closeout`, +輸出 future authorization signing execution closeout、unsigned signed-authorization receipt +boundary、signing execution closeout contract 與 12 項 closeout checks;目前仍是 +signing-execution-closeout-only / unsigned-receipt-boundary-only / no-secret-read / +no-signing / no-shell-execute / no-SQL-execute / no-DB-write preview。production-derived batch=12 smoke +已達 `DB_APPLY_AUTHORIZATION_SIGNING_EXECUTION_CLOSEOUT_READY`、 +`authorization_signing_execution_closeout_ready_count=1`、 +`signing_execution_closeout_check_count=12`、`signing_execution_closeout_pass_count=12`、 +`signing_execution_closeout_waiting_count=0`、 +`authorization_signing_execution_preflight_ready_count=1`、 +`unsigned_signed_authorization_receipt_boundary_count=1`、 +`operator_held_secret_boundary_count=1`、`signing_execution_input_requirement_count=10`、 +`signing_execution_abort_condition_count=8`、`rollback_boundary_count=4`、 +`post_apply_verifier_required_count=1`、`same_run_truth_required_count=1`、 +`signed_authorization_receipt_included=false`、`signature_material_included=false`、 +`signs_database_apply_authorization_count=0`、`ready_for_database_apply_now=false`、 +`secret_material_included=false`、`secret_material_required_in_preview=false`、 +`reads_secret_count=0`、`executes_script_count=0`、`executes_sql_count=0`、 +`executes_migration_count=0`、`writes_database_count=0`。下一個 P0 是 future signed +authorization receipt preflight / external signing receipt evidence boundary,仍不是直接 DB apply。 +DB apply authorization signed receipt preflight 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-preflight`, +輸出 future authorization signed receipt preflight、external signing receipt evidence +boundary、signed receipt preflight contract 與 12 項 preflight checks;目前仍是 +signed-receipt-preflight-only / external-evidence-boundary-only / no-secret-read / +no-signed-receipt-in-preview / no-signing / no-shell-execute / no-SQL-execute / +no-DB-write preview。production-derived batch=12 smoke 已達 +`DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_PREFLIGHT_READY`、 +`authorization_signed_receipt_preflight_ready_count=1`、 +`signed_receipt_preflight_check_count=12`、`signed_receipt_preflight_pass_count=12`、 +`signed_receipt_preflight_waiting_count=0`、 +`authorization_signing_execution_closeout_ready_count=1`、 +`unsigned_signed_authorization_receipt_boundary_count=1`、 +`external_signing_receipt_evidence_boundary_count=1`、 +`required_external_receipt_evidence_count=10`、`external_receipt_acceptance_gate_count=8`、 +`operator_held_secret_boundary_count=1`、`signing_execution_input_requirement_count=10`、 +`signing_execution_abort_condition_count=8`、`rollback_boundary_count=4`、 +`post_apply_verifier_required_count=1`、`same_run_truth_required_count=1`、 +`external_signed_authorization_receipt_included=false`、 +`signed_authorization_receipt_included=false`、`signature_material_included=false`、 +`signs_database_apply_authorization_count=0`、`ready_for_database_apply_now=false`、 +`secret_material_included=false`、`secret_material_required_in_preview=false`、 +`reads_secret_count=0`、`executes_script_count=0`、`executes_sql_count=0`、 +`executes_migration_count=0`、`writes_database_count=0`。下一個 P0 是 future signed +authorization receipt closeout / detached receipt verification boundary,仍不是直接 DB apply。 +DB apply authorization signed receipt closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-closeout`, +輸出 future authorization signed receipt closeout、detached receipt verification boundary、 +signed receipt closeout contract 與 12 項 closeout checks;目前仍是 +signed-receipt-closeout-only / detached-verification-boundary-only / no-secret-read / +no-signed-receipt-in-preview / no-signature-material / no-signing / no-shell-execute / +no-SQL-execute / no-DB-write preview。production-derived batch=12 smoke 已達 +`DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_CLOSEOUT_READY`、 +`authorization_signed_receipt_closeout_ready_count=1`、 +`signed_receipt_closeout_check_count=12`、`signed_receipt_closeout_pass_count=12`、 +`signed_receipt_closeout_waiting_count=0`、 +`authorization_signed_receipt_preflight_ready_count=1`、 +`external_signing_receipt_evidence_boundary_count=1`、 +`detached_receipt_verification_boundary_count=1`、 +`required_external_receipt_evidence_count=10`、`external_receipt_acceptance_gate_count=8`、 +`detached_receipt_verification_check_count=10`、 +`post_apply_verifier_required_count=1`、`same_run_truth_required_count=1`、 +`external_signed_authorization_receipt_included=false`、 +`signed_authorization_receipt_included=false`、`signature_material_included=false`、 +`detached_signature_verification_performed=false`、 +`signs_database_apply_authorization_count=0`、`ready_for_database_apply_now=false`、 +`secret_material_included=false`、`secret_material_required_in_preview=false`、 +`reads_secret_count=0`、`executes_script_count=0`、`executes_sql_count=0`、 +`executes_migration_count=0`、`writes_database_count=0`。下一個 P0 是 future signed +authorization receipt evidence intake / detached verification evidence schema,仍不是直接 DB apply。 +DB apply authorization signed receipt evidence intake 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-evidence-intake`, +輸出 future signed authorization receipt evidence intake、detached verification evidence schema、 +signed receipt evidence intake contract 與 12 項 intake checks;目前仍是 +evidence-intake-schema-only / detached-verification-schema-only / no-secret-read / +no-plaintext-secret / no-signed-receipt-body / no-signature-material / no-detached-verification-execute / +no-signing / no-shell-execute / no-SQL-execute / no-DB-write preview。production-derived +batch=12 smoke 已達 `DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_EVIDENCE_INTAKE_READY`、 +`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`、 +`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`、 +`post_apply_verifier_required_count=1`、`same_run_truth_required_count=1`、 +`external_signed_authorization_receipt_included=false`、 +`signed_authorization_receipt_included=false`、`signature_material_included=false`、 +`detached_signature_verification_performed=false`、`accepts_plaintext_secret=false`、 +`signs_database_apply_authorization_count=0`、`ready_for_database_apply_now=false`、 +`secret_material_included=false`、`secret_material_required_in_preview=false`、 +`reads_secret_count=0`、`executes_script_count=0`、`executes_sql_count=0`、 +`executes_migration_count=0`、`writes_database_count=0`。下一個 P0 是 future detached +verification evidence validation / verifier receipt closeout,仍不是直接 DB apply。 +DB apply authorization detached verification evidence validation 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-detached-verification-evidence-validation`, +輸出 future detached verification evidence validation、verifier receipt closeout boundary、 +detached verification evidence validation contract 與 12 項 validation checks;目前仍是 +validation-boundary-only / verifier-receipt-closeout-boundary-only / no-secret-read / +no-plaintext-secret / no-signed-receipt-body / no-signature-material / no-detached-verification-execute / +no-verifier-receipt-persist / no-signing / no-shell-execute / no-SQL-execute / +no-DB-write preview。production-derived batch=12 smoke 已達 +`DB_APPLY_AUTHORIZATION_DETACHED_VERIFICATION_EVIDENCE_VALIDATION_READY`、 +`authorization_detached_verification_evidence_validation_ready_count=1`、 +`detached_verification_evidence_validation_check_count=12`、 +`detached_verification_evidence_validation_pass_count=12`、 +`detached_verification_evidence_validation_waiting_count=0`、 +`authorization_signed_receipt_evidence_intake_ready_count=1`、 +`signed_receipt_evidence_intake_check_count=12`、 +`detached_verification_evidence_schema_count=1`、 +`verifier_receipt_closeout_boundary_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`、 +`verifier_receipt_field_count=12`、`verifier_receipt_acceptance_gate_count=10`、 +`post_apply_verifier_required_count=1`、`same_run_truth_required_count=1`、 +`external_signed_authorization_receipt_included=false`、 +`signed_authorization_receipt_included=false`、`signature_material_included=false`、 +`detached_signature_verification_performed=false`、`verifier_receipt_persisted=false`、 +`accepts_plaintext_secret=false`、`signs_database_apply_authorization_count=0`、 +`ready_for_database_apply_now=false`、`secret_material_included=false`、 +`reads_secret_count=0`、`executes_script_count=0`、`executes_sql_count=0`、 +`executes_migration_count=0`、`writes_database_count=0`。下一個 P0 是 future verifier +receipt closeout / verifier receipt evidence handoff,仍不是直接 DB apply。 +DB apply authorization verifier receipt closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-verifier-receipt-closeout`, +輸出 future verifier receipt closeout、verifier receipt evidence handoff、 +verifier receipt closeout contract 與 12 項 closeout checks;目前仍是 +verifier-receipt-closeout-handoff-only / evidence-handoff-only / no-secret-read / +no-plaintext-secret / no-signed-receipt-body / no-signature-material / no-detached-verification-execute / +no-verifier-receipt-persist / no-signing / no-shell-execute / no-SQL-execute / +no-DB-write preview。production-derived batch=12 smoke 已達 +`DB_APPLY_AUTHORIZATION_VERIFIER_RECEIPT_CLOSEOUT_READY`、 +`authorization_verifier_receipt_closeout_ready_count=1`、 +`verifier_receipt_closeout_check_count=12`、 +`verifier_receipt_closeout_pass_count=12`、 +`verifier_receipt_closeout_waiting_count=0`、 +`authorization_detached_verification_evidence_validation_ready_count=1`、 +`detached_verification_evidence_validation_check_count=12`、 +`authorization_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`、 +`post_apply_verifier_required_count=1`、`same_run_truth_required_count=1`、 +`external_signed_authorization_receipt_included=false`、 +`signed_authorization_receipt_included=false`、`signature_material_included=false`、 +`detached_signature_verification_performed=false`、`verifier_receipt_persisted=false`、 +`accepts_plaintext_secret=false`、`signs_database_apply_authorization_count=0`、 +`ready_for_database_apply_now=false`、`secret_material_included=false`、 +`reads_secret_count=0`、`executes_script_count=0`、`executes_sql_count=0`、 +`executes_migration_count=0`、`writes_database_count=0`。下一個 P0 是 future database +apply authorization verifier handoff / authorization evidence execution preflight,仍不是直接 DB apply。 +DB apply authorization evidence execution preflight 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-evidence-execution-preflight`, +輸出 future database apply authorization verifier handoff、authorization evidence execution +preflight package、preflight contract 與 12 項 execution preflight checks;目前仍是 +authorization-evidence-execution-preflight-only / no-secret-read / no-plaintext-secret / +no-signed-receipt-body / no-signature-material / no-detached-verification-execute / +no-verifier-receipt-persist / no-authorization-evidence-execute / no-signing / +no-shell-execute / no-endpoint-execute / no-SQL-execute / no-DB-write preview。 +production-derived batch=12 smoke 已達 +`DB_APPLY_AUTHORIZATION_EVIDENCE_EXECUTION_PREFLIGHT_READY`、 +`authorization_evidence_execution_preflight_ready_count=1`、 +`authorization_evidence_execution_preflight_check_count=12`、 +`authorization_evidence_execution_preflight_pass_count=12`、 +`authorization_evidence_execution_preflight_waiting_count=0`、 +`authorization_verifier_receipt_closeout_ready_count=1`、 +`verifier_receipt_closeout_check_count=12`、 +`authorization_detached_verification_evidence_validation_ready_count=1`、 +`detached_verification_evidence_validation_check_count=12`、 +`verifier_receipt_evidence_handoff_count=1`、 +`authorization_evidence_execution_preflight_count=1`、 +`authorization_evidence_execution_field_count=12`、 +`authorization_evidence_execution_acceptance_gate_count=10`、 +`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`、 +`required_external_receipt_evidence_count=10`、`external_receipt_acceptance_gate_count=8`、 +`post_apply_verifier_required_count=1`、`same_run_truth_required_count=1`、 +`ready_for_future_database_apply_authorization_verifier_handoff=true`、 +`can_enter_future_authorization_evidence_execution_closeout=true`、 +`ready_for_future_authorization_evidence_execution_preflight=true`、 +`external_signed_authorization_receipt_included=false`、 +`signed_authorization_receipt_included=false`、`signature_material_included=false`、 +`detached_signature_verification_performed=false`、`verifier_receipt_persisted=false`、 +`executes_authorization_evidence=false`、`accepts_plaintext_secret=false`、 +`signs_database_apply_authorization_count=0`、`ready_for_database_apply_now=false`、 +`secret_material_included=false`、`reads_secret_count=0`、`executes_script_count=0`、 +`executes_endpoint_count=0`、`executes_sql_count=0`、`executes_migration_count=0`、 +`writes_database_count=0`。下一個 P0 是 future authorization evidence execution closeout / +database apply authorization final verifier gate,仍不是直接 DB apply。 +DB apply authorization evidence execution closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-evidence-execution-closeout`, +輸出 future database apply authorization final verifier gate、authorization evidence execution +closeout package、closeout contract 與 12 項 closeout checks;目前仍是 +authorization-evidence-execution-closeout-final-verifier-gate-only / no-secret-read / +no-plaintext-secret / no-signed-receipt-body / no-signature-material / +no-detached-verification-execute / no-verifier-receipt-persist / +no-authorization-evidence-execute / no-database-apply-execute / no-signing / +no-shell-execute / no-endpoint-execute / no-SQL-execute / no-DB-write preview。 +production-derived batch=12 smoke 已達 +`DB_APPLY_AUTHORIZATION_EVIDENCE_EXECUTION_CLOSEOUT_READY`、 +`authorization_evidence_execution_closeout_ready_count=1`、 +`authorization_evidence_execution_closeout_check_count=12`、 +`authorization_evidence_execution_closeout_pass_count=12`、 +`authorization_evidence_execution_closeout_waiting_count=0`、 +`authorization_evidence_execution_preflight_ready_count=1`、 +`authorization_evidence_execution_preflight_check_count=12`、 +`authorization_verifier_receipt_closeout_ready_count=1`、 +`verifier_receipt_closeout_check_count=12`、 +`authorization_detached_verification_evidence_validation_ready_count=1`、 +`detached_verification_evidence_validation_check_count=12`、 +`verifier_receipt_evidence_handoff_count=1`、 +`authorization_evidence_execution_preflight_count=1`、 +`authorization_evidence_execution_closeout_count=1`、 +`database_apply_final_verifier_gate_count=1`、 +`database_apply_authorization_final_verifier_gate_ready_count=1`、 +`authorization_evidence_execution_closeout_field_count=12`、 +`authorization_evidence_execution_closeout_acceptance_gate_count=10`、 +`authorization_evidence_execution_field_count=12`、 +`authorization_evidence_execution_acceptance_gate_count=10`、 +`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`、 +`required_external_receipt_evidence_count=10`、`external_receipt_acceptance_gate_count=8`、 +`post_apply_verifier_required_count=1`、`same_run_truth_required_count=1`、 +`ready_for_future_database_apply_authorization_final_verifier_gate=true`、 +`can_enter_future_database_apply_controlled_apply_final_preflight=true`、 +`ready_for_future_authorization_evidence_execution_closeout=true`、 +`external_signed_authorization_receipt_included=false`、 +`signed_authorization_receipt_included=false`、`signature_material_included=false`、 +`detached_signature_verification_performed=false`、`verifier_receipt_persisted=false`、 +`executes_authorization_evidence=false`、`executes_database_apply=false`、 +`database_apply_authorized=false`、`accepts_plaintext_secret=false`、 +`signs_database_apply_authorization_count=0`、`ready_for_database_apply_now=false`、 +`secret_material_included=false`、`reads_secret_count=0`、`executes_script_count=0`、 +`executes_endpoint_count=0`、`executes_sql_count=0`、`executes_migration_count=0`、 +`writes_database_count=0`。下一個 P0 是 future database apply controlled-apply final +preflight / rollback + post-apply verifier binding,仍不是直接 DB apply。 +DB apply controlled-apply final preflight 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-apply-final-preflight`, +輸出 future database apply controlled-apply final preflight、rollback binding、 +post-apply verifier binding、controlled apply final preflight contract 與 12 項 final +preflight checks;目前仍是 controlled-apply-final-preflight-rollback-and-verifier-binding-only / +dry-run-only / check-mode-only / no-secret-read / no-plaintext-secret / +no-signature-material / no-authorization-evidence-execute / no-database-apply-execute / +no-endpoint-execute / no-SQL-execute / no-DB-write preview。production-derived batch=12 +smoke 已達 `DB_APPLY_CONTROLLED_APPLY_FINAL_PREFLIGHT_READY`、 +`controlled_apply_final_preflight_ready_count=1`、 +`controlled_apply_final_preflight_check_count=12`、 +`controlled_apply_final_preflight_pass_count=12`、 +`controlled_apply_final_preflight_waiting_count=0`、 +`authorization_evidence_execution_closeout_ready_count=1`、 +`authorization_evidence_execution_closeout_check_count=12`、 +`authorization_evidence_execution_preflight_ready_count=1`、 +`authorization_evidence_execution_preflight_check_count=12`、 +`authorization_verifier_receipt_closeout_ready_count=1`、 +`verifier_receipt_closeout_check_count=12`、 +`database_apply_final_verifier_gate_count=1`、 +`database_apply_authorization_final_verifier_gate_ready_count=1`、 +`controlled_apply_final_preflight_count=1`、 +`controlled_apply_final_preflight_field_count=12`、 +`controlled_apply_final_preflight_acceptance_gate_count=10`、 +`rollback_binding_count=1`、`rollback_binding_field_count=8`、 +`post_apply_verifier_binding_count=1`、`post_apply_verifier_binding_field_count=8`、 +`authorization_evidence_execution_closeout_field_count=12`、 +`authorization_evidence_execution_closeout_acceptance_gate_count=10`、 +`post_apply_verifier_required_count=1`、`same_run_truth_required_count=1`、 +`ready_for_future_database_apply_controlled_apply_final_preflight=true`、 +`can_enter_future_database_apply_controlled_dry_run_package=true`、 +`dry_run_only=true`、`check_mode_only=true`、`rollback_bound=true`、 +`post_apply_verifier_bound=true`、`database_apply_authorized=false`、 +`executes_authorization_evidence=false`、`executes_database_apply=false`、 +`executes_endpoint_count=0`、`executes_sql_count=0`、`writes_database_count=0`、 +`reads_secret_count=0`、`signs_database_apply_authorization_count=0`。下一個 P0 是 +future database apply controlled dry-run package / dry-run execution receipt,仍不是直接 DB apply。 +DB apply controlled dry-run package 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-package`, +輸出 future database apply controlled dry-run execution receipt、controlled dry-run +package、dry-run command shape、dry-run execution receipt preview 與 12 項 dry-run +package checks;目前仍是 controlled-dry-run-package-and-receipt-preview-only / +dry-run-only / check-mode-only / non-executable command shape / receipt-preview-only / +no-secret-read / no-plaintext-secret / no-signature-material / +no-authorization-evidence-execute / no-database-apply-execute / no-endpoint-execute / +no-SQL-execute / no-DB-write preview。production-derived batch=12 smoke 已達 +`DB_APPLY_CONTROLLED_DRY_RUN_PACKAGE_READY`、 +`controlled_dry_run_package_ready_count=1`、 +`controlled_dry_run_package_check_count=12`、 +`controlled_dry_run_package_pass_count=12`、 +`controlled_dry_run_package_waiting_count=0`、 +`controlled_apply_final_preflight_ready_count=1`、 +`controlled_apply_final_preflight_check_count=12`、 +`authorization_evidence_execution_closeout_ready_count=1`、 +`authorization_evidence_execution_closeout_check_count=12`、 +`authorization_evidence_execution_preflight_ready_count=1`、 +`authorization_evidence_execution_preflight_check_count=12`、 +`authorization_verifier_receipt_closeout_ready_count=1`、 +`verifier_receipt_closeout_check_count=12`、 +`database_apply_final_verifier_gate_count=1`、 +`database_apply_authorization_final_verifier_gate_ready_count=1`、 +`controlled_dry_run_package_count=1`、 +`controlled_dry_run_package_field_count=12`、 +`controlled_dry_run_acceptance_gate_count=10`、 +`dry_run_execution_receipt_preview_count=1`、 +`dry_run_execution_receipt_field_count=8`、 +`controlled_apply_final_preflight_count=1`、 +`rollback_binding_count=1`、`post_apply_verifier_binding_count=1`、 +`post_apply_verifier_required_count=1`、`same_run_truth_required_count=1`、 +`ready_for_future_database_apply_controlled_dry_run_execution_receipt=true`、 +`can_enter_future_database_apply_controlled_dry_run_receipt_closeout=true`、 +`execution_performed=false`、`database_apply_authorized=false`、 +`executes_authorization_evidence=false`、`executes_database_apply=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`reads_secret_count=0`、`executes_endpoint_count=0`、`executes_sql_count=0`、 +`writes_database_count=0`、`signs_database_apply_authorization_count=0`。下一個 P0 +是 future database apply controlled dry-run receipt closeout / dry-run result parser +verification,仍不是直接 DB apply。 +DB apply controlled dry-run receipt closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-receipt-closeout`, +輸出 future database apply controlled dry-run result parser verification、controlled +dry-run receipt closeout、dry-run result parser、receipt validation report 與 12 項 +receipt closeout checks;目前仍是 +controlled-dry-run-receipt-closeout-and-result-parser-verification-only / +receipt-preview-only / parser-schema-only / dry-run-only / check-mode-only / +no-secret-read / no-plaintext-secret / no-signature-material / +no-authorization-evidence-execute / no-database-apply-execute / no-endpoint-execute / +no-SQL-execute / no-DB-write preview。production-derived batch=12 smoke 已達 +`DB_APPLY_CONTROLLED_DRY_RUN_RECEIPT_CLOSEOUT_READY`、 +`controlled_dry_run_receipt_closeout_ready_count=1`、 +`controlled_dry_run_receipt_closeout_check_count=12`、 +`controlled_dry_run_receipt_closeout_pass_count=12`、 +`controlled_dry_run_receipt_closeout_waiting_count=0`、 +`controlled_dry_run_package_ready_count=1`、 +`controlled_dry_run_package_check_count=12`、 +`controlled_apply_final_preflight_ready_count=1`、 +`controlled_apply_final_preflight_check_count=12`、 +`authorization_evidence_execution_closeout_ready_count=1`、 +`authorization_evidence_execution_closeout_check_count=12`、 +`authorization_evidence_execution_preflight_ready_count=1`、 +`authorization_evidence_execution_preflight_check_count=12`、 +`authorization_verifier_receipt_closeout_ready_count=1`、 +`verifier_receipt_closeout_check_count=12`、 +`database_apply_final_verifier_gate_count=1`、 +`database_apply_authorization_final_verifier_gate_ready_count=1`、 +`controlled_dry_run_receipt_closeout_count=1`、 +`controlled_dry_run_receipt_closeout_field_count=12`、 +`controlled_dry_run_receipt_closeout_acceptance_gate_count=10`、 +`dry_run_result_parser_count=1`、`dry_run_result_parser_field_count=10`、 +`receipt_validation_report_count=1`、`receipt_validation_field_count=8`、 +`ready_for_future_database_apply_controlled_dry_run_result_parser_verification=true`、 +`can_enter_future_database_apply_controlled_dry_run_runner_readiness=true`、 +`receipt_validation_status=preview_validated_not_executed`、 +`parser_execution_required=false`、`execution_performed=false`、 +`database_apply_authorized=false`、`executes_endpoint=false`、`executes_sql=false`、 +`writes_database=false`、`reads_secret_count=0`、`executes_endpoint_count=0`、 +`executes_sql_count=0`、`writes_database_count=0`、 +`signs_database_apply_authorization_count=0`。下一個 P0 是 future database apply +controlled dry-run runner readiness / execution plan binding,仍不是直接 DB apply。 +DB apply controlled dry-run runner readiness 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-readiness`, +輸出 future database apply controlled dry-run execution plan binding、controlled +dry-run runner readiness、execution plan binding 與 12 項 runner readiness checks; +目前仍是 controlled-dry-run-runner-readiness-and-execution-plan-binding-only / +execution-plan-preview-only / runner-readiness-only / dry-run-only / +check-mode-only / no-secret-read / no-plaintext-secret / no-signature-material / +no-authorization-evidence-execute / no-database-apply-execute / no-endpoint-execute / +no-SQL-execute / no-DB-write preview。production-derived batch=12 smoke 已達 +`DB_APPLY_CONTROLLED_DRY_RUN_RUNNER_READINESS_READY`、 +`controlled_dry_run_runner_readiness_ready_count=1`、 +`controlled_dry_run_runner_readiness_check_count=12`、 +`controlled_dry_run_runner_readiness_pass_count=12`、 +`controlled_dry_run_runner_readiness_waiting_count=0`、 +`controlled_dry_run_receipt_closeout_ready_count=1`、 +`controlled_dry_run_receipt_closeout_check_count=12`、 +`controlled_dry_run_package_ready_count=1`、 +`controlled_dry_run_package_check_count=12`、 +`controlled_apply_final_preflight_ready_count=1`、 +`controlled_apply_final_preflight_check_count=12`、 +`authorization_evidence_execution_closeout_ready_count=1`、 +`authorization_evidence_execution_closeout_check_count=12`、 +`authorization_evidence_execution_preflight_ready_count=1`、 +`authorization_evidence_execution_preflight_check_count=12`、 +`database_apply_final_verifier_gate_count=1`、 +`database_apply_authorization_final_verifier_gate_ready_count=1`、 +`controlled_dry_run_runner_readiness_count=1`、 +`controlled_dry_run_runner_readiness_field_count=12`、 +`controlled_dry_run_runner_readiness_acceptance_gate_count=10`、 +`execution_plan_binding_count=1`、`execution_plan_binding_field_count=12`、 +`ready_for_future_database_apply_controlled_dry_run_execution_plan_binding=true`、 +`can_enter_future_database_apply_controlled_dry_run_execution_plan_closeout=true`、 +`execution_plan_bound=true`、`runner_execution_authorized=false`、 +`dry_run_execution_authorized=false`、`execution_authorized=false`、 +`database_apply_authorized=false`、`executes_endpoint=false`、`executes_sql=false`、 +`writes_database=false`、`reads_secret_count=0`、`executes_endpoint_count=0`、 +`executes_sql_count=0`、`writes_database_count=0`、 +`signs_database_apply_authorization_count=0`。下一個 P0 是 future database apply +controlled dry-run execution plan closeout / non-executable command artifact +verification,仍不是直接 DB apply。 +DB apply controlled dry-run execution plan closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-plan-closeout`, +輸出 future database apply controlled dry-run command artifact verification、 +controlled dry-run execution plan closeout、non-executable command artifact 與 12 +項 execution plan closeout checks;目前仍是 +controlled-dry-run-execution-plan-closeout-and-non-executable-command-artifact-verification-only / +execution-plan-closeout-only / non-executable-command-artifact-only / dry-run-only / +check-mode-only / no-secret-read / no-plaintext-secret / no-command-text / +no-argv / no-signature-material / no-authorization-evidence-execute / +no-database-apply-execute / no-endpoint-execute / no-SQL-execute / +no-DB-write preview。production-derived batch=12 smoke 已達 +`DB_APPLY_CONTROLLED_DRY_RUN_EXECUTION_PLAN_CLOSEOUT_READY`、 +`controlled_dry_run_execution_plan_closeout_ready_count=1`、 +`controlled_dry_run_execution_plan_closeout_check_count=12`、 +`controlled_dry_run_execution_plan_closeout_pass_count=12`、 +`controlled_dry_run_execution_plan_closeout_waiting_count=0`、 +`controlled_dry_run_runner_readiness_ready_count=1`、 +`controlled_dry_run_runner_readiness_check_count=12`、 +`controlled_dry_run_receipt_closeout_ready_count=1`、 +`controlled_dry_run_receipt_closeout_check_count=12`、 +`controlled_dry_run_package_ready_count=1`、 +`controlled_dry_run_package_check_count=12`、 +`controlled_apply_final_preflight_ready_count=1`、 +`controlled_apply_final_preflight_check_count=12`、 +`authorization_evidence_execution_closeout_ready_count=1`、 +`authorization_evidence_execution_closeout_check_count=12`、 +`authorization_evidence_execution_preflight_ready_count=1`、 +`authorization_evidence_execution_preflight_check_count=12`、 +`database_apply_final_verifier_gate_count=1`、 +`database_apply_authorization_final_verifier_gate_ready_count=1`、 +`controlled_dry_run_execution_plan_closeout_count=1`、 +`controlled_dry_run_execution_plan_closeout_field_count=12`、 +`controlled_dry_run_execution_plan_closeout_acceptance_gate_count=10`、 +`non_executable_command_artifact_count=1`、 +`non_executable_command_artifact_field_count=10`、 +`execution_plan_binding_count=1`、`execution_plan_binding_field_count=12`、 +`ready_for_future_database_apply_controlled_dry_run_command_artifact_verification=true`、 +`can_enter_future_database_apply_controlled_dry_run_command_artifact_closeout=true`、 +`execution_plan_closeout_ready=true`、 +`non_executable_command_artifact_verified=true`、 +`runner_execution_authorized=false`、`dry_run_execution_authorized=false`、 +`execution_authorized=false`、`database_apply_authorized=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`reads_secret_count=0`、`executes_endpoint_count=0`、`executes_sql_count=0`、 +`writes_database_count=0`、`signs_database_apply_authorization_count=0`。下一個 +P0 是 future database apply controlled dry-run command artifact closeout / +runner execution receipt preflight,仍不是直接 DB apply。 +DB apply controlled dry-run command artifact closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-command-artifact-closeout`, +輸出 future database apply controlled dry-run runner execution receipt preflight、 +controlled dry-run command artifact closeout、runner execution receipt preflight 與 +12 項 command artifact closeout checks;目前仍是 +controlled-dry-run-command-artifact-closeout-and-runner-execution-receipt-preflight-only / +command-artifact-closeout-only / runner-execution-receipt-preflight-only / +dry-run-only / check-mode-only / no-secret-read / no-plaintext-secret / +no-command-text / no-argv / no-stdout-capture / no-stderr-capture / +no-signature-material / no-authorization-evidence-execute / +no-database-apply-execute / no-endpoint-execute / no-SQL-execute / +no-DB-write preview。local deterministic fake-fetch smoke 已達 +`DB_APPLY_CONTROLLED_DRY_RUN_COMMAND_ARTIFACT_CLOSEOUT_READY`、 +`controlled_dry_run_command_artifact_closeout_ready_count=1`、 +`controlled_dry_run_command_artifact_closeout_check_count=12`、 +`controlled_dry_run_command_artifact_closeout_pass_count=12`、 +`controlled_dry_run_command_artifact_closeout_waiting_count=0`、 +`controlled_dry_run_execution_plan_closeout_ready_count=1`、 +`controlled_dry_run_execution_plan_closeout_check_count=12`、 +`controlled_dry_run_runner_readiness_ready_count=1`、 +`controlled_dry_run_runner_readiness_check_count=12`、 +`controlled_dry_run_receipt_closeout_ready_count=1`、 +`controlled_dry_run_receipt_closeout_check_count=12`、 +`controlled_dry_run_package_ready_count=1`、 +`controlled_dry_run_package_check_count=12`、 +`controlled_apply_final_preflight_ready_count=1`、 +`controlled_apply_final_preflight_check_count=12`、 +`authorization_evidence_execution_closeout_ready_count=1`、 +`authorization_evidence_execution_closeout_check_count=12`、 +`authorization_evidence_execution_preflight_ready_count=1`、 +`authorization_evidence_execution_preflight_check_count=12`、 +`database_apply_final_verifier_gate_count=1`、 +`database_apply_authorization_final_verifier_gate_ready_count=1`、 +`controlled_dry_run_command_artifact_closeout_count=1`、 +`controlled_dry_run_command_artifact_closeout_field_count=12`、 +`controlled_dry_run_command_artifact_closeout_acceptance_gate_count=10`、 +`runner_execution_receipt_preflight_count=1`、 +`runner_execution_receipt_preflight_field_count=10`、 +`ready_for_future_database_apply_controlled_dry_run_runner_execution_receipt_preflight=true`、 +`can_enter_future_database_apply_controlled_dry_run_runner_execution_receipt_closeout=true`、 +`command_artifact_closeout_ready=true`、 +`runner_execution_receipt_preflight_bound=true`、 +`runner_execution_authorized=false`、`dry_run_execution_authorized=false`、 +`execution_authorized=false`、`stdout_capture_allowed=false`、 +`stderr_capture_allowed=false`、`database_apply_authorized=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`reads_secret_count=0`、`executes_endpoint_count=0`、`executes_sql_count=0`、 +`writes_database_count=0`、`signs_database_apply_authorization_count=0`。本輪 +production truth / production-derived smoke 因本機網路 `Network is unreachable` +暫時 blocked,待網路恢復後必須重跑同層 production-derived smoke。下一個 P0 是 +future database apply controlled dry-run runner execution receipt closeout / +post-receipt parser verification,仍不是直接 DB apply。 +DB apply controlled dry-run runner execution receipt closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-execution-receipt-closeout`, +輸出 future database apply controlled dry-run post-receipt parser verification、 +controlled dry-run runner execution receipt closeout、receipt closeout preview、 +post-receipt parser verification 與 12 項 runner execution receipt closeout checks; +目前仍是 +controlled-dry-run-runner-execution-receipt-closeout-and-post-receipt-parser-verification-only / +runner-execution-receipt-closeout-only / post-receipt-parser-verification-only / +receipt-closeout-preview-not-executed / dry-run-only / check-mode-only / +no-secret-read / no-plaintext-secret / no-stdout-included / no-stderr-included / +no-stdout-capture / no-stderr-capture / no-signature-material / +no-authorization-evidence-execute / no-database-apply-execute / +no-endpoint-execute / no-SQL-execute / no-DB-write preview。production version truth +已重新通過 `healthy postgresql V10.725`;local deterministic fake-fetch smoke 已達 +`DB_APPLY_CONTROLLED_DRY_RUN_RUNNER_EXECUTION_RECEIPT_CLOSEOUT_READY`、 +`controlled_dry_run_runner_execution_receipt_closeout_ready_count=1`、 +`controlled_dry_run_runner_execution_receipt_closeout_check_count=12`、 +`controlled_dry_run_runner_execution_receipt_closeout_pass_count=12`、 +`controlled_dry_run_runner_execution_receipt_closeout_waiting_count=0`、 +`controlled_dry_run_command_artifact_closeout_ready_count=1`、 +`controlled_dry_run_command_artifact_closeout_check_count=12`、 +`controlled_dry_run_execution_plan_closeout_ready_count=1`、 +`controlled_dry_run_execution_plan_closeout_check_count=12`、 +`controlled_dry_run_runner_readiness_ready_count=1`、 +`controlled_dry_run_runner_readiness_check_count=12`、 +`controlled_dry_run_receipt_closeout_ready_count=1`、 +`controlled_dry_run_receipt_closeout_check_count=12`、 +`controlled_dry_run_package_ready_count=1`、 +`controlled_dry_run_package_check_count=12`、 +`controlled_apply_final_preflight_ready_count=1`、 +`controlled_apply_final_preflight_check_count=12`、 +`authorization_evidence_execution_closeout_ready_count=1`、 +`authorization_evidence_execution_closeout_check_count=12`、 +`authorization_evidence_execution_preflight_ready_count=1`、 +`authorization_evidence_execution_preflight_check_count=12`、 +`database_apply_final_verifier_gate_count=1`、 +`database_apply_authorization_final_verifier_gate_ready_count=1`、 +`controlled_dry_run_runner_execution_receipt_closeout_count=1`、 +`controlled_dry_run_runner_execution_receipt_closeout_field_count=12`、 +`controlled_dry_run_runner_execution_receipt_closeout_acceptance_gate_count=10`、 +`post_receipt_parser_verification_count=1`、 +`post_receipt_parser_verification_field_count=10`、 +`receipt_closeout_preview_count=1`、 +`ready_for_future_database_apply_controlled_dry_run_post_receipt_parser_verification=true`、 +`can_enter_future_database_apply_controlled_dry_run_post_receipt_parser_closeout=true`、 +`runner_execution_receipt_closeout_ready=true`、 +`post_receipt_parser_verification_bound=true`、 +`runner_execution_authorized=false`、`dry_run_execution_authorized=false`、 +`execution_authorized=false`、`stdout_capture_allowed=false`、 +`stderr_capture_allowed=false`、`database_apply_authorized=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`reads_secret_count=0`、`executes_endpoint_count=0`、`executes_sql_count=0`、 +`writes_database_count=0`、`signs_database_apply_authorization_count=0`。後續 P0 已接續到 +future database apply controlled dry-run post-receipt parser closeout / +no-apply enforcement verification;仍不是直接 DB apply。 +DB apply controlled dry-run post-receipt parser closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-post-receipt-parser-closeout`, +輸出 future database apply controlled dry-run no-apply enforcement verification、 +controlled dry-run post-receipt parser closeout、no-apply enforcement verification +與 12 項 post-receipt parser closeout checks;目前仍是 +controlled-dry-run-post-receipt-parser-closeout-and-no-apply-enforcement-only / +post-receipt-parser-closeout-only / no-apply-enforcement-verification-only / +dry-run-only / check-mode-only / no-secret-read / no-plaintext-secret / +no-stdout-capture / no-stderr-capture / no-signature-material / +no-authorization-evidence-execute / no-database-apply-execute / +no-endpoint-execute / no-SQL-execute / no-DB-write preview。local deterministic +fake-fetch smoke 已達 +`DB_APPLY_CONTROLLED_DRY_RUN_POST_RECEIPT_PARSER_CLOSEOUT_READY`、 +`controlled_dry_run_post_receipt_parser_closeout_ready_count=1`、 +`controlled_dry_run_post_receipt_parser_closeout_check_count=12`、 +`controlled_dry_run_post_receipt_parser_closeout_pass_count=12`、 +`controlled_dry_run_post_receipt_parser_closeout_waiting_count=0`、 +`controlled_dry_run_runner_execution_receipt_closeout_ready_count=1`、 +`controlled_dry_run_runner_execution_receipt_closeout_check_count=12`、 +`controlled_dry_run_command_artifact_closeout_ready_count=1`、 +`controlled_dry_run_execution_plan_closeout_ready_count=1`、 +`controlled_dry_run_runner_readiness_ready_count=1`、 +`controlled_dry_run_receipt_closeout_ready_count=1`、 +`controlled_dry_run_package_ready_count=1`、 +`controlled_apply_final_preflight_ready_count=1`、 +`no_apply_enforcement_verification_count=1`、 +`no_apply_enforcement_verification_field_count=12`、 +`ready_for_future_database_apply_controlled_dry_run_no_apply_enforcement_verification=true`、 +`can_enter_future_database_apply_controlled_dry_run_no_apply_enforcement_closeout=true`、 +`post_receipt_parser_closeout_ready=true`、 +`no_apply_enforcement_verification_bound=true`、 +`endpoint_execution_allowed=false`、`sql_execution_allowed=false`、 +`database_write_allowed=false`、`database_apply_authorized=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`reads_secret_count=0`、`executes_endpoint_count=0`、`executes_sql_count=0`、 +`writes_database_count=0`、`signs_database_apply_authorization_count=0`。後續 P0 已接續到 +future database apply controlled dry-run no-apply enforcement closeout / +final dry-run executor guard;仍不是直接 DB apply。 +DB apply controlled dry-run no-apply enforcement closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-apply-enforcement-closeout`, +輸出 future database apply controlled dry-run final dry-run executor guard、 +controlled dry-run no-apply enforcement closeout、final dry-run executor guard +與 12 項 no-apply enforcement closeout checks;目前仍是 +controlled-dry-run-no-apply-enforcement-closeout-and-final-executor-guard-only / +no-apply-enforcement-closeout-only / final-dry-run-executor-guard-only / +dry-run-only / check-mode-only / no-secret-read / no-plaintext-secret / +no-stdout-capture / no-stderr-capture / no-signature-material / +no-authorization-evidence-execute / no-database-apply-execute / +no-endpoint-execute / no-SQL-execute / no-DB-write preview。local deterministic +fake-fetch smoke 已達 +`DB_APPLY_CONTROLLED_DRY_RUN_NO_APPLY_ENFORCEMENT_CLOSEOUT_READY`、 +`controlled_dry_run_no_apply_enforcement_closeout_ready_count=1`、 +`controlled_dry_run_no_apply_enforcement_closeout_check_count=12`、 +`controlled_dry_run_no_apply_enforcement_closeout_pass_count=12`、 +`controlled_dry_run_no_apply_enforcement_closeout_waiting_count=0`、 +`controlled_dry_run_post_receipt_parser_closeout_ready_count=1`、 +`controlled_dry_run_post_receipt_parser_closeout_check_count=12`、 +`controlled_dry_run_runner_execution_receipt_closeout_ready_count=1`、 +`controlled_dry_run_runner_execution_receipt_closeout_check_count=12`、 +`controlled_dry_run_command_artifact_closeout_ready_count=1`、 +`controlled_dry_run_execution_plan_closeout_ready_count=1`、 +`controlled_dry_run_runner_readiness_ready_count=1`、 +`controlled_dry_run_receipt_closeout_ready_count=1`、 +`controlled_dry_run_package_ready_count=1`、 +`controlled_apply_final_preflight_ready_count=1`、 +`final_dry_run_executor_guard_count=1`、 +`final_dry_run_executor_guard_field_count=12`、 +`ready_for_future_database_apply_controlled_dry_run_final_dry_run_executor_guard=true`、 +`can_enter_future_database_apply_controlled_dry_run_final_executor_guard_closeout=true`、 +`no_apply_enforcement_closeout_ready=true`、 +`final_dry_run_executor_guard_bound=true`、 +`dry_run_executor_invocation_allowed=false`、 +`endpoint_execution_allowed=false`、`sql_execution_allowed=false`、 +`database_write_allowed=false`、`database_apply_authorized=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`reads_secret_count=0`、`executes_endpoint_count=0`、`executes_sql_count=0`、 +`writes_database_count=0`、`signs_database_apply_authorization_count=0`。後續 P0 已接續到 +future database apply controlled dry-run final executor guard closeout / +pre-apply replay verifier;仍不是直接 DB apply。 +DB apply controlled dry-run final executor guard closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-final-executor-guard-closeout`, +輸出 future database apply controlled dry-run pre-apply replay verifier、 +controlled dry-run final executor guard closeout、pre-apply replay verifier +與 12 項 final executor guard closeout checks;目前仍是 +controlled-dry-run-final-executor-guard-closeout-and-pre-apply-replay-verifier-only / +final-executor-guard-closeout-only / pre-apply-replay-verifier-only / +dry-run-only / check-mode-only / no-secret-read / no-plaintext-secret / +no-stdout-capture / no-stderr-capture / no-signature-material / +no-authorization-evidence-execute / no-database-apply-execute / +no-endpoint-execute / no-SQL-execute / no-DB-write preview。local deterministic +fake-fetch smoke 已達 +`DB_APPLY_CONTROLLED_DRY_RUN_FINAL_EXECUTOR_GUARD_CLOSEOUT_READY`、 +`controlled_dry_run_final_executor_guard_closeout_ready_count=1`、 +`controlled_dry_run_final_executor_guard_closeout_check_count=12`、 +`controlled_dry_run_final_executor_guard_closeout_pass_count=12`、 +`controlled_dry_run_final_executor_guard_closeout_waiting_count=0`、 +`controlled_dry_run_no_apply_enforcement_closeout_ready_count=1`、 +`controlled_dry_run_no_apply_enforcement_closeout_check_count=12`、 +`controlled_dry_run_post_receipt_parser_closeout_ready_count=1`、 +`controlled_dry_run_runner_execution_receipt_closeout_ready_count=1`、 +`controlled_dry_run_command_artifact_closeout_ready_count=1`、 +`controlled_dry_run_execution_plan_closeout_ready_count=1`、 +`controlled_dry_run_runner_readiness_ready_count=1`、 +`controlled_dry_run_receipt_closeout_ready_count=1`、 +`controlled_dry_run_package_ready_count=1`、 +`controlled_apply_final_preflight_ready_count=1`、 +`pre_apply_replay_verifier_count=1`、 +`pre_apply_replay_verifier_field_count=12`、 +`ready_for_future_database_apply_controlled_dry_run_pre_apply_replay_verifier=true`、 +`can_enter_future_database_apply_controlled_dry_run_pre_apply_replay_closeout=true`、 +`final_executor_guard_closeout_ready=true`、 +`pre_apply_replay_verifier_bound=true`、 +`dry_run_executor_invocation_allowed=false`、 +`endpoint_execution_allowed=false`、`sql_execution_allowed=false`、 +`database_write_allowed=false`、`database_apply_authorized=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`reads_secret_count=0`、`executes_endpoint_count=0`、`executes_sql_count=0`、 +`writes_database_count=0`、`signs_database_apply_authorization_count=0`。下一個 P0 +是 future database apply controlled dry-run pre-apply replay closeout / +apply executor readiness contract,仍不是直接 DB apply。 +DB apply controlled dry-run pre-apply replay closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-pre-apply-replay-closeout`, +輸出 future database apply controlled dry-run apply executor readiness contract、 +controlled dry-run pre-apply replay closeout、apply executor readiness contract +與 12 項 pre-apply replay closeout checks;目前仍是 +controlled-dry-run-pre-apply-replay-closeout-and-apply-executor-readiness-contract-only / +pre-apply-replay-closeout-only / apply-executor-readiness-contract-only / +dry-run-only / check-mode-only / no-secret-read / no-plaintext-secret / +no-stdout-capture / no-stderr-capture / no-signature-material / +no-authorization-evidence-execute / no-database-apply-execute / +no-dry-run-executor-invocation / no-endpoint-execute / no-SQL-execute / +no-DB-write preview。local deterministic fake-fetch smoke 目標為 +`DB_APPLY_CONTROLLED_DRY_RUN_PRE_APPLY_REPLAY_CLOSEOUT_READY`、 +`controlled_dry_run_pre_apply_replay_closeout_ready_count=1`、 +`controlled_dry_run_pre_apply_replay_closeout_check_count=12`、 +`controlled_dry_run_pre_apply_replay_closeout_pass_count=12`、 +`controlled_dry_run_pre_apply_replay_closeout_waiting_count=0`、 +`controlled_dry_run_final_executor_guard_closeout_ready_count=1`、 +`controlled_dry_run_no_apply_enforcement_closeout_ready_count=1`、 +`controlled_dry_run_post_receipt_parser_closeout_ready_count=1`、 +`controlled_dry_run_runner_execution_receipt_closeout_ready_count=1`、 +`controlled_dry_run_command_artifact_closeout_ready_count=1`、 +`controlled_dry_run_execution_plan_closeout_ready_count=1`、 +`controlled_dry_run_runner_readiness_ready_count=1`、 +`controlled_dry_run_receipt_closeout_ready_count=1`、 +`controlled_dry_run_package_ready_count=1`、 +`controlled_apply_final_preflight_ready_count=1`、 +`apply_executor_readiness_contract_count=1`、 +`apply_executor_readiness_contract_field_count=12`、 +`ready_for_future_database_apply_controlled_dry_run_apply_executor_readiness_contract=true`、 +`can_enter_future_database_apply_controlled_dry_run_apply_executor_readiness_closeout=true`、 +`pre_apply_replay_closeout_ready=true`、 +`apply_executor_readiness_contract_bound=true`、 +`dry_run_executor_invocation_allowed=false`、 +`endpoint_execution_allowed=false`、`sql_execution_allowed=false`、 +`database_write_allowed=false`、`database_apply_authorized=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`reads_secret_count=0`、`executes_endpoint_count=0`、`executes_sql_count=0`、 +`writes_database_count=0`、`signs_database_apply_authorization_count=0`。下一個 P0 +是 future database apply controlled dry-run apply executor readiness closeout / +dry-run invocation readiness receipt,仍不是直接 DB apply。 +DB apply controlled dry-run apply executor readiness closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-apply-executor-readiness-closeout`, +輸出 future database apply controlled dry-run invocation readiness receipt、 +controlled dry-run apply executor readiness closeout、dry-run invocation readiness receipt +與 12 項 apply executor readiness closeout checks;目前仍是 +controlled-dry-run-apply-executor-readiness-closeout-and-dry-run-invocation-readiness-receipt-only / +apply-executor-readiness-closeout-only / dry-run-invocation-readiness-receipt-only / +dry-run-only / check-mode-only / no-secret-read / no-plaintext-secret / +no-stdout-capture / no-stderr-capture / no-signature-material / +no-authorization-evidence-execute / no-database-apply-execute / +no-dry-run-executor-invocation / no-endpoint-execute / no-SQL-execute / +no-DB-write preview。local deterministic fake-fetch smoke 目標為 +`DB_APPLY_CONTROLLED_DRY_RUN_APPLY_EXECUTOR_READINESS_CLOSEOUT_READY`、 +`controlled_dry_run_apply_executor_readiness_closeout_ready_count=1`、 +`controlled_dry_run_apply_executor_readiness_closeout_check_count=12`、 +`controlled_dry_run_apply_executor_readiness_closeout_pass_count=12`、 +`controlled_dry_run_apply_executor_readiness_closeout_waiting_count=0`、 +`controlled_dry_run_pre_apply_replay_closeout_ready_count=1`、 +`controlled_dry_run_final_executor_guard_closeout_ready_count=1`、 +`controlled_dry_run_no_apply_enforcement_closeout_ready_count=1`、 +`controlled_dry_run_post_receipt_parser_closeout_ready_count=1`、 +`controlled_dry_run_runner_execution_receipt_closeout_ready_count=1`、 +`controlled_dry_run_command_artifact_closeout_ready_count=1`、 +`controlled_dry_run_execution_plan_closeout_ready_count=1`、 +`controlled_dry_run_runner_readiness_ready_count=1`、 +`controlled_dry_run_receipt_closeout_ready_count=1`、 +`controlled_dry_run_package_ready_count=1`、 +`controlled_apply_final_preflight_ready_count=1`、 +`dry_run_invocation_readiness_receipt_count=1`、 +`dry_run_invocation_readiness_receipt_field_count=12`、 +`ready_for_future_database_apply_controlled_dry_run_invocation_readiness_receipt=true`、 +`can_enter_future_database_apply_controlled_dry_run_invocation_receipt_closeout=true`、 +`apply_executor_readiness_closeout_ready=true`、 +`dry_run_invocation_readiness_receipt_bound=true`、 +`dry_run_executor_invocation_allowed=false`、 +`ready_for_dry_run_executor_invocation_now=false`、 +`endpoint_execution_allowed=false`、`sql_execution_allowed=false`、 +`database_write_allowed=false`、`database_apply_authorized=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`reads_secret_count=0`、`executes_endpoint_count=0`、`executes_sql_count=0`、 +`writes_database_count=0`、`signs_database_apply_authorization_count=0`。下一個 P0 +是 future database apply controlled dry-run invocation receipt closeout / +no-write invocation package,仍不是直接 DB apply。 +DB apply controlled dry-run invocation receipt closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-invocation-receipt-closeout`, +輸出 future database apply controlled dry-run no-write invocation package、 +controlled dry-run invocation receipt closeout、no-write invocation package +與 12 項 invocation receipt closeout checks;目前仍是 +controlled-dry-run-invocation-receipt-closeout-and-no-write-invocation-package-only / +invocation-receipt-closeout-only / no-write-invocation-package-only / +dry-run-only / check-mode-only / no-secret-read / no-plaintext-secret / +no-stdout-capture / no-stderr-capture / no-signature-material / +no-authorization-evidence-execute / no-database-apply-execute / +no-dry-run-executor-invocation / no-endpoint-execute / no-SQL-execute / +no-DB-write preview。local deterministic fake-fetch smoke 目標為 +`DB_APPLY_CONTROLLED_DRY_RUN_INVOCATION_RECEIPT_CLOSEOUT_READY`、 +`controlled_dry_run_invocation_receipt_closeout_ready_count=1`、 +`controlled_dry_run_invocation_receipt_closeout_check_count=12`、 +`controlled_dry_run_invocation_receipt_closeout_pass_count=12`、 +`controlled_dry_run_invocation_receipt_closeout_waiting_count=0`、 +`controlled_dry_run_apply_executor_readiness_closeout_ready_count=1`、 +`controlled_dry_run_pre_apply_replay_closeout_ready_count=1`、 +`controlled_dry_run_final_executor_guard_closeout_ready_count=1`、 +`controlled_dry_run_no_apply_enforcement_closeout_ready_count=1`、 +`controlled_dry_run_post_receipt_parser_closeout_ready_count=1`、 +`controlled_dry_run_runner_execution_receipt_closeout_ready_count=1`、 +`controlled_dry_run_command_artifact_closeout_ready_count=1`、 +`controlled_dry_run_execution_plan_closeout_ready_count=1`、 +`controlled_dry_run_runner_readiness_ready_count=1`、 +`controlled_dry_run_receipt_closeout_ready_count=1`、 +`controlled_dry_run_package_ready_count=1`、 +`controlled_apply_final_preflight_ready_count=1`、 +`no_write_invocation_package_count=1`、 +`no_write_invocation_package_field_count=12`、 +`ready_for_future_database_apply_controlled_dry_run_no_write_invocation_package=true`、 +`can_enter_future_database_apply_controlled_dry_run_no_write_invocation_package_closeout=true`、 +`invocation_receipt_closeout_ready=true`、 +`no_write_invocation_package_bound=true`、 +`dry_run_executor_invocation_allowed=false`、 +`ready_for_dry_run_executor_invocation_now=false`、 +`ready_for_actual_dry_run_execution_now=false`、 +`endpoint_execution_allowed=false`、`sql_execution_allowed=false`、 +`database_write_allowed=false`、`database_apply_authorized=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`reads_secret_count=0`、`executes_endpoint_count=0`、`executes_sql_count=0`、 +`writes_database_count=0`、`signs_database_apply_authorization_count=0`。下一個 P0 +是 future database apply controlled dry-run no-write invocation package closeout / +execution-preflight guard,仍不是直接 DB apply。 +DB apply controlled dry-run no-write invocation package closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-write-invocation-package-closeout`, +輸出 future database apply controlled dry-run execution preflight guard、 +controlled dry-run no-write invocation package closeout、execution preflight guard +與 12 項 no-write invocation package closeout checks;目前仍是 +controlled-dry-run-no-write-invocation-package-closeout-and-execution-preflight-guard-only / +no-write-invocation-package-closeout-only / execution-preflight-guard-only / +dry-run-only / check-mode-only / no-secret-read / no-plaintext-secret / +no-stdout-capture / no-stderr-capture / no-signature-material / +no-authorization-evidence-execute / no-database-apply-execute / +no-dry-run-executor-invocation / no-endpoint-execute / no-SQL-execute / +no-DB-write preview。local deterministic fake-fetch smoke 目標為 +`DB_APPLY_CONTROLLED_DRY_RUN_NO_WRITE_INVOCATION_PACKAGE_CLOSEOUT_READY`、 +`controlled_dry_run_no_write_invocation_package_closeout_ready_count=1`、 +`controlled_dry_run_no_write_invocation_package_closeout_check_count=12`、 +`controlled_dry_run_no_write_invocation_package_closeout_pass_count=12`、 +`controlled_dry_run_no_write_invocation_package_closeout_waiting_count=0`、 +`controlled_dry_run_invocation_receipt_closeout_ready_count=1`、 +`controlled_dry_run_apply_executor_readiness_closeout_ready_count=1`、 +`controlled_dry_run_pre_apply_replay_closeout_ready_count=1`、 +`controlled_dry_run_final_executor_guard_closeout_ready_count=1`、 +`controlled_dry_run_no_apply_enforcement_closeout_ready_count=1`、 +`controlled_dry_run_post_receipt_parser_closeout_ready_count=1`、 +`controlled_dry_run_runner_execution_receipt_closeout_ready_count=1`、 +`controlled_dry_run_command_artifact_closeout_ready_count=1`、 +`controlled_dry_run_execution_plan_closeout_ready_count=1`、 +`controlled_dry_run_runner_readiness_ready_count=1`、 +`controlled_dry_run_receipt_closeout_ready_count=1`、 +`controlled_dry_run_package_ready_count=1`、 +`controlled_apply_final_preflight_ready_count=1`、 +`execution_preflight_guard_count=1`、 +`execution_preflight_guard_field_count=12`、 +`ready_for_future_database_apply_controlled_dry_run_execution_preflight_guard=true`、 +`can_enter_future_database_apply_controlled_dry_run_execution_preflight_guard_closeout=true`、 +`no_write_invocation_package_closeout_ready=true`、 +`execution_preflight_guard_bound=true`、 +`dry_run_executor_invocation_allowed=false`、 +`ready_for_dry_run_executor_invocation_now=false`、 +`ready_for_actual_dry_run_execution_now=false`、 +`endpoint_execution_allowed=false`、`sql_execution_allowed=false`、 +`database_write_allowed=false`、`database_apply_authorized=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`reads_secret_count=0`、`executes_endpoint_count=0`、`executes_sql_count=0`、 +`writes_database_count=0`、`signs_database_apply_authorization_count=0`。下一個 P0 +是 future database apply controlled dry-run execution preflight guard closeout / +runner invocation boundary,仍不是直接 DB apply。 +DB apply controlled dry-run execution preflight guard closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-preflight-guard-closeout`, +輸出 future database apply controlled dry-run runner invocation boundary、 +controlled dry-run execution preflight guard closeout、runner invocation boundary +與 12 項 execution preflight guard closeout checks;目前仍是 +controlled-dry-run-execution-preflight-guard-closeout-and-runner-invocation-boundary-only / +execution-preflight-guard-closeout-only / runner-invocation-boundary-only / +dry-run-only / check-mode-only / no-secret-read / no-plaintext-secret / +no-stdout-capture / no-stderr-capture / no-signature-material / +no-authorization-evidence-execute / no-database-apply-execute / +no-dry-run-executor-invocation / no-runner-invocation / no-endpoint-execute / +no-SQL-execute / no-DB-write preview。local deterministic fake-fetch smoke 目標為 +`DB_APPLY_CONTROLLED_DRY_RUN_EXECUTION_PREFLIGHT_GUARD_CLOSEOUT_READY`、 +`controlled_dry_run_execution_preflight_guard_closeout_ready_count=1`、 +`controlled_dry_run_execution_preflight_guard_closeout_check_count=12`、 +`controlled_dry_run_execution_preflight_guard_closeout_pass_count=12`、 +`controlled_dry_run_execution_preflight_guard_closeout_waiting_count=0`、 +`controlled_dry_run_no_write_invocation_package_closeout_ready_count=1`、 +`controlled_dry_run_invocation_receipt_closeout_ready_count=1`、 +`controlled_dry_run_apply_executor_readiness_closeout_ready_count=1`、 +`controlled_dry_run_pre_apply_replay_closeout_ready_count=1`、 +`controlled_dry_run_final_executor_guard_closeout_ready_count=1`、 +`controlled_dry_run_no_apply_enforcement_closeout_ready_count=1`、 +`controlled_dry_run_post_receipt_parser_closeout_ready_count=1`、 +`controlled_dry_run_runner_execution_receipt_closeout_ready_count=1`、 +`controlled_dry_run_command_artifact_closeout_ready_count=1`、 +`controlled_dry_run_execution_plan_closeout_ready_count=1`、 +`controlled_dry_run_runner_readiness_ready_count=1`、 +`controlled_dry_run_receipt_closeout_ready_count=1`、 +`controlled_dry_run_package_ready_count=1`、 +`controlled_apply_final_preflight_ready_count=1`、 +`runner_invocation_boundary_count=1`、 +`runner_invocation_boundary_field_count=12`、 +`ready_for_future_database_apply_controlled_dry_run_runner_invocation_boundary=true`、 +`can_enter_future_database_apply_controlled_dry_run_runner_invocation_boundary_closeout=true`、 +`execution_preflight_guard_closeout_ready=true`、 +`runner_invocation_boundary_bound=true`、 +`dry_run_executor_invocation_allowed=false`、 +`runner_invocation_allowed=false`、 +`ready_for_dry_run_executor_invocation_now=false`、 +`ready_for_actual_dry_run_execution_now=false`、 +`endpoint_execution_allowed=false`、`sql_execution_allowed=false`、 +`database_write_allowed=false`、`database_apply_authorized=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`captures_stdout=false`、`captures_stderr=false`、 +`reads_secret_count=0`、`executes_endpoint_count=0`、`executes_sql_count=0`、 +`writes_database_count=0`、`signs_database_apply_authorization_count=0`。下一個 P0 +是 future database apply controlled dry-run runner invocation boundary closeout / +no-execution receipt handoff,仍不是直接 DB apply。 + +DB apply controlled dry-run runner invocation boundary closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-invocation-boundary-closeout`, +輸出 future database apply controlled dry-run no-execution receipt handoff、 +controlled dry-run runner invocation boundary closeout、no-execution receipt handoff +與 12 項 runner invocation boundary closeout checks;目前仍是 +controlled-dry-run-runner-invocation-boundary-closeout-and-no-execution-receipt-handoff-only / +runner-invocation-boundary-closeout-only / no-execution-receipt-handoff-only / +dry-run-only / check-mode-only / no-secret-read / no-plaintext-secret / +no-stdout-capture / no-stderr-capture / no-signature-material / +no-authorization-evidence-execute / no-database-apply-execute / +no-dry-run-executor-invocation / no-runner-invocation / no-endpoint-execute / +no-SQL-execute / no-DB-write preview。local deterministic fake-fetch smoke 目標為 +`DB_APPLY_CONTROLLED_DRY_RUN_RUNNER_INVOCATION_BOUNDARY_CLOSEOUT_READY`、 +`controlled_dry_run_runner_invocation_boundary_closeout_ready_count=1`、 +`controlled_dry_run_runner_invocation_boundary_closeout_check_count=12`、 +`controlled_dry_run_runner_invocation_boundary_closeout_pass_count=12`、 +`controlled_dry_run_runner_invocation_boundary_closeout_waiting_count=0`、 +`controlled_dry_run_execution_preflight_guard_closeout_ready_count=1`、 +`controlled_dry_run_no_write_invocation_package_closeout_ready_count=1`、 +`controlled_dry_run_invocation_receipt_closeout_ready_count=1`、 +`controlled_dry_run_apply_executor_readiness_closeout_ready_count=1`、 +`controlled_dry_run_pre_apply_replay_closeout_ready_count=1`、 +`controlled_dry_run_final_executor_guard_closeout_ready_count=1`、 +`controlled_dry_run_no_apply_enforcement_closeout_ready_count=1`、 +`controlled_dry_run_post_receipt_parser_closeout_ready_count=1`、 +`controlled_dry_run_runner_execution_receipt_closeout_ready_count=1`、 +`controlled_dry_run_command_artifact_closeout_ready_count=1`、 +`controlled_dry_run_execution_plan_closeout_ready_count=1`、 +`controlled_dry_run_runner_readiness_ready_count=1`、 +`controlled_dry_run_receipt_closeout_ready_count=1`、 +`controlled_dry_run_package_ready_count=1`、 +`controlled_apply_final_preflight_ready_count=1`、 +`no_execution_receipt_handoff_count=1`、 +`no_execution_receipt_handoff_field_count=12`、 +`ready_for_future_database_apply_controlled_dry_run_no_execution_receipt_handoff=true`、 +`can_enter_future_database_apply_controlled_dry_run_no_execution_receipt_handoff_closeout=true`、 +`runner_invocation_boundary_closeout_ready=true`、 +`no_execution_receipt_handoff_bound=true`、 +`dry_run_executor_invocation_allowed=false`、 +`runner_invocation_allowed=false`、 +`execution_receipt_present=false`、 +`ready_for_dry_run_executor_invocation_now=false`、 +`ready_for_actual_dry_run_execution_now=false`、 +`endpoint_execution_allowed=false`、`sql_execution_allowed=false`、 +`database_write_allowed=false`、`database_apply_authorized=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`stdout_included=false`、`stderr_included=false`、 +`reads_secret_count=0`、`executes_endpoint_count=0`、`executes_sql_count=0`、 +`writes_database_count=0`、`signs_database_apply_authorization_count=0`。下一個 P0 +是 future database apply controlled dry-run no-execution receipt handoff closeout / +final no-runner-execution proof,仍不是直接 DB apply。 + +DB apply controlled dry-run no-execution receipt handoff closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-execution-receipt-handoff-closeout`, +輸出 future database apply controlled dry-run final no-runner-execution proof、 +controlled dry-run no-execution receipt handoff closeout、final no-runner-execution proof +與 12 項 no-execution receipt handoff closeout checks;目前仍是 +controlled-dry-run-no-execution-receipt-handoff-closeout-and-final-no-runner-execution-proof-only / +no-execution-receipt-handoff-closeout-only / final-no-runner-execution-proof-only / +dry-run-only / check-mode-only / no-secret-read / no-plaintext-secret / +no-stdout-capture / no-stderr-capture / no-signature-material / +no-authorization-evidence-execute / no-database-apply-execute / +no-dry-run-executor-invocation / no-runner-invocation / no-endpoint-execute / +no-SQL-execute / no-DB-write preview。local deterministic fake-fetch smoke 目標為 +`DB_APPLY_CONTROLLED_DRY_RUN_NO_EXECUTION_RECEIPT_HANDOFF_CLOSEOUT_READY`、 +`controlled_dry_run_no_execution_receipt_handoff_closeout_ready_count=1`、 +`controlled_dry_run_no_execution_receipt_handoff_closeout_check_count=12`、 +`controlled_dry_run_no_execution_receipt_handoff_closeout_pass_count=12`、 +`controlled_dry_run_no_execution_receipt_handoff_closeout_waiting_count=0`、 +`controlled_dry_run_runner_invocation_boundary_closeout_ready_count=1`、 +`controlled_dry_run_execution_preflight_guard_closeout_ready_count=1`、 +`controlled_dry_run_no_write_invocation_package_closeout_ready_count=1`、 +`controlled_dry_run_invocation_receipt_closeout_ready_count=1`、 +`controlled_dry_run_apply_executor_readiness_closeout_ready_count=1`、 +`controlled_dry_run_pre_apply_replay_closeout_ready_count=1`、 +`controlled_dry_run_final_executor_guard_closeout_ready_count=1`、 +`controlled_dry_run_no_apply_enforcement_closeout_ready_count=1`、 +`controlled_dry_run_post_receipt_parser_closeout_ready_count=1`、 +`controlled_dry_run_runner_execution_receipt_closeout_ready_count=1`、 +`controlled_dry_run_command_artifact_closeout_ready_count=1`、 +`controlled_dry_run_execution_plan_closeout_ready_count=1`、 +`controlled_dry_run_runner_readiness_ready_count=1`、 +`controlled_dry_run_receipt_closeout_ready_count=1`、 +`controlled_dry_run_package_ready_count=1`、 +`controlled_apply_final_preflight_ready_count=1`、 +`final_no_runner_execution_proof_count=1`、 +`final_no_runner_execution_proof_field_count=12`、 +`ready_for_future_database_apply_controlled_dry_run_final_no_runner_execution_proof=true`、 +`can_enter_future_database_apply_controlled_dry_run_final_no_runner_execution_proof_closeout=true`、 +`no_execution_receipt_handoff_closeout_ready=true`、 +`final_no_runner_execution_proof_bound=true`、 +`dry_run_executor_invoked=false`、`runner_invocation_performed=false`、 +`endpoint_executed=false`、`sql_executed=false`、`database_written=false`、 +`execution_receipt_present=false`、`dry_run_executor_invocation_allowed=false`、 +`runner_invocation_allowed=false`、`ready_for_dry_run_executor_invocation_now=false`、 +`ready_for_actual_dry_run_execution_now=false`、 +`endpoint_execution_allowed=false`、`sql_execution_allowed=false`、 +`database_write_allowed=false`、`database_apply_authorized=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`stdout_included=false`、`stderr_included=false`、 +`reads_secret_count=0`、`executes_endpoint_count=0`、`executes_sql_count=0`、 +`writes_database_count=0`、`dry_run_executor_invoked_count=0`、 +`runner_invocation_performed_count=0`、`endpoint_executed_count=0`、 +`sql_executed_count=0`、`database_written_count=0`、 +`signs_database_apply_authorization_count=0`。下一個 P0 +是 future database apply controlled dry-run final no-runner-execution proof closeout / +controlled executor quarantine proof,仍不是直接 DB apply。 + +DB apply controlled dry-run final no-runner-execution proof closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-final-no-runner-execution-proof-closeout`, +輸出 future database apply controlled dry-run controlled executor quarantine proof、 +controlled dry-run final no-runner-execution proof closeout、controlled executor quarantine proof +與 12 項 final no-runner-execution proof closeout checks;目前仍是 +controlled-dry-run-final-no-runner-execution-proof-closeout-and-controlled-executor-quarantine-proof-only / +final-no-runner-execution-proof-closeout-only / controlled-executor-quarantine-proof-only / +dry-run-only / check-mode-only / no-secret-read / no-plaintext-secret / +no-stdout-capture / no-stderr-capture / no-signature-material / +no-authorization-evidence-execute / no-database-apply-execute / +no-dry-run-executor-invocation / no-runner-invocation / no-endpoint-execute / +no-SQL-execute / no-DB-write preview。local deterministic fake-fetch smoke 目標為 +`DB_APPLY_CONTROLLED_DRY_RUN_FINAL_NO_RUNNER_EXECUTION_PROOF_CLOSEOUT_READY`、 +`controlled_dry_run_final_no_runner_execution_proof_closeout_ready_count=1`、 +`controlled_dry_run_final_no_runner_execution_proof_closeout_check_count=12`、 +`controlled_dry_run_final_no_runner_execution_proof_closeout_pass_count=12`、 +`controlled_dry_run_final_no_runner_execution_proof_closeout_waiting_count=0`、 +`controlled_dry_run_no_execution_receipt_handoff_closeout_ready_count=1`、 +`controlled_dry_run_runner_invocation_boundary_closeout_ready_count=1`、 +`controlled_dry_run_execution_preflight_guard_closeout_ready_count=1`、 +`controlled_dry_run_no_write_invocation_package_closeout_ready_count=1`、 +`controlled_dry_run_invocation_receipt_closeout_ready_count=1`、 +`controlled_dry_run_apply_executor_readiness_closeout_ready_count=1`、 +`controlled_dry_run_pre_apply_replay_closeout_ready_count=1`、 +`controlled_dry_run_final_executor_guard_closeout_ready_count=1`、 +`controlled_dry_run_no_apply_enforcement_closeout_ready_count=1`、 +`controlled_dry_run_post_receipt_parser_closeout_ready_count=1`、 +`controlled_dry_run_runner_execution_receipt_closeout_ready_count=1`、 +`controlled_dry_run_command_artifact_closeout_ready_count=1`、 +`controlled_dry_run_execution_plan_closeout_ready_count=1`、 +`controlled_dry_run_runner_readiness_ready_count=1`、 +`controlled_dry_run_receipt_closeout_ready_count=1`、 +`controlled_dry_run_package_ready_count=1`、 +`controlled_apply_final_preflight_ready_count=1`、 +`controlled_executor_quarantine_proof_count=1`、 +`controlled_executor_quarantine_proof_field_count=12`、 +`ready_for_future_database_apply_controlled_dry_run_controlled_executor_quarantine_proof=true`、 +`can_enter_future_database_apply_controlled_dry_run_controlled_executor_quarantine_proof_closeout=true`、 +`final_no_runner_execution_proof_closeout_ready=true`、 +`controlled_executor_quarantine_proof_bound=true`、 +`controlled_executor_quarantine_bound=true`、`executor_quarantine_enforced=true`、 +`dry_run_executor_invoked=false`、`runner_invocation_performed=false`、 +`endpoint_executed=false`、`sql_executed=false`、`database_written=false`、 +`execution_receipt_present=false`、`dry_run_executor_invocation_allowed=false`、 +`runner_invocation_allowed=false`、`ready_for_dry_run_executor_invocation_now=false`、 +`ready_for_actual_dry_run_execution_now=false`、 +`endpoint_execution_allowed=false`、`sql_execution_allowed=false`、 +`database_write_allowed=false`、`database_apply_authorized=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`stdout_included=false`、`stderr_included=false`、 +`reads_secret_count=0`、`executes_endpoint_count=0`、`executes_sql_count=0`、 +`writes_database_count=0`、`dry_run_executor_invoked_count=0`、 +`runner_invocation_performed_count=0`、`endpoint_executed_count=0`、 +`sql_executed_count=0`、`database_written_count=0`、 +`signs_database_apply_authorization_count=0`。 + +DB apply controlled dry-run controlled executor quarantine proof closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-controlled-executor-quarantine-proof-closeout`, +輸出 future database apply controlled dry-run execution envelope freeze proof、 +controlled dry-run controlled executor quarantine proof closeout、dry-run execution +envelope freeze proof 與 12 項 controlled executor quarantine proof closeout checks; +目前仍是 controlled-executor-quarantine-proof-closeout-and-execution-envelope-freeze-proof-only / +dry-run-execution-envelope-freeze-proof-only / dry-run-only / check-mode-only / +no-secret-read / no-plaintext-secret / no-stdout-capture / no-stderr-capture / +no-signature-material / no-authorization-evidence-execute / +no-database-apply-execute / no-dry-run-executor-invocation / no-runner-invocation / +no-endpoint-execute / no-SQL-execute / no-DB-write preview。local deterministic +fake-fetch smoke 目標為 +`DB_APPLY_CONTROLLED_DRY_RUN_CONTROLLED_EXECUTOR_QUARANTINE_PROOF_CLOSEOUT_READY`、 +`controlled_dry_run_controlled_executor_quarantine_proof_closeout_ready_count=1`、 +`controlled_dry_run_controlled_executor_quarantine_proof_closeout_check_count=12`、 +`controlled_dry_run_controlled_executor_quarantine_proof_closeout_pass_count=12`、 +`controlled_dry_run_controlled_executor_quarantine_proof_closeout_waiting_count=0`、 +`controlled_dry_run_final_no_runner_execution_proof_closeout_ready_count=1`、 +`dry_run_execution_envelope_freeze_proof_count=1`、 +`dry_run_execution_envelope_freeze_proof_field_count=12`、 +`ready_for_future_database_apply_controlled_dry_run_execution_envelope_freeze_proof=true`、 +`can_enter_future_database_apply_controlled_dry_run_execution_envelope_freeze_proof_closeout=true`、 +`controlled_executor_quarantine_proof_closeout_ready=true`、 +`dry_run_execution_envelope_freeze_proof_bound=true`、 +`execution_envelope_frozen=true`、`execution_envelope_mutation_allowed=false`、 +`dry_run_executor_invoked=false`、`runner_invocation_performed=false`、 +`endpoint_executed=false`、`sql_executed=false`、`database_written=false`、 +`execution_receipt_present=false`、`dry_run_executor_invocation_allowed=false`、 +`runner_invocation_allowed=false`、`ready_for_dry_run_executor_invocation_now=false`、 +`ready_for_actual_dry_run_execution_now=false`、 +`endpoint_execution_allowed=false`、`sql_execution_allowed=false`、 +`database_write_allowed=false`、`database_apply_authorized=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`stdout_included=false`、`stderr_included=false`、 +`reads_secret_count=0`、`executes_endpoint_count=0`、`executes_sql_count=0`、 +`writes_database_count=0`、`dry_run_executor_invoked_count=0`、 +`runner_invocation_performed_count=0`、`endpoint_executed_count=0`、 +`sql_executed_count=0`、`database_written_count=0`、 +`signs_database_apply_authorization_count=0`。 + +DB apply controlled dry-run execution envelope freeze proof closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-envelope-freeze-proof-closeout`, +輸出 future database apply controlled dry-run frozen envelope verifier handoff、 +controlled dry-run execution envelope freeze proof closeout、frozen envelope verifier +handoff 與 12 項 execution envelope freeze proof closeout checks;目前仍是 +execution-envelope-freeze-proof-closeout-and-frozen-envelope-verifier-handoff-only / +frozen-envelope-verifier-handoff-only / dry-run-only / check-mode-only / +no-secret-read / no-plaintext-secret / no-stdout-capture / no-stderr-capture / +no-signature-material / no-authorization-evidence-execute / +no-database-apply-execute / no-verifier-invocation / +no-dry-run-executor-invocation / no-runner-invocation / no-endpoint-execute / +no-SQL-execute / no-DB-write preview。local deterministic fake-fetch smoke 目標為 +`DB_APPLY_CONTROLLED_DRY_RUN_EXECUTION_ENVELOPE_FREEZE_PROOF_CLOSEOUT_READY`、 +`controlled_dry_run_execution_envelope_freeze_proof_closeout_ready_count=1`、 +`controlled_dry_run_execution_envelope_freeze_proof_closeout_check_count=12`、 +`controlled_dry_run_execution_envelope_freeze_proof_closeout_pass_count=12`、 +`controlled_dry_run_execution_envelope_freeze_proof_closeout_waiting_count=0`、 +`controlled_dry_run_controlled_executor_quarantine_proof_closeout_ready_count=1`、 +`controlled_dry_run_final_no_runner_execution_proof_closeout_ready_count=1`、 +`frozen_envelope_verifier_handoff_count=1`、 +`frozen_envelope_verifier_handoff_field_count=12`、 +`ready_for_future_database_apply_controlled_dry_run_frozen_envelope_verifier_handoff=true`、 +`can_enter_future_database_apply_controlled_dry_run_frozen_envelope_verifier_handoff_closeout=true`、 +`execution_envelope_freeze_proof_closeout_ready=true`、 +`frozen_envelope_verifier_handoff_bound=true`、 +`execution_envelope_frozen=true`、`execution_envelope_mutation_allowed=false`、 +`verifier_invocation_allowed=false`、`verifier_invoked=false`、 +`verifier_receipt_present=false`、`dry_run_executor_invoked=false`、 +`runner_invocation_performed=false`、`endpoint_executed=false`、 +`sql_executed=false`、`database_written=false`、 +`ready_for_database_apply_now=false`、`ready_for_verifier_invocation_now=false`、 +`ready_for_dry_run_executor_invocation_now=false`、 +`endpoint_execution_allowed=false`、`sql_execution_allowed=false`、 +`database_write_allowed=false`、`database_apply_authorized=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`stdout_included=false`、`stderr_included=false`、 +`reads_secret_count=0`、`executes_endpoint_count=0`、`executes_sql_count=0`、 +`writes_database_count=0`、`verifier_invoked_count=0`、 +`verifier_receipt_present_count=0`、`dry_run_executor_invoked_count=0`、 +`runner_invocation_performed_count=0`、`endpoint_executed_count=0`、 +`sql_executed_count=0`、`database_written_count=0`、 +`signs_database_apply_authorization_count=0`。 + +DB apply controlled dry-run frozen envelope verifier handoff closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-frozen-envelope-verifier-handoff-closeout`, +輸出 future database apply controlled dry-run verifier invocation lock proof、 +controlled dry-run frozen envelope verifier handoff closeout、verifier invocation +lock proof 與 12 項 frozen envelope verifier handoff closeout checks;目前仍是 +frozen-envelope-verifier-handoff-closeout-and-verifier-invocation-lock-proof-only / +verifier-invocation-lock-proof-only / dry-run-only / check-mode-only / +no-secret-read / no-plaintext-secret / no-stdout-capture / no-stderr-capture / +no-signature-material / no-authorization-evidence-execute / +no-database-apply-execute / no-verifier-invocation / +no-dry-run-executor-invocation / no-runner-invocation / no-endpoint-execute / +no-SQL-execute / no-DB-write preview。local deterministic fake-fetch smoke 目標為 +`DB_APPLY_CONTROLLED_DRY_RUN_FROZEN_ENVELOPE_VERIFIER_HANDOFF_CLOSEOUT_READY`、 +`controlled_dry_run_frozen_envelope_verifier_handoff_closeout_ready_count=1`、 +`controlled_dry_run_frozen_envelope_verifier_handoff_closeout_check_count=12`、 +`controlled_dry_run_frozen_envelope_verifier_handoff_closeout_pass_count=12`、 +`controlled_dry_run_frozen_envelope_verifier_handoff_closeout_waiting_count=0`、 +`controlled_dry_run_execution_envelope_freeze_proof_closeout_ready_count=1`、 +`controlled_dry_run_controlled_executor_quarantine_proof_closeout_ready_count=1`、 +`verifier_invocation_lock_proof_count=1`、 +`verifier_invocation_lock_proof_field_count=12`、 +`ready_for_future_database_apply_controlled_dry_run_verifier_invocation_lock_proof=true`、 +`can_enter_future_database_apply_controlled_dry_run_verifier_invocation_lock_proof_closeout=true`、 +`frozen_envelope_verifier_handoff_closeout_ready=true`、 +`execution_envelope_freeze_proof_closeout_ready=true`、 +`frozen_envelope_verifier_handoff_ready=true`、 +`verifier_invocation_lock_proof_bound=true`、 +`execution_envelope_frozen=true`、`execution_envelope_mutation_allowed=false`、 +`verifier_invocation_locked=true`、`verifier_invocation_allowed=false`、 +`verifier_invoked=false`、`verifier_receipt_present=false`、 +`dry_run_executor_invoked=false`、`runner_invocation_performed=false`、 +`endpoint_executed=false`、`sql_executed=false`、`database_written=false`、 +`ready_for_database_apply_now=false`、`ready_for_verifier_invocation_now=false`、 +`ready_for_dry_run_executor_invocation_now=false`、 +`endpoint_execution_allowed=false`、`sql_execution_allowed=false`、 +`database_write_allowed=false`、`database_apply_authorized=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`stdout_included=false`、`stderr_included=false`、 +`reads_secret_count=0`、`executes_endpoint_count=0`、`executes_sql_count=0`、 +`writes_database_count=0`、`verifier_invoked_count=0`、 +`verifier_receipt_present_count=0`、`dry_run_executor_invoked_count=0`、 +`runner_invocation_performed_count=0`、`endpoint_executed_count=0`、 +`sql_executed_count=0`、`database_written_count=0`、 +`signs_database_apply_authorization_count=0`。 + +DB apply controlled dry-run verifier invocation lock proof closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-invocation-lock-proof-closeout`, +輸出 future database apply controlled dry-run verifier no-execution receipt proof、 +controlled dry-run verifier invocation lock proof closeout、verifier no-execution +receipt proof 與 12 項 verifier invocation lock proof closeout checks;目前仍是 +verifier-invocation-lock-proof-closeout-and-verifier-no-execution-receipt-proof-only / +verifier-no-execution-receipt-proof-only / dry-run-only / check-mode-only / +no-secret-read / no-plaintext-secret / no-stdout-capture / no-stderr-capture / +no-signature-material / no-authorization-evidence-execute / +no-database-apply-execute / no-verifier-invocation / +no-dry-run-executor-invocation / no-runner-invocation / no-endpoint-execute / +no-SQL-execute / no-DB-write preview。local deterministic fake-fetch smoke 目標為 +`DB_APPLY_CONTROLLED_DRY_RUN_VERIFIER_INVOCATION_LOCK_PROOF_CLOSEOUT_READY`、 +`controlled_dry_run_verifier_invocation_lock_proof_closeout_ready_count=1`、 +`controlled_dry_run_verifier_invocation_lock_proof_closeout_check_count=12`、 +`controlled_dry_run_verifier_invocation_lock_proof_closeout_pass_count=12`、 +`controlled_dry_run_verifier_invocation_lock_proof_closeout_waiting_count=0`、 +`controlled_dry_run_frozen_envelope_verifier_handoff_closeout_ready_count=1`、 +`controlled_dry_run_execution_envelope_freeze_proof_closeout_ready_count=1`、 +`verifier_no_execution_receipt_proof_count=1`、 +`verifier_no_execution_receipt_proof_field_count=12`、 +`ready_for_future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof=true`、 +`can_enter_future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof_closeout=true`、 +`verifier_invocation_lock_proof_closeout_ready=true`、 +`frozen_envelope_verifier_handoff_closeout_ready=true`、 +`verifier_invocation_lock_proof_ready=true`、 +`verifier_no_execution_receipt_proof_bound=true`、 +`verifier_invocation_locked=true`、`verifier_invocation_allowed=false`、 +`verifier_invoked=false`、`verifier_receipt_present=false`、 +`verifier_receipt_required=false`、`dry_run_executor_invoked=false`、 +`runner_invocation_performed=false`、`endpoint_executed=false`、 +`sql_executed=false`、`database_written=false`、 +`ready_for_database_apply_now=false`、`ready_for_verifier_invocation_now=false`、 +`ready_for_dry_run_executor_invocation_now=false`、 +`endpoint_execution_allowed=false`、`sql_execution_allowed=false`、 +`database_write_allowed=false`、`database_apply_authorized=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`stdout_included=false`、`stderr_included=false`、 +`reads_secret_count=0`、`executes_endpoint_count=0`、`executes_sql_count=0`、 +`writes_database_count=0`、`verifier_invoked_count=0`、 +`verifier_receipt_present_count=0`、`dry_run_executor_invoked_count=0`、 +`runner_invocation_performed_count=0`、`endpoint_executed_count=0`、 +`sql_executed_count=0`、`database_written_count=0`、 +`signs_database_apply_authorization_count=0`。 + +DB apply controlled dry-run verifier no-execution receipt proof closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-no-execution-receipt-proof-closeout`, +輸出 future database apply controlled dry-run verifier receipt persistence guard proof、 +controlled dry-run verifier no-execution receipt proof closeout、verifier receipt +persistence guard proof 與 12 項 verifier no-execution receipt proof closeout +checks;目前仍是 verifier-no-execution-receipt-proof-closeout-and-verifier- +receipt-persistence-guard-proof-only / verifier-receipt-persistence-guard-proof- +only / dry-run-only / check-mode-only / no-secret-read / no-plaintext-secret / +no-stdout-capture / no-stderr-capture / no-signature-material / +no-verifier-receipt-persistence / no-authorization-evidence-execute / +no-database-apply-execute / no-verifier-invocation / +no-dry-run-executor-invocation / no-runner-invocation / no-endpoint-execute / +no-SQL-execute / no-DB-write preview。local deterministic fake-fetch smoke 目標為 +`DB_APPLY_CONTROLLED_DRY_RUN_VERIFIER_NO_EXECUTION_RECEIPT_PROOF_CLOSEOUT_READY`、 +`controlled_dry_run_verifier_no_execution_receipt_proof_closeout_ready_count=1`、 +`controlled_dry_run_verifier_no_execution_receipt_proof_closeout_check_count=12`、 +`controlled_dry_run_verifier_no_execution_receipt_proof_closeout_pass_count=12`、 +`controlled_dry_run_verifier_no_execution_receipt_proof_closeout_waiting_count=0`、 +`controlled_dry_run_verifier_invocation_lock_proof_closeout_ready_count=1`、 +`verifier_receipt_persistence_guard_proof_count=1`、 +`verifier_receipt_persistence_guard_proof_field_count=12`、 +`ready_for_future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof=true`、 +`can_enter_future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout=true`、 +`verifier_no_execution_receipt_proof_closeout_ready=true`、 +`verifier_invocation_lock_proof_closeout_ready=true`、 +`verifier_no_execution_receipt_proof_ready=true`、 +`verifier_receipt_persistence_guard_proof_bound=true`、 +`verifier_receipt_persistence_locked=true`、 +`verifier_receipt_persistence_allowed=false`、 +`verifier_receipt_persisted=false`、`persists_verifier_receipt=false`、 +`verifier_invocation_locked=true`、`verifier_invocation_allowed=false`、 +`verifier_invoked=false`、`verifier_receipt_present=false`、 +`dry_run_executor_invoked=false`、`runner_invocation_performed=false`、 +`endpoint_executed=false`、`sql_executed=false`、`database_written=false`、 +`ready_for_database_apply_now=false`、 +`ready_for_verifier_receipt_persistence_now=false`、 +`ready_for_verifier_invocation_now=false`、 +`ready_for_dry_run_executor_invocation_now=false`、 +`endpoint_execution_allowed=false`、`sql_execution_allowed=false`、 +`database_write_allowed=false`、`database_apply_authorized=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`stdout_included=false`、`stderr_included=false`、 +`reads_secret_count=0`、`executes_endpoint_count=0`、`executes_sql_count=0`、 +`writes_database_count=0`、`verifier_invoked_count=0`、 +`verifier_receipt_present_count=0`、`dry_run_executor_invoked_count=0`、 +`runner_invocation_performed_count=0`、`endpoint_executed_count=0`、 +`sql_executed_count=0`、`database_written_count=0`、 +`persists_verifier_receipt_count=0`、 +`signs_database_apply_authorization_count=0`。 + +DB apply controlled dry-run verifier receipt persistence guard proof closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-receipt-persistence-guard-proof-closeout`, +輸出 future database apply controlled dry-run receipt persistence storage boundary proof、 +controlled dry-run verifier receipt persistence guard proof closeout、receipt +persistence storage boundary proof 與 12 項 verifier receipt persistence guard +proof closeout checks;目前仍是 verifier-receipt-persistence-guard-proof- +closeout-and-receipt-persistence-storage-boundary-proof-only / +receipt-persistence-storage-boundary-proof-only / dry-run-only / check-mode-only / +no-secret-read / no-plaintext-secret / no-stdout-capture / no-stderr-capture / +no-signature-material / no-verifier-receipt-persistence / no-receipt-storage-write / +no-authorization-evidence-execute / no-database-apply-execute / +no-verifier-invocation / no-dry-run-executor-invocation / +no-runner-invocation / no-endpoint-execute / no-SQL-execute / no-DB-write +preview。local deterministic fake-fetch smoke 目標為 +`DB_APPLY_CONTROLLED_DRY_RUN_VERIFIER_RECEIPT_PERSISTENCE_GUARD_PROOF_CLOSEOUT_READY`、 +`controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_ready_count=1`、 +`controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_check_count=12`、 +`controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_pass_count=12`、 +`controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_waiting_count=0`、 +`controlled_dry_run_verifier_no_execution_receipt_proof_closeout_ready_count=1`、 +`receipt_persistence_storage_boundary_proof_count=1`、 +`receipt_persistence_storage_boundary_proof_field_count=12`、 +`ready_for_future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof=true`、 +`can_enter_future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout=true`、 +`verifier_receipt_persistence_guard_proof_closeout_ready=true`、 +`verifier_no_execution_receipt_proof_closeout_ready=true`、 +`verifier_receipt_persistence_guard_proof_ready=true`、 +`receipt_persistence_storage_boundary_proof_bound=true`、 +`receipt_persistence_storage_boundary_locked=true`、 +`receipt_persistence_storage_write_allowed=false`、 +`receipt_persistence_storage_written=false`、 +`verifier_receipt_persistence_locked=true`、 +`verifier_receipt_persistence_allowed=false`、 +`verifier_receipt_persisted=false`、`persists_verifier_receipt=false`、 +`verifier_invocation_locked=true`、`verifier_invocation_allowed=false`、 +`verifier_invoked=false`、`verifier_receipt_present=false`、 +`dry_run_executor_invoked=false`、`runner_invocation_performed=false`、 +`endpoint_executed=false`、`sql_executed=false`、`database_written=false`、 +`ready_for_database_apply_now=false`、 +`ready_for_receipt_persistence_storage_now=false`、 +`ready_for_verifier_receipt_persistence_now=false`、 +`ready_for_verifier_invocation_now=false`、 +`ready_for_dry_run_executor_invocation_now=false`、 +`endpoint_execution_allowed=false`、`sql_execution_allowed=false`、 +`database_write_allowed=false`、`database_apply_authorized=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`stdout_included=false`、`stderr_included=false`、 +`reads_secret_count=0`、`executes_endpoint_count=0`、`executes_sql_count=0`、 +`writes_database_count=0`、`verifier_invoked_count=0`、 +`verifier_receipt_present_count=0`、`dry_run_executor_invoked_count=0`、 +`runner_invocation_performed_count=0`、`endpoint_executed_count=0`、 +`sql_executed_count=0`、`database_written_count=0`、 +`persists_verifier_receipt_count=0`、 +`receipt_persistence_storage_write_allowed_count=0`、 +`receipt_persistence_storage_written_count=0`、 +`signs_database_apply_authorization_count=0`。 + +DB apply controlled dry-run receipt persistence storage boundary proof closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-receipt-persistence-storage-boundary-proof-closeout`, +輸出 future database apply controlled dry-run storage boundary no-write ledger proof、 +controlled dry-run receipt persistence storage boundary proof closeout、storage +boundary no-write ledger proof 與 12 項 receipt persistence storage boundary +proof closeout checks;目前仍是 +controlled-dry-run-receipt-persistence-storage-boundary-proof-closeout-and-storage- +boundary-no-write-ledger-proof-only / storage-boundary-no-write-ledger-proof-only / +dry-run-only / check-mode-only / no-secret-read / no-plaintext-secret / +no-stdout-capture / no-stderr-capture / no-signature-material / +no-ledger-write / no-receipt-storage-write / no-verifier-receipt-persistence / +no-authorization-evidence-execute / no-database-apply-execute / +no-verifier-invocation / no-dry-run-executor-invocation / +no-runner-invocation / no-endpoint-execute / no-SQL-execute / no-DB-write +preview。local deterministic fake-fetch smoke 目標為 +`DB_APPLY_CONTROLLED_DRY_RUN_RECEIPT_PERSISTENCE_STORAGE_BOUNDARY_PROOF_CLOSEOUT_READY`、 +`controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_ready_count=1`、 +`controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_check_count=12`、 +`controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_pass_count=12`、 +`controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_waiting_count=0`、 +`controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_ready_count=1`、 +`storage_boundary_no_write_ledger_proof_count=1`、 +`storage_boundary_no_write_ledger_proof_field_count=12`、 +`ready_for_future_database_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof=true`、 +`can_enter_future_database_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout=true`、 +`receipt_persistence_storage_boundary_proof_closeout_ready=true`、 +`verifier_receipt_persistence_guard_proof_closeout_ready=true`、 +`receipt_persistence_storage_boundary_proof_ready=true`、 +`storage_boundary_no_write_ledger_proof_bound=true`、 +`storage_boundary_write_locked=true`、`storage_boundary_write_allowed=false`、 +`storage_boundary_written=false`、`ledger_write_allowed=false`、 +`ledger_written=false`、 +`receipt_persistence_storage_write_allowed=false`、 +`receipt_persistence_storage_written=false`、 +`verifier_receipt_persistence_locked=true`、 +`verifier_receipt_persistence_allowed=false`、 +`verifier_receipt_persisted=false`、`persists_verifier_receipt=false`、 +`verifier_invocation_locked=true`、`verifier_invocation_allowed=false`、 +`verifier_invoked=false`、`verifier_receipt_present=false`、 +`dry_run_executor_invoked=false`、`runner_invocation_performed=false`、 +`endpoint_executed=false`、`sql_executed=false`、`database_written=false`、 +`ready_for_database_apply_now=false`、 +`ready_for_storage_boundary_ledger_write_now=false`、 +`ready_for_receipt_persistence_storage_now=false`、 +`ready_for_verifier_receipt_persistence_now=false`、 +`ready_for_verifier_invocation_now=false`、 +`ready_for_dry_run_executor_invocation_now=false`、 +`endpoint_execution_allowed=false`、`sql_execution_allowed=false`、 +`database_write_allowed=false`、`database_apply_authorized=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`stdout_included=false`、`stderr_included=false`、 +`reads_secret_count=0`、`executes_endpoint_count=0`、`executes_sql_count=0`、 +`writes_database_count=0`、`verifier_invoked_count=0`、 +`verifier_receipt_present_count=0`、`dry_run_executor_invoked_count=0`、 +`runner_invocation_performed_count=0`、`endpoint_executed_count=0`、 +`sql_executed_count=0`、`database_written_count=0`、 +`ledger_write_allowed_count=0`、`ledger_written_count=0`、 +`persists_verifier_receipt_count=0`、 +`receipt_persistence_storage_write_allowed_count=0`、 +`receipt_persistence_storage_written_count=0`、 +`signs_database_apply_authorization_count=0`。 + +DB apply controlled dry-run storage boundary no-write ledger proof closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-storage-boundary-no-write-ledger-proof-closeout`, +輸出 future database apply controlled dry-run no-write ledger retention proof、 +controlled dry-run storage boundary no-write ledger proof closeout、no-write +ledger retention proof 與 12 項 storage boundary no-write ledger proof closeout +checks;目前仍是 controlled-dry-run-storage-boundary-no-write-ledger-proof- +closeout-and-no-write-ledger-retention-proof-only / +no-write-ledger-retention-proof-only / dry-run-only / check-mode-only / +no-secret-read / no-plaintext-secret / no-stdout-capture / no-stderr-capture / +no-signature-material / no-retention-write / no-ledger-write / +no-receipt-storage-write / no-verifier-receipt-persistence / +no-authorization-evidence-execute / no-database-apply-execute / +no-verifier-invocation / no-dry-run-executor-invocation / no-runner-invocation / +no-endpoint-execute / no-SQL-execute / no-DB-write preview。local +deterministic fake-fetch smoke 目標為 +`DB_APPLY_CONTROLLED_DRY_RUN_STORAGE_BOUNDARY_NO_WRITE_LEDGER_PROOF_CLOSEOUT_READY`、 +`controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_ready_count=1`、 +`controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_check_count=12`、 +`controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_pass_count=12`、 +`controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_waiting_count=0`、 +`controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_ready_count=1`、 +`no_write_ledger_retention_proof_count=1`、 +`no_write_ledger_retention_proof_field_count=12`、 +`ready_for_future_database_apply_controlled_dry_run_no_write_ledger_retention_proof=true`、 +`can_enter_future_database_apply_controlled_dry_run_no_write_ledger_retention_proof_closeout=true`、 +`storage_boundary_no_write_ledger_proof_closeout_ready=true`、 +`receipt_persistence_storage_boundary_proof_closeout_ready=true`、 +`storage_boundary_no_write_ledger_proof_ready=true`、 +`no_write_ledger_retention_proof_bound=true`、 +`ledger_retention_write_locked=true`、 +`ledger_retention_write_allowed=false`、 +`ledger_retention_written=false`、`ledger_write_allowed=false`、 +`ledger_written=false`、 +`receipt_persistence_storage_write_allowed=false`、 +`receipt_persistence_storage_written=false`、 +`verifier_receipt_persistence_locked=true`、 +`verifier_receipt_persistence_allowed=false`、 +`verifier_receipt_persisted=false`、`persists_verifier_receipt=false`、 +`verifier_invocation_locked=true`、`verifier_invocation_allowed=false`、 +`verifier_invoked=false`、`verifier_receipt_present=false`、 +`dry_run_executor_invoked=false`、`runner_invocation_performed=false`、 +`endpoint_executed=false`、`sql_executed=false`、`database_written=false`、 +`ready_for_database_apply_now=false`、 +`ready_for_no_write_ledger_retention_now=false`、 +`ready_for_storage_boundary_ledger_write_now=false`、 +`ready_for_receipt_persistence_storage_now=false`、 +`ready_for_verifier_receipt_persistence_now=false`、 +`ready_for_verifier_invocation_now=false`、 +`ready_for_dry_run_executor_invocation_now=false`、 +`endpoint_execution_allowed=false`、`sql_execution_allowed=false`、 +`database_write_allowed=false`、`database_apply_authorized=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`stdout_included=false`、`stderr_included=false`、 +`reads_secret_count=0`、`executes_endpoint_count=0`、`executes_sql_count=0`、 +`writes_database_count=0`、`verifier_invoked_count=0`、 +`verifier_receipt_present_count=0`、`dry_run_executor_invoked_count=0`、 +`runner_invocation_performed_count=0`、`endpoint_executed_count=0`、 +`sql_executed_count=0`、`database_written_count=0`、 +`ledger_retention_write_allowed_count=0`、 +`ledger_retention_written_count=0`、 +`ledger_write_allowed_count=0`、`ledger_written_count=0`、 +`persists_verifier_receipt_count=0`、 +`receipt_persistence_storage_write_allowed_count=0`、 +`receipt_persistence_storage_written_count=0`、 +`signs_database_apply_authorization_count=0`。 + +DB apply controlled dry-run no-write ledger retention proof closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-write-ledger-retention-proof-closeout`, +輸出 future database apply controlled dry-run retention boundary no-write archive proof、 +controlled dry-run no-write ledger retention proof closeout、retention boundary +no-write archive proof 與 12 項 no-write ledger retention proof closeout checks; +目前仍是 controlled-dry-run-no-write-ledger-retention-proof-closeout-and-retention- +boundary-no-write-archive-proof-only / retention-boundary-no-write-archive-proof- +only / dry-run-only / check-mode-only / no-secret-read / no-plaintext-secret / +no-stdout-capture / no-stderr-capture / no-signature-material / +no-archive-write / no-retention-write / no-ledger-write / +no-receipt-storage-write / no-verifier-receipt-persistence / +no-authorization-evidence-execute / no-database-apply-execute / +no-verifier-invocation / no-dry-run-executor-invocation / no-runner-invocation / +no-endpoint-execute / no-SQL-execute / no-DB-write preview。local +deterministic fake-fetch smoke 目標為 +`DB_APPLY_CONTROLLED_DRY_RUN_NO_WRITE_LEDGER_RETENTION_PROOF_CLOSEOUT_READY`、 +`controlled_dry_run_no_write_ledger_retention_proof_closeout_ready_count=1`、 +`controlled_dry_run_no_write_ledger_retention_proof_closeout_check_count=12`、 +`controlled_dry_run_no_write_ledger_retention_proof_closeout_pass_count=12`、 +`controlled_dry_run_no_write_ledger_retention_proof_closeout_waiting_count=0`、 +`controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_ready_count=1`、 +`retention_boundary_no_write_archive_proof_count=1`、 +`retention_boundary_no_write_archive_proof_field_count=12`、 +`ready_for_future_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof=true`、 +`can_enter_future_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout=true`、 +`no_write_ledger_retention_proof_closeout_ready=true`、 +`storage_boundary_no_write_ledger_proof_closeout_ready=true`、 +`no_write_ledger_retention_proof_ready=true`、 +`retention_boundary_no_write_archive_proof_bound=true`、 +`retention_archive_write_locked=true`、 +`retention_archive_write_allowed=false`、 +`retention_archive_written=false`、 +`ledger_retention_write_locked=true`、 +`ledger_retention_write_allowed=false`、 +`ledger_retention_written=false`、`ledger_write_allowed=false`、 +`ledger_written=false`、 +`receipt_persistence_storage_write_allowed=false`、 +`receipt_persistence_storage_written=false`、 +`verifier_receipt_persistence_locked=true`、 +`verifier_receipt_persistence_allowed=false`、 +`verifier_receipt_persisted=false`、`persists_verifier_receipt=false`、 +`verifier_invocation_locked=true`、`verifier_invocation_allowed=false`、 +`verifier_invoked=false`、`verifier_receipt_present=false`、 +`dry_run_executor_invoked=false`、`runner_invocation_performed=false`、 +`endpoint_executed=false`、`sql_executed=false`、`database_written=false`、 +`ready_for_database_apply_now=false`、 +`ready_for_retention_boundary_archive_now=false`、 +`ready_for_no_write_ledger_retention_now=false`、 +`ready_for_storage_boundary_ledger_write_now=false`、 +`ready_for_receipt_persistence_storage_now=false`、 +`ready_for_verifier_receipt_persistence_now=false`、 +`ready_for_verifier_invocation_now=false`、 +`ready_for_dry_run_executor_invocation_now=false`、 +`endpoint_execution_allowed=false`、`sql_execution_allowed=false`、 +`database_write_allowed=false`、`database_apply_authorized=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`stdout_included=false`、`stderr_included=false`、 +`reads_secret_count=0`、`executes_endpoint_count=0`、`executes_sql_count=0`、 +`writes_database_count=0`、`verifier_invoked_count=0`、 +`verifier_receipt_present_count=0`、`dry_run_executor_invoked_count=0`、 +`runner_invocation_performed_count=0`、`endpoint_executed_count=0`、 +`sql_executed_count=0`、`database_written_count=0`、 +`retention_archive_write_allowed_count=0`、 +`retention_archive_written_count=0`、 +`ledger_retention_write_allowed_count=0`、 +`ledger_retention_written_count=0`、 +`ledger_write_allowed_count=0`、`ledger_written_count=0`、 +`persists_verifier_receipt_count=0`、 +`receipt_persistence_storage_write_allowed_count=0`、 +`receipt_persistence_storage_written_count=0`、 +`signs_database_apply_authorization_count=0`。 + +DB apply controlled dry-run retention boundary no-write archive proof closeout 已接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-retention-boundary-no-write-archive-proof-closeout`, +輸出 future database apply controlled dry-run archive retention sealed handoff proof、 +controlled dry-run retention boundary no-write archive proof closeout、archive +retention sealed handoff proof 與 12 項 archive proof closeout checks;目前 +仍是 controlled-dry-run-retention-boundary-no-write-archive-proof-closeout-and- +archive-retention-sealed-handoff-proof-only / archive-retention-sealed-handoff- +proof-only / dry-run-only / check-mode-only / no-secret-read / +no-plaintext-secret / no-stdout-capture / no-stderr-capture / +no-signature-material / no-handoff-write / no-archive-write / +no-retention-write / no-ledger-write / no-receipt-storage-write / +no-verifier-receipt-persistence / no-authorization-evidence-execute / +no-database-apply-execute / no-verifier-invocation / +no-dry-run-executor-invocation / no-runner-invocation / no-endpoint-execute / +no-SQL-execute / no-DB-write preview。local deterministic fake-fetch smoke +目標為 +`DB_APPLY_CONTROLLED_DRY_RUN_RETENTION_BOUNDARY_NO_WRITE_ARCHIVE_PROOF_CLOSEOUT_READY`、 +`controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_ready_count=1`、 +`controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_check_count=12`、 +`controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_pass_count=12`、 +`controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_waiting_count=0`、 +`controlled_dry_run_no_write_ledger_retention_proof_closeout_ready_count=1`、 +`archive_retention_sealed_handoff_proof_count=1`、 +`archive_retention_sealed_handoff_proof_field_count=12`、 +`ready_for_future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof=true`、 +`can_enter_future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof_closeout=true`、 +`retention_boundary_no_write_archive_proof_closeout_ready=true`、 +`no_write_ledger_retention_proof_closeout_ready=true`、 +`retention_boundary_no_write_archive_proof_ready=true`、 +`archive_retention_sealed_handoff_proof_bound=true`、 +`sealed_handoff_write_locked=true`、 +`sealed_handoff_write_allowed=false`、 +`sealed_handoff_written=false`、 +`retention_archive_write_locked=true`、 +`retention_archive_write_allowed=false`、 +`retention_archive_written=false`、 +`ledger_retention_write_locked=true`、 +`ledger_retention_write_allowed=false`、 +`ledger_retention_written=false`、`ledger_write_allowed=false`、 +`ledger_written=false`、 +`receipt_persistence_storage_write_allowed=false`、 +`receipt_persistence_storage_written=false`、 +`verifier_receipt_persistence_locked=true`、 +`verifier_receipt_persistence_allowed=false`、 +`verifier_receipt_persisted=false`、`persists_verifier_receipt=false`、 +`verifier_invocation_locked=true`、`verifier_invocation_allowed=false`、 +`verifier_invoked=false`、`verifier_receipt_present=false`、 +`dry_run_executor_invoked=false`、`runner_invocation_performed=false`、 +`endpoint_executed=false`、`sql_executed=false`、`database_written=false`、 +`ready_for_database_apply_now=false`、 +`ready_for_archive_retention_sealed_handoff_write_now=false`、 +`ready_for_retention_boundary_archive_now=false`、 +`ready_for_no_write_ledger_retention_now=false`、 +`ready_for_storage_boundary_ledger_write_now=false`、 +`ready_for_receipt_persistence_storage_now=false`、 +`ready_for_verifier_receipt_persistence_now=false`、 +`ready_for_verifier_invocation_now=false`、 +`ready_for_dry_run_executor_invocation_now=false`、 +`endpoint_execution_allowed=false`、`sql_execution_allowed=false`、 +`database_write_allowed=false`、`database_apply_authorized=false`、 +`executes_endpoint=false`、`executes_sql=false`、`writes_database=false`、 +`stdout_included=false`、`stderr_included=false`、 +`sealed_handoff_write_allowed_count=0`、 +`sealed_handoff_written_count=0`、 +`retention_archive_write_allowed_count=0`、 +`retention_archive_written_count=0`、 +`ledger_retention_write_allowed_count=0`、 +`ledger_retention_written_count=0`、 +`ledger_write_allowed_count=0`、`ledger_written_count=0`、 +`persists_verifier_receipt_count=0`、 +`receipt_persistence_storage_write_allowed_count=0`、 +`receipt_persistence_storage_written_count=0`、 +`signs_database_apply_authorization_count=0`。下一個 P0 +是 future database apply controlled dry-run archive retention sealed handoff proof closeout / +sealed handoff verifier transfer proof,仍不是直接 DB apply。 + +2026-07-01 正式 readback 收斂:上述 retention boundary no-write archive proof +closeout route 已部署到 V10.725 正式環境,預設 `response_mode=compact`, +不再回傳完整 nested proof payload;完整 payload 僅允許 `full=1`。正式 +驗證為 `HTTP 200`、`content-length=8697`、`checks=12`、 +`writes_database_count=0`、`executes_sql_count=0`、 +`sealed_handoff_write_locked_count=1`、sealed handoff manifest hash 長度 64、 +`database_apply_authorized=false`、`writes_database=false`,`/health` 仍是 +healthy PostgreSQL `V10.725`。 + +DB apply controlled dry-run archive retention sealed handoff proof closeout 已本地 +接上 +`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-archive-retention-sealed-handoff-proof-closeout`, +預設 `response_mode=compact`,輸出 future database apply controlled dry-run +sealed handoff verifier transfer proof、archive retention sealed handoff proof +closeout、sealed handoff verifier transfer proof 與 12 項 closeout checks。 +local focused tests 已通過:`archive_retention_sealed_handoff_proof_closeout` +為 `2 passed`,retention boundary + archive retention chain 為 `5 passed`, +完整 PChome mapping backlog test file 為 `218 passed`。正式 V10.725 已 +部署並驗證:`HTTP 200`、`content-length=8425`、`response_mode=compact`、 +`checks=12`、`writes_database_count=0`、`executes_sql_count=0`、 +`verifier_invoked_count=0`、verifier transfer manifest hash 長度 64、 +`database_apply_authorized=false`、`writes_database=false`,`/health` 仍是 +healthy PostgreSQL `V10.725`。目前仍是 archive-retention-sealed-handoff-proof-closeout-and- +sealed-handoff-verifier-transfer-proof-only / dry-run-only / check-mode-only / +no-secret-read / no-plaintext-secret / no-signature-material / +no-verifier-transfer-write / no-handoff-write / no-verifier-invocation / +no-verifier-receipt-persistence / no-authorization-evidence-execute / +no-database-apply-execute / no-endpoint-execute / no-SQL-execute / no-DB-write +preview。下一個 P0 是 sealed handoff verifier transfer proof closeout,仍不是 +直接 DB apply。 + ================================================================================ 前端 V3 守門落地 + FastAPI 重新評估 (2026-05-12) [IN PROGRESS] ================================================================================ @@ -6,7 +2080,7 @@ 【已完成】 - V10.601 收斂 Gemini / 111 治理與全 repo 已知密鑰清除:正式 `ai_calls` 近 24 小時與近 7 天 provider 彙總未見 Gemini 出站;舊 K8s manifest、n8n workflow、監控/auto-repair scripts、Superset 文件、Google Drive token 檔與歷史文件中的已知實密鑰全部改為占位符,並補測試禁止 Google API/OAuth key、Telegram token、Ollama Cloud key、Superset 預設密碼再次入庫;OpenClaw 日/週/月/Meta 等敘事長報告改為 GCP-A/GCP-B only,不再讓 `openclaw_meta` 在 GCP 超時後落到 111,避免 111 被長文生成壓高負載。 - V10.600 收斂 AI Intelligence 競品表前台文案:PChome 競品卡片 footer 不再顯示 `TTL: 6h`、比對門檻等工程參數,改為「僅顯示已通過身份比對的競品」;`identity_v2`、`match_type_exact`、`price_alert_exact`、`evidence_*`、`match_*` 等內部診斷 tag 只會轉成營運可讀的中文 badge,未知 tag 直接隱藏,避免把 matcher 內部碼或實驗性標記露給使用者。 - - V10.599 重整 PChome 比價覆核工作台 UX 並補全站巡檢能力:覆核頁不再沿用首頁商品表格,也不再把 `matcher_rescore`、`stored_status`、`rescore_accepted_current`、`HITL`、`COMPLETE` 等內部診斷/狀態碼輸出到前台或 tooltip;改為「商品 / MOMO、PChome 候選、覆核判讀、下一步、紀錄」六欄工作流。同步修正 catalog review status 的前台語義、決策信封中文標籤、局部 1540px 橫向工作台、手機版欄位 label,並把覆核狀態分段列改為自適應 grid,避免 chip 造成桌面/平板/手機視覺溢出;`check_responsive_overflow.js` 改為逐頁輸出、HTTPS context、commit+body ready、timeout 後安全收尾,讓桌面/平板/手機全站 UX 巡檢可追蹤;topbar AI 觀測台 indicator 增加前端 60 秒 session cache / 2.5 秒 abort 與後端 30 秒 cache,避免每頁跳轉重複打 DB 查詢拖慢全站;`market_intel/disabled.html` 從 1MB 大型停用頁改為輕量狀態頁,保留狀態與正式操作入口,避免停用模組拖慢巡檢與使用者操作;新增憲法第 14.2 條與測試 guard,禁止把工作視窗溝通、施工紀錄或版本發布說明放到使用者可見前端頁面;ICAIM 競情 API 改為 120 秒短快取、5 秒 PostgreSQL statement timeout、stale 快照降級與 LATERAL 最新價查詢,避免 AI 競情看板重查詢拖慢全站。 + - V10.599 重整 PChome 比價覆核工作台 UX 並補全站巡檢能力:覆核頁不再沿用首頁商品表格,也不再把 `matcher_rescore`、`stored_status`、`rescore_accepted_current`、`AI 例外決策`、`COMPLETE` 等內部診斷/狀態碼輸出到前台或 tooltip;改為「商品 / MOMO、PChome 候選、覆核判讀、下一步、紀錄」六欄工作流。同步修正 catalog review status 的前台語義、決策信封中文標籤、局部 1540px 橫向工作台、手機版欄位 label,並把覆核狀態分段列改為自適應 grid,避免 chip 造成桌面/平板/手機視覺溢出;`check_responsive_overflow.js` 改為逐頁輸出、HTTPS context、commit+body ready、timeout 後安全收尾,讓桌面/平板/手機全站 UX 巡檢可追蹤;topbar AI 觀測台 indicator 增加前端 60 秒 session cache / 2.5 秒 abort 與後端 30 秒 cache,避免每頁跳轉重複打 DB 查詢拖慢全站;`market_intel/disabled.html` 從 1MB 大型停用頁改為輕量狀態頁,保留狀態與正式操作入口,避免停用模組拖慢巡檢與使用者操作;新增憲法第 14.2 條與測試 guard,禁止把工作視窗溝通、施工紀錄或版本發布說明放到使用者可見前端頁面;ICAIM 競情 API 改為 120 秒短快取、5 秒 PostgreSQL statement timeout、stale 快照降級與 LATERAL 最新價查詢,避免 AI 競情看板重查詢拖慢全站。 - V10.584 補 PChome Nick 去重與 stale recovery 單品窄門:`Nick` 先去 HTML / 行銷星號 / 重複品名,避免 `29g`、`100ml` 被同一商品副標重複計數成 `component_count_conflict`;同步新增 NIVEA 妮維雅霜 100ml、Schick 舒綺敏感肌除毛刀片 3 入、TS6 沁涼潔淨慕斯 100g 的具名 exact total-price alignment。IBL 沐浴精+洗髮精 vs 洗髮精仍保留 identity review,唇釉色號/目錄款與 Paula's Choice 效期/金蓋差異仍不自動寫正式價差。 - V10.583 補 Paula's Choice 身體乳 PChome Nick 具名 alignment:`2%水楊酸身體乳210ml二入` 可和 PChome `Nick` 補出的 `水楊酸身體乳雙入組 / 210ml x2` 對齊,進 `exact / total_price / price_alert_exact`;但 `118ml二入組(金蓋限定版)` 對上 PChome 效期品仍保留 `manual_review / identity_review`,不泛用放寬中文入數。 - V10.582 補 PChome 比價通知專業分級與 Nick 副標身份證據:NemoTron 價格決策信封現在保留 `momo_price`、`competitor_price`、`candidate_gap_pct` 與 `sales_7d_delta_pct`,EventRouter / Telegram 模板會把 `match_type / price_basis / alert_tier` 翻成「直接價格威脅、單位價覆核、身份覆核、壓制告警」與操作邊界;PChome crawler 會保留 `Nick` 副標為 `match_name` 給 matcher 使用,UI/DB 顯示仍維持原品名,讓容量、入數、濃度資訊可參與比對。 @@ -16,14 +2090,14 @@ - V10.578 修正 Code Review 靜態掃描 timeout 誤報:Hermes deterministic scan 對 `requests.get/post/...` 會檢查同一呼叫 block 的後續行,多行呼叫已帶 `timeout=` 時不再報「HTTP request 未設定 timeout」。避免 V10.577 的 preflight helper 因多行格式被自己誤判為 MEDIUM。 - V10.577 補 Code Review Ollama host preflight:OpenClaw 架構評估在 explicit GCP host generate 前先以短 `/api/version` 探測健康度,GCP-A 不通時會快速跳 GCP-B,不再等 15 秒 generate timeout;仍維持 GCP-A/GCP-B 優先、111 預設禁用、Gemini hard-disabled 預設不呼叫。 - V10.576 補 PChome backfill backlog 的型錄 lane counts,讓 `/api/ai/pchome-match/backfill/status` 也能回傳 `catalog_variant_review`、`catalog_unit_review`、`catalog_identity_review` 三條操作隊列;同版修正 `OllamaService.generate(allow_111_fallback=False)`,當 lazy resolver 快取到 111 時會強制改試 GCP-A/GCP-B allowlist,不再直接 `all 0 hosts failed`,且仍不把長分析推給 111。 - - V10.575 拆分 PChome 型錄可比覆核 lane:`catalog_comparable` 不再只是一個總數,正式拆成 `catalog_variant_review`(選項/色號/款式待核)、`catalog_unit_review`(入數/檔期/商業條件待核)與 `catalog_identity_review`(身份採用待核)。Coverage、review queue filter、Dashboard 分段、decision envelope、Webcrumbs host data 都共用同一套 SQL helper 與 metadata;仍維持 HITL、不自動寫正式價差,讓營運可批次清理最有機會轉成單位價或正式身份的候選。 - - V10.574 接上 PChome 型錄/任選可比覆核隊列:沿用 V10.572 的 `catalog_comparable_count` 安全口徑,將高分、無 hard veto、具同品線身份證據但仍有任選/型錄/商業條件待確認的 `true_low_confidence` 候選,拆成獨立 `catalog_comparable` 篩選與 decision envelope。此隊列仍維持 HITL,不寫入正式 `competitor_prices`、不算 exact matched,並把「型錄可比」與真正「證據不足」分開,讓營運可以先批次處理最有機會轉成單位價或正式身份的候選。 + - V10.575 拆分 PChome 型錄可比覆核 lane:`catalog_comparable` 不再只是一個總數,正式拆成 `catalog_variant_review`(選項/色號/款式待核)、`catalog_unit_review`(入數/檔期/商業條件待核)與 `catalog_identity_review`(身份採用待核)。Coverage、review queue filter、Dashboard 分段、decision envelope、Webcrumbs host data 都共用同一套 SQL helper 與 metadata;仍維持 AI 例外決策、不自動寫正式價差,讓營運可批次清理最有機會轉成單位價或正式身份的候選。 + - V10.574 接上 PChome 型錄/任選可比覆核隊列:沿用 V10.572 的 `catalog_comparable_count` 安全口徑,將高分、無 hard veto、具同品線身份證據但仍有任選/型錄/商業條件待確認的 `true_low_confidence` 候選,拆成獨立 `catalog_comparable` 篩選與 decision envelope。此隊列仍維持 AI 例外決策,不寫入正式 `competitor_prices`、不算 exact matched,並把「型錄可比」與真正「證據不足」分開,讓營運可以先批次處理最有機會轉成單位價或正式身份的候選。 - V10.574 新增市場情報 Source Governance → Fetch Target bridge:新增 `/api/market_intel/mcp_fetch_target_source_governance_review`、市場情報頁 bridge panel 與 deployment readiness smoke target,交叉審核 Professional Source Governance 與 MCP Fetch Target Review,要求每個 target `platform_code/source_key` 都能對上已通過治理的公開 source contract;仍不抓外站、不讀 robots/sitemap、不開 DB、不寫檔、不執行 CLI、不掛 scheduler。 - V10.572 新增 PChome 決策支援覆蓋率:不放寬 `matched` / `decision_ready` 的 exact identity 門檻,另外把高分、無 hard veto、具同品線與規格證據,但因「任選 / 色號 / 型錄 / 即期」仍需覆核的候選,納入 `catalog_comparable_count` 與 `decision_support_rate`。Dashboard、當日業績、成長分析與 backfill 狀態摘要同步顯示「決策支援覆蓋率 / 精準可告警覆蓋 / 型錄可比 / 單位價」,讓覆蓋率提升建立在可解釋情報分層上,而不是把非 exact 商品硬寫成正式同款。 - V10.571 提升 PChome pending 覆蓋率搜尋召回:`PCHOME_FEEDER_MAX_SEARCH_TERMS` 預設由 5 提升到 6,新增 `PCHOME_FEEDER_SEARCH_COVERAGE_RESCUE_ENABLED`,在主要搜尋詞與原始名稱 fallback 之間插入狹義 coverage rescue terms。搜尋詞會保留 `5.5g`、`2.4g` 等小數規格,不再變成 `5 5g` / `2 4g`;同時排除外出清潔、卸除髒汙、卸防曬等非身份核心噪音。正式 pilot 顯示 CeraVe / TUNEMAKERS / Embryolisse / Neogence / NIVEA 這類雙語品牌商品常卡在 PChome 搜尋召回,因此補上「英文品牌 + 中文品牌 + 核心身份 + 規格」窄搜尋詞;「品牌 + 品類 + 規格」仍只開給安全品類,避免為了拉 pending 覆蓋率引入假陽性。 - V10.570 補 PChome 身份 / 報價證據契約:matcher 的 `match_diagnostic_json` 新增 `identity_evidence`、`offer_evidence`,把品牌、品類、identity anchor、型號、規格、入數與 variant guardrail 拆成結構化證據;覆核隊列與 decision envelope 新增 `difference_highlights`,可直接指出容量、入數、色號、香味、款式、補充包、檔期組合等差異。價格明確標記為 offer evidence,不再被誤當身份證據,Dashboard / PPT / OpenClaw / Webcrumbs 能共用同一份比對證據。 - 外部專業 benchmark 固定節奏:已建立每週一 09:30 自動檢視,並新增 `docs/guides/external_professional_benchmark.md`,把 Google Merchant Center、Google Product structured data、Schema.org Product/Offer/AggregateOffer 與 Baymard 電商 UX 做法轉成可落地準則:identity evidence、fresh offer、review 差異高亮、PPT/AI evidence 分層。 - - V10.565 補 PChome 覆蓋率操作建議:`/api/ai/pchome-match/backfill/status` 會把低覆蓋率拆成 `operation_backlog`,分別列出刷新舊 identity、重評近門檻、補抓未配對、人工覆核、單位價覆核與過期搜尋救援預覽;同時回傳 `recommended_next_action`,Dashboard 狀態摘要會顯示「建議執行比價補強 / 刷新過期 identity / 處理覆核」等下一步,讓覆蓋率 KPI 直接連到可執行行動。 + - V10.565 補 PChome 覆蓋率操作建議:`/api/ai/pchome-match/backfill/status` 會把低覆蓋率拆成 `operation_backlog`,分別列出刷新舊 identity、重評近門檻、補抓未配對、AI 例外決策、單位價覆核與過期搜尋救援預覽;同時回傳 `recommended_next_action`,Dashboard 狀態摘要會顯示「建議執行比價補強 / 刷新過期 identity / 處理覆核」等下一步,讓覆蓋率 KPI 直接連到可執行行動。 - V10.563 收斂正式 preview 假可救候選:M.A.C 超持妝輕透濾鏡蜜粉若只有 PChome 端出現明確色號(例如 `#絕絕紫`),會標成 `variant_selection_review` 並維持 `true_low_confidence`,不再佔 recoverable 池;SAUGELLA 賽吉兒菁萃潔浴凝露新增潤澤 / 日用型 / 加強 / 黃金女郎型變體互斥,避免同品線不同私密清潔款式被誤救成 matched。 - V10.566 新增市場情報 Professional Source Governance:把 robots/REP、sitemap/lastmod、JSON-LD / schema.org structured data、canonical URL、rate limit、公開資料邊界、provenance、snapshot hash 與 idempotency key 變成可審核 source contract。新增 `/api/market_intel/mcp_professional_source_governance` 與市場情報頁卡片、deployment readiness smoke target;API/UI 只審核操作員貼回的治理摘要,不抓外站、不讀 robots/sitemap、不開 DB、不寫檔、不掛 scheduler,後續 fetch target review 才能引用通過治理的來源。 - V10.561 補 PChome 比價補強前端分段回饋:Dashboard 的 PChome 卡片從「補抓產線」改為「比價補強產線」,按鈕與確認文案同步說明會先刷新舊 identity、再重評近門檻與補抓未配對;結果區新增刷新 / 重評 / 補抓三段 matched/total 摘要,避免後端已完成分段統計但操作員仍只看到一個籠統成功數。 @@ -33,36 +2107,36 @@ - V10.557 收緊 focused reason-based 回刷 guard:上一版 reason-based 回刷現在不只要求 `focused_exact_total_price_safe`,還必須同時命中一條具名 `focused_exact_identity_*` 且該 identity 屬於 matcher 的 total-price safe set。這避免未來只有總開關、缺少具名身份證據的舊 attempt 被納入回刷;rom&nd / Solone / Summer’s Eve 等 review-only focused line 仍被測試鎖在自動價差線外。 - V10.556 修 Ollama GCP-B model fallback:GCP-B 若缺 coder/large 指定模型,先用 host-compatible fallback `gemma3:4b` 留在 GCP-B,不直接把流量推到 111;`model not found` 404 視為模型缺失,不再把整台 GCP-B 標 unhealthy。主機順序仍維持 GCP-A → GCP-B → 111。 - V10.555 補 focused total-price reason-based 回刷:`_fetch_retryable_candidate_skus()` 新增一條結構化 reason 窄門,只要舊 attempt 已帶 `focused_exact_total_price_safe` 且命中 matcher 的 `FOCUSED_IDENTITY_TOTAL_PRICE_REASONS`,即可進近門檻重評;仍要求無 hard veto、`exact_identity`、分數下限,並排除 commercial / variant / count / bundle 等阻擋理由。這讓已經被 matcher 明確判為 total-price exact 的舊候選不再依賴手寫商品名 SQL 才能回刷,同時 rom&nd / Solone / Summer’s Eve 等 review-only 品線仍不會進自動價差線。 - - V10.554 接線香氛 / 精油 focused exact 回刷:Herb24 晨霧純精油擴香儀黑色、Pavaruni 40 香味 10ml 精油、Pavaruni 20 香味 450g 香氛蠟燭、Derma 大地有機植萃護膚油 150ml 現在明確標記 `focused_exact_total_price_safe`,並接進 `_fetch_retryable_candidate_skus()` 近門檻舊候選回刷。此入口只收 `low_score / refresh_low_score / true_low_confidence` 中命中精準名稱錨點、無 hard veto、`exact_identity` 且沒有變體 / 商業條件 / 件數衝突的候選;Laundrin、好物良品融蠟燈、Yuskin 等仍保留人工覆核,不為了拉覆蓋率強推自動價差。 + - V10.554 接線香氛 / 精油 focused exact 回刷:Herb24 晨霧純精油擴香儀黑色、Pavaruni 40 香味 10ml 精油、Pavaruni 20 香味 450g 香氛蠟燭、Derma 大地有機植萃護膚油 150ml 現在明確標記 `focused_exact_total_price_safe`,並接進 `_fetch_retryable_candidate_skus()` 近門檻舊候選回刷。此入口只收 `low_score / refresh_low_score / true_low_confidence` 中命中精準名稱錨點、無 hard veto、`exact_identity` 且沒有變體 / 商業條件 / 件數衝突的候選;Laundrin、好物良品融蠟燈、Yuskin 等仍保留AI 例外決策,不為了拉覆蓋率強推自動價差。 - V10.553 優化 current PPT/AI 比價結果查詢:`fetch_competitor_comparison_results()` 的 current/latest MOMO 價格改用 `JOIN LATERAL` 取單品最新價,移除 `ROW_NUMBER() OVER (PARTITION BY p.id ...)` window scan;歷史報表的 `end_date` cutoff 仍保留在 lateral 子查詢內,維持「指定期間截止日前最新 MOMO 價」語意不變。這能降低簡報、OpenClaw/AI payload 與比價匯出在大量 price_records 下的查詢成本。 - V10.552 收斂決策查詢的新鮮度口徑:`fetch_top_competitor_risks()`、PChome review queue、review sample 與 current PPT/AI 比價結果都不再把 `expires_at IS NULL` 當成有效現價,只接受 `expires_at > CURRENT_TIMESTAMP` 的 PChome identity_v2 價格。未知新鮮度只留在 coverage 的診斷欄位與 V10.551 刷新入口,不再進入價格風險、簡報、AI 決策或覆核排除條件。 - V10.551 收斂未知新鮮度刷新與補抓排序:`_fetch_expired_identity_skus()` / `_fetch_expired_identity_recovery_skus()` 將 `expires_at IS NULL` 視為必須刷新或可搜尋救援的未知新鮮度 identity,和 V10.549「未知新鮮度不算可決策覆蓋率」口徑對齊;兩條路徑改用 `JOIN LATERAL` 取最新 MOMO 價,移除 product-wide window scan。`_fetch_unmatched_priority_skus()` 也改用 lateral 最新價,並優先重搜低風險 `no_result / refresh_no_result`,讓 V10.550 的安全召回詞先用在最可能被救回的商品。 - V10.550 補安全搜尋召回詞:`_build_variant_recall_search_plan()` 對低風險穩定品類新增 `品牌 + 品類` 的補搜尋詞,讓 `no_result / refresh_no_result` 更有機會找到 PChome 候選後再交給 matcher 安全判斷;美甲片、指甲油、唇彩、香氛/精油、粉底、防曬、任選/色號/款式等高 variant 風險商品不走通用召回,DASHING DIVA 仍只走既有 line-specific recall + sort fallback。此變更不改 `MIN_MATCH_SCORE`、hard veto、fresh-search write safety 或 stronger existing match 覆寫保護。 - V10.549 收斂比價新鮮度 KPI 口徑:coverage cache 升到 v10,`expires_at IS NULL` 不再算進「可用比價 / decision ready」,改拆成 `unknown_freshness_matches` / `unknown_freshness_count`,避免沒有到期時間的舊資料被當成可直接決策的新鮮價格。Dashboard / daily / growth 同步顯示未知新鮮度與「未形成有效身份配對」,並把 PChome/MOMO 價格方向文案改成 `PChome 價格壓力` / `MOMO 價格優勢`,降低誤讀。 - V10.548 接線更多 focused exact 舊候選回刷:把 matcher 已驗證可安全走 total-price 的 3W CLINIC 膠原蛋白粉底液 50ml x2、花美水 Moisture/Inclear 1.7g x3、KUSSEN 寶寶益菌屁屁膏 50ml 3 入、Lab52 齒妍堂嬰幼兒/汪汪隊牙刷 2 入接進 `_fetch_retryable_candidate_skus()` focused true-low / rescore 窄門。這只擴大「舊候選可被新版 matcher 重評」的入口,不改 `MIN_MATCH_SCORE`、hard veto、auto price write safety 或既有覆寫保護。 - - V10.547 強化單位價覆核洞察:`manual_unit_price_required` 不再只是人工狀態,覆核隊列與商品看板會重新帶出單位價換算、MOMO/PChome 單位價方向、差距百分比與處理建議;決策信封 / OpenClaw / PPT 摘要可讀到 `unit_price_insight`。人工覆核寫回也會保留原始 `match_diagnostic_json` / comparison mode / diagnostic codes,避免後續簡報、審計或 AI 策略只剩人工文案而失去 matcher 證據鏈。 - - V10.546 補近門檻舊候選回刷隊列:`run_retryable_candidate_revalidation()` 新增 `legacy_unmasked_attempt`,當最新狀態是 `no_result` / `refresh_no_result` / `expired_match` 時,可回撈同 SKU 早期近門檻候選交給最新版 matcher 重評;仍要求 candidate id、分數下限、無 hard veto、exact_identity,且不打開人工否決、單位價、identity_veto 或 protected existing match。 - - V10.545 收斂 Dashboard 比價覆蓋率口徑:coverage cache 升到 v9,新增身份覆蓋、可用比價、新鮮度、待補身份、過期身份與人工閉環欄位;商品看板和 PChome 覆核頁改只把真正待處理狀態算進「比價覆核」,人工已否決 / 人工單位價 / 需補研究改列為人工閉環;PChome competitor map 只吃有效價格、新鮮、identity_v2 最新 row,資料新鮮度也改看可用比價 row。 - - V10.544 收斂變體安全與 YES 指甲工具線:新增 YES 德悅氏指甲剪附除垢銼刀、腳皮銼腳板、藍寶石銼刀、三面拋光棒與 6/8cm 指甲剪的精準 total-price 線,要求同品牌、同工具名稱、同尺寸與同亮面/霧面/可收納/三面/不掉屑等款式訊號;同步接進 revalidation SQL。新增 MUJI / COCODOR 未知香味差異與 OPI 無型號不同色名 hard veto,HOOOME 暖燈材質差留人工覆核,搜尋詞也會優先帶香味/色名,提升 crawler 精準候選率。 - - V10.543 打通 `rescore_accepted_current` 窄門回刷:已進人工覆核池的候選若命中具名 focused exact 線,可進 `run_retryable_candidate_revalidation()` 重新評分;新增 SK-II 青春露 330ml 兩入、AMIINO 安美諾 30ml、YES 腳指甲剪刀 10.5cm、YES 極細指甲緣硬皮剪刀 9cm 的安全 total-price 線,並補 ANNY / OPI 指甲油型號 code hard veto,避免不同色號被錯配。 + - V10.547 強化單位價覆核洞察:`manual_unit_price_required` 不再只是人工狀態,覆核隊列與商品看板會重新帶出單位價換算、MOMO/PChome 單位價方向、差距百分比與處理建議;決策信封 / OpenClaw / PPT 摘要可讀到 `unit_price_insight`。AI 例外決策寫回也會保留原始 `match_diagnostic_json` / comparison mode / diagnostic codes,避免後續簡報、審計或 AI 策略只剩人工文案而失去 matcher 證據鏈。 + - V10.546 補近門檻舊候選回刷隊列:`run_retryable_candidate_revalidation()` 新增 `legacy_unmasked_attempt`,當最新狀態是 `no_result` / `refresh_no_result` / `expired_match` 時,可回撈同 SKU 早期近門檻候選交給最新版 matcher 重評;仍要求 candidate id、分數下限、無 hard veto、exact_identity,且不打開AI 否決、單位價、identity_veto 或 protected existing match。 + - V10.545 收斂 Dashboard 比價覆蓋率口徑:coverage cache 升到 v9,新增身份覆蓋、可用比價、新鮮度、待補身份、過期身份與 AI 例外閉環欄位;商品看板和 PChome 覆核頁改只把真正待處理狀態算進「比價覆核」,AI 已否決 / AI 單位價 / 需補研究改列為AI 例外閉環;PChome competitor map 只吃有效價格、新鮮、identity_v2 最新 row,資料新鮮度也改看可用比價 row。 + - V10.544 收斂變體安全與 YES 指甲工具線:新增 YES 德悅氏指甲剪附除垢銼刀、腳皮銼腳板、藍寶石銼刀、三面拋光棒與 6/8cm 指甲剪的精準 total-price 線,要求同品牌、同工具名稱、同尺寸與同亮面/霧面/可收納/三面/不掉屑等款式訊號;同步接進 revalidation SQL。新增 MUJI / COCODOR 未知香味差異與 OPI 無型號不同色名 hard veto,HOOOME 暖燈材質差留AI 例外決策,搜尋詞也會優先帶香味/色名,提升 crawler 精準候選率。 + - V10.543 打通 `rescore_accepted_current` 窄門回刷:已進 AI 例外池的候選若命中具名 focused exact 線,可進 `run_retryable_candidate_revalidation()` 重新評分;新增 SK-II 青春露 330ml 兩入、AMIINO 安美諾 30ml、YES 腳指甲剪刀 10.5cm、YES 極細指甲緣硬皮剪刀 9cm 的安全 total-price 線,並補 ANNY / OPI 指甲油型號 code hard veto,避免不同色號被錯配。 - V10.542 拆開「可用比價覆蓋率」與「身份覆蓋率」:`decision_ready_rate = fresh identity / ACTIVE 商品數`,Dashboard 第一張 KPI 改顯示真正可進入決策、圖表、簡報的比價資料比例;daily / growth / Webcrumbs / OpenClaw payload 同步輸出,避免把身份覆蓋、新鮮率、價格可用率混成單一數字。 - V10.541 補正式覆核頁高信心 exact 線:The Ordinary 咖啡因 EGCG 單側漏 30ml、Natures Care 綿羊油同入數 125ml/125m、TOMOON 指甲剪同 L/S 尺寸、HH 私密潔淨露+衣物手洗精雙 200ml、SEBAMED 護潔露 200ml x2、YES 德悅氏 9cm 剪刀;都同步進 revalidation SQL,且 TOMOON/O.P.I 不同型號或尺寸仍不得自動通過。 - - V10.540 補 O.P.I 類光繚指彩精準型號線:雙方同為 O.P.I 類光繚 / 如膠似漆指甲油或指彩,且共享 `ISL...` 型號 token 時才允許 total-price;不同型號/色號仍不得自動寫入。同步把此族群接進 `true_low_confidence` revalidation 窄門,降低高信心指彩候選卡在人工覆核池的比例。 + - V10.540 補 O.P.I 類光繚指彩精準型號線:雙方同為 O.P.I 類光繚 / 如膠似漆指甲油或指彩,且共享 `ISL...` 型號代碼時才允許 total-price;不同型號/色號仍不得自動寫入。同步把此族群接進 `true_low_confidence` revalidation 窄門,降低高信心指彩候選卡在 AI 例外池的比例。 - V10.539 補 PChome 任選 catalog focused exact 線:FLORTTE 水果沙拉眼線液筆 0.5ml、露得清護手霜 56g 無香/有香、Kanebo ALLIE 持采亮化 UV 防曬水凝乳 60g 雙方皆任選時可走 total-price;同步接進 revalidation queue。focused bypass 新增 commercial condition 防線,`即期品` 等商業狀態差異不會被自動寫入正式價差。 - V10.538 修 ai_calls provider CHECK 對齊:Hermes/Ollama 全失敗或未選定 host 時的 `ollama_other` 只作 telemetry bucket,migration 043 放行此值,`ai_call_logger` 也會將空值/unknown/非白名單 provider 正規化,避免觀測寫入失敗。 - V10.537 將 V10.536 focused exact 線接進 `run_retryable_candidate_revalidation()` 窄門:既有 `true_low_confidence` 舊候選若命中新品線且無 hard veto / 型別、款式、香味、件數、組合阻擋,就可重新走 matcher 寫入正式價差;有色號/香味/即期等阻擋仍不進回刷。 - V10.536 補 PChome 高分 `true_low_confidence` 安全救回線:新增花美水 Relax 薰衣草潤滑凝膠 1.7g x3、St.Clare 私密呼呼慕斯 x2 / 慕斯+噴霧組、BIOPEUTIC 果酸煥膚水凝乳 20% 150ml、台塑生醫嬰兒沐浴洗髮 3 件組、Elizabeth Arden 八小時護唇膏 SPF15 3.7g x3、理膚寶水全面修復潤唇膏 7.5ml focused total-price 規則;這些都要求同品牌、同品線與同規格/同組合,仍保留色號、香味、款式敏感品的 `variant_selection_review` 防線。 - V10.535 修 ElephantAlpha 價格 trigger statement timeout:`price_drop_alert` / `market_opportunity` / DB evidence prefetch 改為先篩最近有效 PChome identity_v2,再用 `JOIN LATERAL` 查單一 SKU 最新 MOMO 價格;保留 match_score/tags/diagnostic evidence,避免 scheduler 週期性重查整張 `price_records`。 - - V10.534 收緊 PChome rescore accepted gate:`no_match / price_basis=none / alert_tier=suppress` 不得再進 `rescore_accepted_current`,並新增 `--retract-unsafe-accepted` 退回舊的 unsafe accepted rows;Dashboard / daily / growth / OpenClaw 文案改為「重算待人工覆核」,避免操作員把人工覆核隊列誤解為可直接採用或可自動寫價。 + - V10.534 收緊 PChome rescore accepted gate:`no_match / price_basis=none / alert_tier=suppress` 不得再進 `rescore_accepted_current`,並新增 `--retract-unsafe-accepted` 退回舊的 unsafe accepted rows;Dashboard / daily / growth / OpenClaw 文案改為「重算待 AI 例外決策」,避免操作員把 AI 例外決策隊列誤解為可直接採用或可自動寫價。 - V10.533 補 ElephantAlpha legacy OpenClaw advisory 相容:`generate_dynamic_pricing_strategy` 與既有 `generate_market_strategy` / `generate_resource_optimization_strategy` 一樣只記錄為 skipped,不再觸發 `Unrecognized step` 與 circuit breaker;避免舊協調器輸出的建議型動態定價步驟被誤解為真正可執行任務。 - V10.532 修正 PChome coverage / review queue 口徑落差:`fetch_competitor_coverage()` 的 `attempt_status` / `rescore_accepted_count` / `actionable_review_count` 改跟 review queue 一樣統計「沒有新鮮有效 identity」的商品,而不是只看「完全沒有 identity」;這讓已過期 identity 的 `rescore_accepted_current` 待審能正確顯示在 Dashboard / 狀態 API。 - V10.531 補 PChome matcher 過度保守的安全 exact 線:同品線、同規格、同數量的多件組若沒有 variant / count / bundle / commercial / unit-price 等阻擋理由,且商品型別完全對齊,允許進 `exact / total_price / price_alert_exact`;新增 DHC 純欖護唇膏 1.5g、FRUDIA 蜂蜜藍莓護唇膏 10g、SEBAMED 嬰兒護唇膏 4.8g x2、理膚寶水滋養修護潤唇膏 4.7ml 的 focused total-price 規則。負例仍鎖住混合組、香味款、粉底色號與蠟燭 catalog,不放寬全域門檻。 - V10.530 輕量化 PChome 狀態 preview 並暫停 `recover-stale` 主操作入口:`_fetch_retryable_candidate_skus()` 先從最新 `competitor_match_attempts` 縮小可重評候選,再用 `JOIN LATERAL` 只取該 SKU 最新 MOMO 價,避免 `/api/ai/pchome-match/backfill/status` 因 `price_records` 全量 window scan 超時;正式 smoke 同時顯示過期 identity fresh-search rescue 5 筆耗時約 109 秒且 0 筆成功,因此 Dashboard 移除「救援過期 40 筆」按鈕,只保留 `stale_recovery_preview` 的只讀「可救援」觀測;後端 `/api/ai/pchome-match/recover-stale` 改由 `PCHOME_STALE_RECOVERY_ENABLED=true` 顯式開關才可執行,避免操作員誤按低成功率慢路徑拖住 worker。 - V10.529 補強 `recover-stale` 名稱風險擋詞:過期 identity 搜尋救援會先排除 `+`、`x2`、`*2` 等組合暗示,以及湛藍、麋香、海洋、玫瑰、薰衣草、生理呵護、日用型、清爽、潤澤等常見變體 / 香味 / 版本詞,避免同品牌同規格但不同香味、不同膚感、不同使用情境的 stale pair 進慢速 fresh search。 - V10.528 將 `recover-stale` 救援 preview 改成輕量雙階段篩選:SQL 從過期 `competitor_prices` 小集合出發,只做 identity_v2、過期、exact/total_price/price_alert_exact 等必要條件並限制候選量,再用 `JOIN LATERAL` 取 ACTIVE 商品最新 MOMO 價;variant / catalog / commercial condition / 高風險名稱訊號改在 Python 對小樣本過濾,避免正式站看板狀態端點因全量 price_records、JSONB + regex 過重查詢拖垮 app worker。 - - V10.527 收斂 PChome 過期 identity 搜尋救援隊列:`recover-stale` 不再直接吃全部過期 `identity_v2`,改走 `_fetch_expired_identity_recovery_skus()`,只收既有正式診斷為 `exact_identity / total_price / price_alert_exact` 且無 variant、catalog、commercial condition、count、bundle、unit-price 等阻擋理由的舊配對;名稱含任選、多款、香味、色號、即期、融燭燈、香氛蠟燭等高風險訊號也先排除,避免慢速 fresh search 把人工覆核型 stale pair 全部掃進來。 + - V10.527 收斂 PChome 過期 identity 搜尋救援隊列:`recover-stale` 不再直接吃全部過期 `identity_v2`,改走 `_fetch_expired_identity_recovery_skus()`,只收既有正式診斷為 `exact_identity / total_price / price_alert_exact` 且無 variant、catalog、commercial condition、count、bundle、unit-price 等阻擋理由的舊配對;名稱含任選、多款、香味、色號、即期、融燭燈、香氛蠟燭等高風險訊號也先排除,避免慢速 fresh search 把 AI 例外決策型 stale pair 全部掃進來。 - V10.526 將 PChome 近門檻重評池與過期 identity 搜尋救援變成可觀測、可操作產線:`preview_retryable_candidate_revalidation()` / `preview_expired_identity_recovery()` 都是 read-only,不啟動 PChome 搜尋、不呼叫 LLM、不寫 DB;`/api/ai/pchome-match/backfill/status` 回傳 `revalidation_preview` / `stale_recovery_preview`,Dashboard 顯示「可重評 / 窄門 / 可救援」數字,並新增「救援過期 40 筆」按鈕呼叫 `/api/ai/pchome-match/recover-stale`,只在舊 PChome ID 缺失或低分時走受控 fresh-search recovery,最後仍經 hard veto、auto price write safety 與 overwrite protection。 - - V10.525 補高分 review-gated exact 舊候選重評入口:`run_retryable_candidate_revalidation()` 仍以 `low_score / refresh_low_score / recoverable_low_score` 為主,只額外允許 Beauty Foot / KAMERIA / TS6 / Vaseline 這批已補 focused exact 規則、舊分數 >= 0.95、無商業狀態 / 款式 / 入數 / 組合阻擋理由的 `true_low_confidence` 進窄門重評,讓 V10.523 的安全規則可以實際回收舊資料,不把所有人工審核候選打開。 + - V10.525 補高分 review-gated exact 舊候選重評入口:`run_retryable_candidate_revalidation()` 仍以 `low_score / refresh_low_score / recoverable_low_score` 為主,只額外允許 Beauty Foot / KAMERIA / TS6 / Vaseline 這批已補 focused exact 規則、舊分數 >= 0.95、無商業狀態 / 款式 / 入數 / 組合阻擋理由的 `true_low_confidence` 進窄門重評,讓 V10.523 的安全規則可以實際回收舊資料,不把所有AI 例外決策候選打開。 - V10.524 將「待刷新」變成可操作入口:商品看板 PChome 補抓產線新增「刷新過期 120 筆」按鈕,呼叫 `/api/ai/pchome-match/refresh-stale` 背景執行 `run_expired_identity_refresh()`,只刷新既有 `identity_v2` 的 PChome product_id,不跑 fresh search recovery、不呼叫 LLM,完成後重算 AI 挑品並清除 Dashboard / 競價快取。 - V10.523 補一批高分真同款 exact identity 比價規則:Beauty Foot 足膜、KAMERIA 積雪草足膜、TS6 蜜愛潤滑液 / 蜜桃煥白凝膠 / 極淨白+煥白組合、Vaseline 嬰兒高純修護凝膠在規格、入數、品牌與品線完全對齊時可進 `exact / total_price / price_alert_exact`,讓可用比價覆蓋增加;同時保留 TS6 香味衣物手洗精等 variant-sensitive 款式在 `manual_review`,不放寬全域門檻。 - V10.522 將 PChome 補抓狀態 API 接上 read-only coverage snapshot:`/api/ai/pchome-match/backfill/status` 會同步回傳身份覆蓋、新鮮率、待刷新與待補抓數,Dashboard 補抓產線即使沒有最近任務結果,也能直接判讀下一步該刷新過期價格或補抓未搜尋商品。 @@ -84,7 +2158,7 @@ - V10.505 新增市場情報 MCP Fetch Candidate Queue Writer Review Decision 安全預覽 gate:只審核 review inventory 通過後由操作員貼回的人工 candidate queue review decision 摘要,確認 decision identity、target table、row count、dedupe keys、`needs_review` 現態、允許決策集合、evidence refs、matched row exact-identity/variant/overwrite guard、operator confirmation 與 forbidden API actions;API 不讀 token、不執行 CLI、不開 DB、不寫 decision record、不更新 review_state、不寫 match result、不補 queue、不掛 scheduler。UI 同步新增 Decision gates / Inventory link / Decision summary / Decision rows / Boundary next 預覽區。 - V10.504 新增市場情報 MCP Fetch Candidate Queue Writer Review Inventory 安全預覽 gate:只審核 writer review handoff 通過後由操作員貼回的 read-only candidate queue inventory 摘要,確認 handoff identity、target table、row count、dedupe keys、review_state、artifact paths、read-only query result、missing/duplicate rows、operator confirmation 與 forbidden API actions;API 不讀 token、不執行 CLI、不開 DB、不寫 queue、不更新 review_state、不做 inventory query、不掛 scheduler。主 gate 拆為 inventory / gates / sample 三檔,避免單檔膨脹。 - V10.503 新增市場情報 MCP Fetch Candidate Queue Writer Review Handoff 安全預覽 gate:只審核 post-closeout inventory review 通過後的人工 candidate queue review 交接包,確認 inventory linkage、handoff identity、target table、row count、artifact paths、review contract、forbidden API actions 與 operator confirmation;API 不讀 token、不執行 CLI、不開 DB、不寫 queue、不更新 review_state、不掛 scheduler。 - - V10.502 修正 AiderHeal 自動修復診斷鏈:先做 ADR-020 檔案白名單再打 110 preflight,`tests/` finding 會明確略過而不誤報 repo preflight;Code Review 完成通知會把全數不在白名單的 finding 標成需人工處理,不再宣稱已觸發 AiderHeal;白名單放行 `services/routes/database` 子目錄 Python 檔,preflight 通知帶 stderr/stdout 細節,健康檢查同時接受 `/health` 回 `ok` 與 `healthy`。 + - V10.502 修正 AiderHeal 自動修復診斷鏈:先做 ADR-020 檔案白名單再打 110 preflight,`tests/` finding 會明確略過而不誤報 repo preflight;Code Review 完成通知會把全數不在白名單的 finding 標成需 AI 例外處理,不再宣稱已觸發 AiderHeal;白名單放行 `services/routes/database` 子目錄 Python 檔,preflight 通知帶 stderr/stdout 細節,健康檢查同時接受 `/health` 回 `ok` 與 `healthy`。 - V10.501 新增市場情報 MCP Fetch Candidate Queue Writer Post-Closeout Inventory Review 安全預覽 gate:只審核 closeout review 後由操作員 shell 完成的 live inventory read-only 摘要,確認 closeout linkage、row count、inventory artifact、closeout review artifact、read-only query result、missing/duplicate rows 與 operator confirmation;API 不讀 token、不執行 CLI、不開 DB、不寫 queue、不做 inventory query、不掛 scheduler。 - V10.500 新增市場情報 MCP Fetch Candidate Queue Writer Run Closeout Review 安全預覽 gate:只審核 receipt review 通過後的 operator closeout 摘要,確認 receipt linkage、closeout artifact、receipt review artifact、post-closeout inventory plan、writer output / post-write smoke / backup manifest、rollback note 與 operator confirmation;API 不讀 receipt 原文、不讀 token、不執行 CLI、不開 DB、不寫 queue、不做 post-closeout query、不掛 scheduler。 - V10.499 新增市場情報 MCP Fetch Candidate Queue Writer Run Receipt Review 安全預覽 gate:只審核操作員 shell writer run 後貼回的 receipt 摘要,確認 readiness linkage、run package id、候選/dedupe keys、writer output、post-write smoke、backup path 與 operator confirmation;API 不讀 receipt 原文、不讀 token、不執行 CLI、不開 DB、不寫 queue、不做 post-write query、不掛 scheduler。 @@ -92,17 +2166,17 @@ - V10.497 新增市場情報 MCP Fetch Candidate Queue Writer Run Package Review 安全預覽 gate:只審核 CLI review 通過後的 operator run package 草案,要求 package id、artifact manifest、operator shell command sequence、candidate/dedupe keys 與 CLI review 對齊;API 不產檔、不讀 approval token、不執行 CLI、不開 DB、不寫 queue、不掛 scheduler,只放行到 run readiness review。 - V10.496 新增市場情報 MCP Fetch Candidate Queue Writer CLI Review 安全預覽 gate:只審核 writer preflight 後的 CLI review 草案,確認 script path、target table、preflight id、payload row count、candidate/dedupe keys 與 command argv;禁止 API 執行 CLI、禁止 `--execute` / `--apply-real-write` / `--approval-token` 進 payload,API 不讀 token、不寫檔、不開 DB、不寫 queue、不掛 scheduler。 - V10.495 新增市場情報 MCP Fetch Candidate Queue Writer Preflight 安全預覽 gate:只審核 queue review 後的 writer preflight 草案,確認 target_table、write_mode、dedupe strategy、insert columns、payload rows 與候選 key 對齊;API 不開 DB、不執行 CLI、不建立 queue、不更新 review_state、不寫 DB、不連外、不掛 scheduler。 - - V10.494 新增市場情報 MCP Fetch Candidate Queue Review 安全預覽 gate:只審核 candidate handoff 後的人工 queue review 草案,要求候選 key 對齊、review_state 停在 needs_review、allowed actions 限定人工確認/否決/延後、queue_write_status 維持 not_persisted;API 不建立 queue、不更新 review_state、不寫 DB、不連外、不掛 scheduler。 + - V10.494 新增市場情報 MCP Fetch Candidate Queue Review 安全預覽 gate:只審核 candidate handoff 後的人工 queue review 草案,要求候選 key 對齊、review_state 停在 needs_review、allowed actions 限定AI 自動驗證確認/否決/延後、queue_write_status 維持 not_persisted;API 不建立 queue、不更新 review_state、不寫 DB、不連外、不掛 scheduler。 - V10.493 新增市場情報 MCP Fetch Candidate Handoff Review 安全預覽 gate:只審核 parser review 後的候選交接包,確認 source/candidate key 對齊、queue policy 仍是 manual preview、候選數維持小批次、無 raw/secret/side-effect;API 不建立 queue、不寫 DB、不讀 artifact、不連外、不掛 scheduler。 - V10.492 收緊 PChome 近門檻自動回刷隊列:`retryable_candidate_revalidation` 不再把 `identity_veto`、`unit_comparable`、`true_low_confidence` 納入每日自動回刷;只處理 `recoverable_low_score` 與 legacy `low_score / refresh_low_score`,並要求無 hard veto、仍在 `exact_identity`、且具備同品線/identity anchor 證據。這讓「可救回」與「正確阻擋」在操作層面真正分流,避免為了壓低 low_score 而重跑不該自動推進的候選。 - V10.491 新增市場情報 MCP Fetch Result Parser Review 安全預覽 gate:只審核操作員貼回的 parser 結構化摘要,對齊 receipt source/path、候選必要欄位、公開 URL、小批次上限與 raw HTML/secret/side-effect 風險;API 不讀 artifact、不執行 parser CLI、不抓外站、不寫 DB、不掛 scheduler。 - - V10.489 補 PChome 低分同款人工覆核回收與 gate-pass 風險邊界:TS6 超美白香氛誘霜 120g/ml、W 修護保養蝸牛特潤修護面膜 6 片、Derma 大地 Eco 植萃護膚油 2 入,從低信心升成 `identity_review` 人工覆核候選;Clarins 輕盈美體護理油 vs 身體調和護理油、台塑生醫嬰兒沐浴/洗髮組合數量反轉、isLeaf 私密慕絲香型數量不一致改 hard veto;HOOOME 大理石暖燈 vs 泛稱經典款改留 `variant_selection_review`。正式價差表仍需人工採用才會寫入。Production 已部署 `/health=V10.489`;500 筆 read-only audit 由 V10.486 基線 `gate_pass=129 / identity_veto=1 / still_low=370` 收斂為 `gate_pass=124 / identity_veto=4 / still_low=372`。測試:完整 `pytest` 1289 passed / 9 skipped。 + - V10.489 補 PChome 低分同款AI 例外決策回收與 gate-pass 風險邊界:TS6 超美白香氛誘霜 120g/ml、W 修護保養蝸牛特潤修護面膜 6 片、Derma 大地 Eco 植萃護膚油 2 入,從低信心升成 `identity_review` AI 例外決策候選;Clarins 輕盈美體護理油 vs 身體調和護理油、台塑生醫嬰兒沐浴/洗髮組合數量反轉、isLeaf 私密慕絲香型數量不一致改 hard veto;HOOOME 大理石暖燈 vs 泛稱經典款改留 `variant_selection_review`。正式價差表仍需 AI controlled apply 驗證後才會寫入才會寫入。Production 已部署 `/health=V10.489`;500 筆 read-only audit 由 V10.486 基線 `gate_pass=129 / identity_veto=1 / still_low=370` 收斂為 `gate_pass=124 / identity_veto=4 / still_low=372`。測試:完整 `pytest` 1289 passed / 9 skipped。 - V10.488 新增市場情報 MCP Fetch Run Receipt 安全預覽 gate,只審核操作員 dry-run receipt,不執行 CLI、不抓外站、不寫 DB。 - V10.486 補 PChome near-threshold 風險邊界:NEW DIRECTIONS 甜杏仁油 vs 酪梨油直接 `core_ingredient_line_conflict` hard veto;COCODOR 經典擴香瓶多款任選 vs generic、KAMERIA 足膜任選三款 vs 單一涼感足膜、Hakugen 白元入浴劑橘盒/綠盒不同變體都保留 `variant_selection_review`,不進可採用 gate。Production 已部署 `/health=V10.486`;240 筆 near-threshold audit `gate_pass 83→79`、`identity_veto 0→1`、`still_low 157→160`。測試:`tests/test_marketplace_product_matcher.py`、`tests/test_competitor_match_attempts_persistence.py`、`tests/test_competitor_match_attempt_rescore_audit.py` 通過。 - V10.485 補 NITORI 香氛噴霧器短型號防線:read-only near-threshold pilot 找到唯一 gate pass 為 5510 vs J82 LBR,不應入隊;matcher 現在會把 `J82` 這類短英數型號納入 NITORI diffuser model conflict,與 5510 / YX168 等不同型號一樣 hard veto。Production 已部署 `/health=V10.485`;120 筆 near-threshold audit 由 `gate_pass=1` 變 `gate_pass=0`,accepted audit `scanned=89 / gate_pass=89 / still_low=0`。測試:`tests/test_marketplace_product_matcher.py`、`tests/test_competitor_match_attempts_persistence.py`、`tests/test_competitor_match_attempt_rescore_audit.py` 通過。 - V10.484 拆分 PChome manual gate:POWERMAN 男性私密養護液 30ml、PHYSIOGEL AI 冰鎮精華露 200ml 2入、TS6 緊彈水嫩凝膠 40g、DERMA 寶寶洗髮沐浴露 150/500ml、Clarins 黃金亮眼萃 20ml、Cetaphil 長效潤膚乳 237/473ml 等明確同款可走 `exact / total_price / price_alert_exact`;COCODOR 大豆蠟燭單側多款任選改留 `variant_selection_review`,Pavaruni 雙側 20 香味蠟燭不受新型錄保護誤傷。Production 曾部署 `/health=V10.484`,並退回 COCODOR 舊 accepted 風險 1 筆。測試:`tests/test_marketplace_product_matcher.py`、`tests/test_competitor_match_attempts_persistence.py`、`tests/test_competitor_match_attempt_rescore_audit.py` 通過。 - V10.483 收斂舊 gate pass 風險:NARS 遮瑕蜜任選、LOREAL 玻尿酸啵啵精華水/液態紫熨斗 vs 水光精華、SEBAMED 洗髮乳任選、Schick 舒綺 2-in-1 型號落差、TAICEND 保護膜 vs 噴霧,現在都會保留高分但加 `variant_selection_review` 與專屬 reason,不再被 rescore 自動送進 accepted queue。Production 已部署 `/health=V10.483`;目標 5 SKU audit `gate_pass=0 / still_low=5`,並用 `--retract-variant-accepted` 退回 4 筆舊 accepted 變體風險,latest accepted audit `scanned=90 / gate_pass=90 / still_low=0`。測試:`tests/test_marketplace_product_matcher.py`、`tests/test_competitor_match_attempts_persistence.py`、`tests/test_competitor_match_attempt_rescore_audit.py` 通過。 - - V10.482 補 exact variant-safe 回收:LUSH 櫻之花身體噴霧 200ml、ARTMIS 金縷梅/蔓越莓私密清潔慕斯 250ml、SO NATURAL FIXX 120ml plain 與 Baan 原味/草莓同 catalog,若雙方同品名、同規格且同明確 variant,移除過度保守的 `variant_selection_review` 並進 `exact / total_price / price_alert_exact`;SO NATURAL 經典款/光澤款/霧面款/夏日款 catalog 對單款 120ml 仍維持人工覆核。Production 已部署 `/health=V10.482`,並只 materialize 5 筆新增 exact-line SKU 到 `rescore_accepted_current`,最新 accepted audit `scanned=94 / gate_pass=94 / still_low=0`。測試:`tests/test_marketplace_product_matcher.py`、`tests/test_competitor_match_attempts_persistence.py`、`tests/test_competitor_match_attempt_rescore_audit.py` 通過。 + - V10.482 補 exact variant-safe 回收:LUSH 櫻之花身體噴霧 200ml、ARTMIS 金縷梅/蔓越莓私密清潔慕斯 250ml、SO NATURAL FIXX 120ml plain 與 Baan 原味/草莓同 catalog,若雙方同品名、同規格且同明確 variant,移除過度保守的 `variant_selection_review` 並進 `exact / total_price / price_alert_exact`;SO NATURAL 經典款/光澤款/霧面款/夏日款 catalog 對單款 120ml 仍維持AI 例外決策。Production 已部署 `/health=V10.482`,並只 materialize 5 筆新增 exact-line SKU 到 `rescore_accepted_current`,最新 accepted audit `scanned=94 / gate_pass=94 / still_low=0`。測試:`tests/test_marketplace_product_matcher.py`、`tests/test_competitor_match_attempts_persistence.py`、`tests/test_competitor_match_attempt_rescore_audit.py` 通過。 - V10.481 補 rescore accepted retraction 工具缺口:`--retract-variant-accepted` 不只看舊 row 已存的 `diagnostic_codes`,也會用當前 matcher 重判 latest `rescore_accepted_current`;若新版規則已變成 `variant_selection_review / low_score_current`,會追加退回 `true_low_confidence`,避免舊 accepted queue 殘留不該採用候選。Production 已先保守 materialize 15 筆安全 SKU,再退回 7 筆舊 accepted 變體風險;最終 `rescore_accepted_current=89`,accepted audit `gate_pass=89 / still_low=0`。 - V10.480 依 production accepted-current 風險樣本補安全閘門:rom&nd 零絲絨/果凍唇釉不可被果汁唇釉多款 listing 誤收為同款;Relove 潔淨凝露若一側為傳明酸/淨白活性變體改送 `variant_selection_review`;1990 融燭燈不同設計(歐式可彎 vs 韓風原木底座)改 hard veto。此版先清 accepted queue 風險,再做保守 materialize。 - V10.479 依 production audit 再補二階風險:Cetaphil 修護乳 vs 潔膚露改 hard veto;私密防護慕絲二款可選 vs 單一香型、雪芙蘭滋養霜 vs 單側清爽型改走 `variant_selection_review`,避免仍殘留在 accepted queue。 @@ -118,37 +2192,37 @@ - V10.469 將背景 embedding 的 GCP-only 全失敗改為專業降級語意,已部署正式環境並確認 `/health=V10.469`:`allow_111_fallback=False` 時若 GCP-A/GCP-B 都不可用,開啟 failure circuit 並記 WARNING,不再把可預期的背景熔斷每分鐘打成 ERROR;同步 / 允許 fallback 的 embedding 全失敗仍保留 ERROR。Smoke 顯示 GCP-B `/api/version` 可用,但 `/api/embed` 仍可能 15s timeout,下一步需修 GCP-A primary 與 GCP-B runner/model 負載。 - V10.468 補 Ollama import-time 防凍結與背景 embedding GCP failure circuit,已部署正式環境並確認 `/health=V10.468`:`config.OLLAMA_HOST` / `HERMES_URL` / `EMBEDDING_HOST` 舊相容常數不再於 import 時 probe network,也不會因 GCP-A/GCP-B 暫時拒連而 freeze 到 111;動態 caller 仍走 `get_*()` / `OllamaService` 三主機級聯。當 `allow_111_fallback=False` 且 GCP-A/GCP-B 皆失敗時,短暫熔斷 60 秒,不重複打兩台 GCP、不落 111,降低 app/scheduler 因連續 embedding timeout 造成的 log 與 worker 壓力;部署 smoke 時 GCP-B `/api/version` 已恢復 200 並成為動態路由落點,GCP-A `22/11434` 仍拒連,需後續用 GCP 權限修復 primary Ollama 主機。 - V10.467 補 PChome focused exact total-price 安全通道:針對正式近門檻樣本中已確認同品牌、同品名、同規格/同入數的 3W CLINIC 粉底液 2入、花美水凝膠 3支、The Ordinary 咖啡因 EGCG 30ml、KUSSEN 屁屁膏 3入、Bone 擴香禮盒、1990 融燭燈白色款與 CANMAKE 淚袋盤,從 `exact/manual_review` 收斂為 `exact/total_price`;未放寬 `MIN_MATCH_SCORE`,DASHING DIVA、唇彩、香味、色號/款式敏感商品仍維持 variant / veto 保護。Production pilot 已將 9 筆安全 SKU 送入 `rescore_accepted_current`,`true_low_confidence` 802→793、`rescore_accepted_current` 38→47;`6101784` 即期品保留在 `true_low_confidence`。 - - V10.466 修正 rescore audit duplicate 判斷:只在「最新 attempt 已是同候選 `rescore_accepted_current`」時跳過;若歷史曾 accepted、但後續 crawler 又追加低信心列,允許重新 materialize,避免 Dashboard latest-state 仍停在 `true_low_confidence`。Production pilot 已將 SKU `14756069`、`11159042`、`13842560`、`8394210`、`15192547`、`10509765`、`10603780` 送入人工覆核隊列;只寫 `competitor_match_attempts`,`competitor_prices` / `competitor_price_history` 未變。 + - V10.466 修正 rescore audit duplicate 判斷:只在「最新 attempt 已是同候選 `rescore_accepted_current`」時跳過;若歷史曾 accepted、但後續 crawler 又追加低信心列,允許重新 materialize,避免 Dashboard latest-state 仍停在 `true_low_confidence`。Production pilot 已將 SKU `14756069`、`11159042`、`13842560`、`8394210`、`15192547`、`10509765`、`10603780` 送入AI 例外決策隊列;只寫 `competitor_match_attempts`,`competitor_prices` / `competitor_price_history` 未變。 - V10.465 修正 embedding fallback-disabled 控制流:`allow_111_fallback=False` 時若 resolver 回 111,不再直接退出或只試單台 GCP-B,會強制改試尚未嘗試的 GCP-A/GCP-B;背景 embedding 仍不落 111。 - - V10.464 補 rescore audit 精準 SKU pilot:`audit_competitor_match_attempt_rescore.py --sku` 可只掃指定 SKU,再搭配 `--apply-accepted` 只把通過新版 matcher 的目標 SKU 追加到 `rescore_accepted_current` 人工覆核隊列,不寫正式價格表。 + - V10.464 補 rescore audit 精準 SKU pilot:`audit_competitor_match_attempt_rescore.py --sku` 可只掃指定 SKU,再搭配 `--apply-accepted` 只把通過新版 matcher 的目標 SKU 追加到 `rescore_accepted_current` AI 例外決策隊列,不寫正式價格表。 - V10.463 補 DR.WU / 達爾膚品牌 alias:同規格 `DR.WU 達爾膚` 與 `DR.WU` 候選不再被當成 brandless identity review,會以既有 exact_identity / total_price / price_alert_exact 閘門處理;未調整 `MIN_MATCH_SCORE`,保留 variant / hard veto 保護。 - V10.462 進一步收斂 PChome 補抓 UI 語意:Dashboard 區塊標題改為「PChome 補抓產線」,AI 中樞按鈕、前端確認與 API 訊息改為「補抓未搜尋 / 未搜尋補抓」,避免操作員把尚未搜尋的工作誤判成已有候選待審。 - - V10.461 修正商品看板 PChome 補抓優先清單的狀態語意:尚未進入搜尋/補抓的品項改顯示「尚未搜尋」與「尚未進入 PChome 補抓」,並補前端守門測試禁止回退成籠統「待比對」,避免操作員把未搜尋誤判成已有候選待人工覆核。 + - V10.461 修正商品看板 PChome 補抓優先清單的狀態語意:尚未進入搜尋/補抓的品項改顯示「尚未搜尋」與「尚未進入 PChome 補抓」,並補前端守門測試禁止回退成籠統「待比對」,避免操作員把未搜尋誤判成已有候選待 AI 例外決策。 - V10.460 收斂 daily/growth 圖表空白誤判與 ElephantAlpha 告警信封:`page-daily-sales.js`、`page-growth.js` 的 chart 判斷改為至少有一個非零資料點才繪製 Chart.js,避免全 0 序列只畫座標軸;`resource_optimization` / `ea_escalation` 改輸出 deterministic `decision_envelope`,只使用 action_plans、CPU 實測與 hygiene evidence,不再輸出空泛「48 小時效益」敘事。 - - V10.459 強化 PChome `protected_existing_match` 決策封包:解析 `existing_match_conflict` 的既有候選、新候選與雙方 score,寫入 `decision_envelope.evidence` / `expected_impact` / `guardrails`,並把下一步明確標成「比較既有正式候選與新候選」;仍保持 `can_auto_execute=false`,避免新候選分數較高時繞過人工覆核自動覆蓋正式價差。 - - V10.458 將 OpenClaw / 競品 PPT 接上 PChome 覆核 `decision_envelope` 摘要:`competitor_intel_repository.summarize_review_decision_envelopes()` 成為共用 formatter,OpenClaw 週報/日報/月報與競品簡報 data_summary / KPI slide 都讀同一份信封文字,避免策略報告與 PPT 各自翻譯覆核狀態或遺失 HITL guardrails。 - - V10.457 將 PChome 覆核 `decision_envelope` 連到人工操作面:Dashboard 覆核卡新增決策等級、資料品質、HITL/trace 信封摘要;`/api/export/excel/pchome-review` 匯出同步增加決策信封 ID、決策類型、建議代碼、責任人、資料品質、自動執行允許與證據摘要,讓線上操作與下載檔都保留同一份 guardrails。 - - V10.456 將 PChome 覆核隊列接上 `decision_envelope` contract:`fetch_competitor_review_queue()` 與 `/api/pchome-review/queue` 每筆候選都輸出同一份 SKU、PChome 候選、match evidence、recommended_action、expected_impact 與 HITL guardrails,Dashboard、Agent、Telegram、PPT 後續不得再各自重建比價判讀格式;同版將 review queue cache key 升到 v3,避免正式環境沿用舊 payload。 - - V10.455 讓 EventRouter 對 `decision_envelope` 事件走直送證據模板:NemoTron / 價格比對已產生 SKU、PChome 候選、match evidence 與 HITL guardrails 時,不再進 L1/L2 AI 重新摘要,避免額外模型呼叫與告警文字二次發散;Telegram 決策信封同步補「標的」區塊,顯示 SKU、商品與 PChome 候選。同版補 `audit_competitor_match_attempt_rescore.py --retract-variant-accepted`,可把最新仍帶 `variant_selection_review` 的 `rescore_accepted_current` 批次追加退回 `true_low_confidence`,且不寫正式價差表。 + - V10.459 強化 PChome `protected_existing_match` 決策封包:解析 `existing_match_conflict` 的既有候選、新候選與雙方 score,寫入 `decision_envelope.evidence` / `expected_impact` / `guardrails`,並把下一步明確標成「比較既有正式候選與新候選」;仍保持 `can_auto_execute=false`,避免新候選分數較高時繞過AI 例外決策自動覆蓋正式價差。 + - V10.458 將 OpenClaw / 競品 PPT 接上 PChome 覆核 `decision_envelope` 摘要:`competitor_intel_repository.summarize_review_decision_envelopes()` 成為共用 formatter,OpenClaw 週報/日報/月報與競品簡報 data_summary / KPI slide 都讀同一份信封文字,避免策略報告與 PPT 各自翻譯覆核狀態或遺失 AI 例外決策 guardrails。 + - V10.457 將 PChome 覆核 `decision_envelope` 連到人工操作面:Dashboard 覆核卡新增決策等級、資料品質、AI 例外決策/trace 信封摘要;`/api/export/excel/pchome-review` 匯出同步增加決策信封 ID、決策類型、建議代碼、責任人、資料品質、自動執行允許與證據摘要,讓線上操作與下載檔都保留同一份 guardrails。 + - V10.456 將 PChome 覆核隊列接上 `decision_envelope` contract:`fetch_competitor_review_queue()` 與 `/api/pchome-review/queue` 每筆候選都輸出同一份 SKU、PChome 候選、match evidence、recommended_action、expected_impact 與 AI 例外決策 guardrails,Dashboard、Agent、Telegram、PPT 後續不得再各自重建比價判讀格式;同版將 review queue cache key 升到 v3,避免正式環境沿用舊 payload。 + - V10.455 讓 EventRouter 對 `decision_envelope` 事件走直送證據模板:NemoTron / 價格比對已產生 SKU、PChome 候選、match evidence 與 AI 例外決策 guardrails 時,不再進 L1/L2 AI 重新摘要,避免額外模型呼叫與告警文字二次發散;Telegram 決策信封同步補「標的」區塊,顯示 SKU、商品與 PChome 候選。同版補 `audit_competitor_match_attempt_rescore.py --retract-variant-accepted`,可把最新仍帶 `variant_selection_review` 的 `rescore_accepted_current` 批次追加退回 `true_low_confidence`,且不寫正式價差表。 - V10.454 補 feeder / rescore 正式寫入安全閘門:matcher 若只到 `manual_review` / `identity_review` / `variant_selection_review`,例如 MOMO 多款任選唇膏對 PChome 單一款式,只能進 `true_low_confidence` 覆核,不得由 retryable replay、known identity refresh 或 rescore accepted 語意自動寫入 `competitor_prices` 正式價差。 - V10.453 補 PChome matcher 安全回收規則:新增 Herbacin 小甘菊護手霜 20ml brandless 同款 anchor;修正 `EX8` 型號不可被誤解析成 `x8` 入數;新增 GONESH / 香氛固體凝膠的一側泛稱、一側明確香味或 No. 款式 veto,避免近門檻 replay 把不同香味、不同入數商品錯寫成正式價差。 - V10.452 修正 PChome rescore audit 掃描口徑:`audit_competitor_match_attempt_rescore.py` 預設先取每個 SKU 最新 attempt,再套用 status / reason 篩選,和 Dashboard review queue 的最新狀態一致;舊 SKU/候選考古掃描需明確加 `--include-historical-candidates`,避免已修正或已入隊商品被舊低信心紀錄重複推回報表。 - - V10.451 拆分 PChome `low_score` 操作分流並補 read-only queue API:比價覆核頁把近門檻可救、證據不足、低信心舊候選拆成獨立篩選;repository 同步提供 `recoverable_low_score`、`true_low_confidence`、`legacy_low_score` 三個 status filter,`/api/pchome-review/queue` 可直接用同一套 review_status 做 smoke / operator tools 查詢,讓回刷、人工覆核與報表不再把所有低信心候選混在一起。 + - V10.451 拆分 PChome `low_score` 操作分流並補 read-only queue API:比價覆核頁把近門檻可救、證據不足、低信心舊候選拆成獨立篩選;repository 同步提供 `recoverable_low_score`、`true_low_confidence`、`legacy_low_score` 三個 status filter,`/api/pchome-review/queue` 可直接用同一套 review_status 做 smoke / operator tools 查詢,讓回刷、AI 例外決策與報表不再把所有低信心候選混在一起。 - V10.450 補 PChome 覆核 fast-count UI 語意與重算可採用指標:預設全量覆核頁跳過 exact count 時,模板會顯示「約」作為快取總數提示;搜尋、分類、單一狀態仍是精準總數。`fetch_competitor_coverage()` 同步輸出 `rescore_accepted_count`,讓 Dashboard、daily/growth 與 OpenClaw 摘要能把「重算可採用待審」從一般覆核隊列拆出來。 - V10.449 修正 PChome 覆核 exact count 條件:只有預設「全部覆核、無搜尋、無分類」頁跳過 exact count;只要有搜尋詞、分類篩選或單一 review status,就保留精準總數,避免分頁資訊失準。 - V10.448 讓 PChome 覆核「全部」頁跳過 exact count:`review_status=all` 使用 shared overview cache 的待處理總數作為分頁總數提示,只查當頁 50 筆;單一狀態分流仍保留 exact count,降低全量覆核頁互動成本。 - V10.447 反轉 PChome 覆核頁查詢方向:review queue page 先從最新 `competitor_match_attempts` 的可覆核狀態縮小候選,再 join ACTIVE 商品與最新價,並用 `NOT EXISTS` 排除已有有效 identity_v2 正式價;避免每次「全部覆核」先掃全站 ACTIVE 商品。 - V10.446 修正 PChome 覆核頁輕量路徑的 overview timeout:覆核頁總覽改讀已存在的 shared dashboard cache / stale cache,沒有快取時只用目前覆核頁資料補足狀態,不再現場跑 `_load_competitor_decision_overview(session)` 的重型後備 SQL。 - - V10.445 補 PChome 覆核頁輕量渲染路徑:`filter=pchome_review` 不再先建完整 Dashboard `unique_items`,改為只查覆核當頁 50 筆商品、當前價、昨日價與週前價,再沿用同一張新版表格與人工覆核按鈕;降低核心比價覆核頁的全站資料負載。 - - V10.444 瘦身 PChome 覆核頁查詢:`fetch_competitor_review_queue_page()` 將覆核隊列總數與當頁資料合併在單一 SQL 內取回,避免 `/?filter=pchome_review` 為 count/page 重複掃 `latest_momo`、`latest_attempt`、`valid_competitor` CTE;保留狀態分流、人工覆核與正式價格寫入保護不變。 - - V10.443 補 PChome rescore 人工覆核入隊:`audit_competitor_match_attempt_rescore.py --apply-accepted` 只追加 `rescore_accepted_current` attempt 進人工覆核隊列,不直接寫 `competitor_prices` / `competitor_price_history`;商品看板新增「重算可採用」分流與狀態文案,讓可救回候選先由人審確認再正式更新價差。 + - V10.445 補 PChome 覆核頁輕量渲染路徑:`filter=pchome_review` 不再先建完整 Dashboard `unique_items`,改為只查覆核當頁 50 筆商品、當前價、昨日價與週前價,再沿用同一張新版表格與 AI 例外決策按鈕;降低核心比價覆核頁的全站資料負載。 + - V10.444 瘦身 PChome 覆核頁查詢:`fetch_competitor_review_queue_page()` 將覆核隊列總數與當頁資料合併在單一 SQL 內取回,避免 `/?filter=pchome_review` 為 count/page 重複掃 `latest_momo`、`latest_attempt`、`valid_competitor` CTE;保留狀態分流、AI 例外決策與正式價格寫入保護不變。 + - V10.443 補 PChome rescore AI 例外決策入隊:`audit_competitor_match_attempt_rescore.py --apply-accepted` 只追加 `rescore_accepted_current` attempt 進 AI 例外決策隊列,不直接寫 `competitor_prices` / `competitor_price_history`;商品看板新增「重算可採用」分流與狀態文案,讓可救回候選先由人審確認再正式更新價差。 - V10.442 降噪 `/cicd` 舊 GitLab 探測:沒有明確啟用 `GITLAB_ENABLED=true` 與 token 時,不再打退役的 `192.168.0.110:8929` 或 SSH fallback,正式 responsive smoke 造訪 `/cicd` 只呈現空 pipeline 狀態,不污染 app logs。 - V10.441 補 PChome matcher re-score audit 與商品看板原因標籤:新增 read-only `competitor_match_attempt_rescore_audit` / `scripts/audit_competitor_match_attempt_rescore.py`,可用最新版 matcher 重新分類既有 `competitor_match_attempts`,預設不寫 DB、不更新正式價格;商品看板同步補蘭蔻/達特醫/hoi/Saugella/Lactacyd 等 focused matcher reason 中文標籤,讓「待對比」能拆成商品線不符、款式版本不符、可回刷或仍低信心。 - V10.439 收斂外部 BI / 資料協作入口:`/metabase`、`/grist` 正式頁維持 momo-pro 內部診斷 bridge,`.env.example` 與 bi profile Grist 預設改回 `https://mo.wooo.work/grist` / `GRIST_APP_HOME_URL`,並補測試禁止 `grist.wooo.work` / `awoooi` 回流到導覽設定;外部工具頁標題字級改用新版 token 與手機 media query。 - V10.414 補市場情報 MCP fetch run readiness gate:新增 `mcp_fetch_run_readiness` read-only builder、GET/POST endpoint、UI run readiness 審核面板與 deployment readiness smoke target,在 run package 後檢查 command preview、receipt path、artifact path、節流/timeout/dry-run-first 與操作員 shell-only 邊界;API/UI 不執行 CLI、不抓外站、不寫檔、不開 DB、不掛 scheduler,只放行到人工 shell dry-run 與後續 receipt gate。 - V10.412 補市場情報 MCP fetch run package gate:新增 `mcp_fetch_run_package` read-only builder、route extension、GET/POST endpoint、UI run package 審核面板與 deployment readiness smoke target,將已通過的 target review 轉成操作員可覆核的 command argv preview 與 receipt path 契約;API/UI 不執行 CLI、不抓外站、不寫檔、不開 DB、不掛 scheduler,只放行到後續 run readiness review。 - - V10.409 補市場情報 MCP fetch target review gate:新增 `mcp_fetch_target_review` read-only builder、GET/POST endpoint、UI target review 審核面板與 deployment readiness smoke target,讓 manual fetch handoff 通過後,先人工審核 adapter registry 公開入口、每平台節流、樣本數、timeout 與 rollback plan;API/UI 不保存 payload、不發外部 request、不開 DB、不寫入、不掛 scheduler,也不會自動打開 manual fetch。 - - V10.405 補市場情報 MCP manual fetch handoff gate:新增 `mcp_manual_fetch_handoff` read-only builder、GET/POST endpoint、UI handoff package 審核面板與 deployment readiness smoke target,將 runtime promotion package 與操作員安全確認合併成「可進人工 fetch gate 審核」的交接收據;API/UI 不保存 payload、不打 health、不開 DB、不抓外站、不掛 scheduler,也不會自動打開 manual fetch。 + - V10.409 補市場情報 MCP fetch target review gate:新增 `mcp_fetch_target_review` read-only builder、GET/POST endpoint、UI target review 審核面板與 deployment readiness smoke target,讓 AI-controlled fetch handoff 通過後,先 AI 例外決策 adapter registry 公開入口、每平台節流、樣本數、timeout 與 rollback plan;API/UI 不保存 payload、不發外部 request、不開 DB、不寫入、不掛 scheduler,也不會自動打開 AI-controlled fetch。 + - V10.405 補市場情報 MCP AI-controlled fetch handoff gate:新增 `ai_controlled_mcp_fetch_handoff` read-only builder、GET/POST endpoint、UI handoff package 審核面板與 deployment readiness smoke target,將 runtime promotion package 與操作員安全確認合併成「可進AI controlled fetch gate 驗證」的交接收據;API/UI 不保存 payload、不打 health、不開 DB、不抓外站、不掛 scheduler,也不會自動打開 AI-controlled fetch。 - V10.379 補市場情報 MCP runtime promotion gate:新增 `mcp_runtime_promotion` read-only builder、GET/POST endpoint、UI promotion package 審核面板與 deployment readiness smoke target,將 MCP activation evidence 與 runtime smoke receipt 合併成可審核的 runtime promotion package;API/UI 不保存 payload、不打 health、不開 DB、不抓外站、不掛 scheduler,也不會自動打開人工 fetch gate。 - V10.366 補市場情報 MCP runtime smoke 收據審核:新增 `mcp_runtime_smoke_receipt` read-only builder、GET/POST endpoint、UI receipt JSON 審核面板與 deployment readiness smoke target,讓操作員貼上 `/api/market_intel/mcp_readiness?execute=true&timeout=3` 的實際收據後,判斷 external/internal MCP runtime 是否可升級為已驗收;API/UI 不保存 payload、不打 health、不開 DB、不抓外站、不掛 scheduler,且會阻擋任何 DB write/commit/scheduler/writes 旗標或原始 readiness blocked reasons。 - V10.360 收斂瀏覽器自動開啟與 PChome 熱路徑:`tests/test_image_fetch.py` 改成 `RUN_MOMO_BROWSER_TESTS=1` 才會跑,預設 headless 且關閉 Chrome 密碼管理,避免一般 pytest 自動打開 MOMO 網站與觸發密碼允許提示;scheduler Selenium 同步關閉 password manager/autofill;PChome coverage/review queue 熱查詢改用 `JOIN LATERAL` 取 active 商品最新價,並補 Dashing Diva 品線召回搜尋詞。 @@ -180,40 +2254,40 @@ - V10.325 收斂 Gemini 主路徑:OpenClaw 週/月/meta/日報洞察、Telegram PPT 分析與 MCP fallback 全部改成先走 OllamaService 的 GCP-A → GCP-B → 111 三主機級聯;Gemini 只在 Ollama/NIM 不可用後作備援。Elephant Alpha resource_optimization 告警補上待處理 action_plans 焦點列表,避免只報隊列數字卻沒有可執行對象。 - V10.324 補市場情報 candidate queue review AI summary Telegram dispatch report run package:新增 read-only report run package builder、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 report input 後整理 run package contract、evidence refs、package sections 與後續 report run readiness gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不派送 Telegram、不開 DB、不寫檔、不產報表、不更新 review_state、不掛 scheduler。 - V10.323 收斂市場情報 seed writer token hardening drift:正式端 service smoke 已確認 `seed_writer_cli_status` 不再回吐 `approval_token_hint`、不洩漏環境 token;輸出補 `api_reads_approval_token=false`、`api_executes_cli=false`、`api_writes_database=false`,讓 API/CLI 邊界可被 regression test 與 smoke 明確驗證。 - - V10.322 修正 Telegram 決策/審核推播舊入口:`price_decision_notify()` 改用 `send_telegram_with_result()` 統一套用 HTML sanitizer 與多 chat 結果彙整,並補齊 `price_decision(report_url=...)` 相容;RAG awaiting review 推播改用正確 `chat_ids=[...]` 呼叫,避免 Stage 4 人工審核按鈕因參數名錯誤完全送不出去。 + - V10.322 修正 Telegram 決策/審核推播舊入口:`price_decision_notify()` 改用 `send_telegram_with_result()` 統一套用 HTML sanitizer 與多 chat 結果彙整,並補齊 `price_decision(report_url=...)` 相容;RAG awaiting review 推播改用正確 `chat_ids=[...]` 呼叫,避免 Stage 4 AI 例外決策按鈕因參數名錯誤完全送不出去。 - V10.321 修正 Telegram HTML 發送格式:所有 `sendMessage` / `sendPhoto` caption 在 HTML parse mode 送出前會把 `
` / `
` / `
` 統一轉成換行,避免 Telegram Bot API 回 `Unsupported start tag "br"` 造成告警或報告送出失敗。 - V10.320 補市場情報 candidate queue review AI summary Telegram dispatch report input:新增 read-only report input builder、獨立 report route extension、UI 按鈕與 deployment readiness smoke target,在 archive summary 後整理 report input sections、report contract、message evidence 與 dispatch audit traceability;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不派送 Telegram、不開 DB、不寫檔、不產報表、不更新 review_state、不掛 scheduler。 - V10.319 補市場情報 candidate queue review AI summary Telegram dispatch archive summary:新增 read-only archive summary builder、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 Telegram dispatch archive 後整理 message identity、dispatch audit、artifact manifest 與後續 report input sections;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不派送 Telegram、不開 DB、不寫檔、不更新 review_state、不掛 scheduler。 - - V10.318 收緊 Elephant Alpha HITL 告警治理:`ea_escalation` 只有真正含 SKU/價格比較的 actions 才排成 TOP 待審 SKU 卡片;非 SKU 診斷改為「待確認事項」,並用測試鎖住價格類低信心但無 DB/Hermes 實證時只 suppress、不寫 human_review、不發 Telegram,避免空泛告警打擾人工審核。 + - V10.318 收緊 Elephant Alpha AI 例外決策 告警治理:`ea_escalation` 只有真正含 SKU/價格比較的 actions 才排成 TOP 待審 SKU 卡片;非 SKU 診斷改為「待確認事項」,並用測試鎖住價格類低信心但無 DB/Hermes 實證時只 suppress、不寫 human_review、不發 Telegram,避免空泛告警打擾AI 例外決策。 - V10.317 修正 PChome 比價覆蓋率分子:`fetch_competitor_coverage()` 的 valid_matches 改成 `ACTIVE + 有 MOMO 最新價` 商品與有效 PChome `identity_v2` 價格的交集,不再把非活躍或無 MOMO 現價的舊 competitor_prices 列入覆蓋率,避免 daily/growth/PPT/AI 報表高估比價資料品質。 - V10.315 修正競品簡報/報表指定日期取價:`fetch_competitor_comparison_results()` 在有 start/end date 時改讀 `competitor_price_history` 的期間快照,MOMO 價格也取期間結束前最新價;沒有指定日期才使用目前有效 `competitor_prices`,避免把今天的 PChome 快取價塞回歷史 daily/growth/PPT 判讀。 - V10.314 擴大 PChome 候選池與搜尋韌性:PChome 搜尋 API 改為依 limit 掃多頁並對 429/5xx/timeout 做有限重試;feeder 預設每個商品最多 5 組搜尋詞、每詞 20 候選、2 頁,且搜尋清理不再刪掉括號/方括號內的品牌與規格,讓正確候選更有機會進 matcher,而不是長期停在「待對比」。 - V10.312 強化 PChome 商品身份比對防錯配:matcher 開始解析 mg/mcg 劑量、件組套組與多規格集合,60ml+150ml vs 60ml+20ml、10mg vs 20mg、10片 vs 10盒、精華 vs 化妝水都會進硬否決或單位價覆核,不再靠單一規格重疊放行;覆核診斷同步新增「劑量差異」標籤,降低核心比價錯配污染 daily/growth/PPT/AI 分析。 - V10.311 統一競品價差語意:`fetch_competitor_comparison_results()`、competitor PPT 與 OpenClaw competitor prompt 全部改用 `MOMO - PChome`,正值代表 MOMO 較貴 / PChome 低價壓力,負值代表 MOMO 價格優勢;避免 daily/growth 顯示價格壓力但 PPT/AI 反向解讀。 - - V10.310 強化 MOMO/PChome 核心比價閉環:PChome feeder 搜尋候選只有強同款 `0.90` 才提前停止,避免第一個 0.76 次佳候選卡掉後續精準搜尋詞;人工否決的候選會被跳過並改挑下一個候選,不再讓已否決商品長期阻塞同 SKU。人工 `reject_identity`、`unit_price_required`、`needs_research` 會立即讓同候選正式 `competitor_prices` 過期,Dashboard 即使尚有舊價也不再顯示正式總價差;手機版比價覆核欄位標籤、覆核按鈕冒泡與候選證據顯示同步修正。 - - V10.308 修正商品列表 PChome 比價閉環狀態:`manual_rejected`、`manual_unit_price_required`、`manual_needs_research` 不再掉回籠統「待比對」,改顯示「人工已否決 / 人工標記單位價 / 人工要求補搜尋」與後續 feeder 行為說明,避免人工覆核後 UI 看起來像沒有處理。 - - V10.307 將 PChome 人工覆核成效接進 daily/growth/PPT 共用資料出口:`fetch_competitor_coverage()` 讀取 `competitor_match_reviews` 最新決策,輸出人工採用、人工否決、人工單位價與採用率;`daily_sales` 與 `growth_analysis` 的比價資料品質區塊直接顯示這些閉環指標,讓報表與簡報不只看待審數,也能看人工處理成效。 - - V10.305 將 PChome 人工覆核回饋接回 feeder:下一輪搜尋若命中已被 `reject_identity` 否決的同一候選,會記錄 `manual_rejected` 並跳過正式寫入;已被標記 `unit_price_required` 的候選只保留單位價比較,不寫入正式總價差;人工 `accept_identity` 可保守覆蓋低分門檻但會打 `manual_review/manual_accept` 標籤,讓核心比價閉環可被後續報表與簡報追蹤。 - - V10.304 補 PChome 比價人工覆核決策閉環:新增 `competitor_match_reviews`、`/api/pchome-review//decision` 與商品看板覆核列「採用同款 / 否決候選 / 標記單位價」動作;只有人工採用同款才寫入 `competitor_prices` + `competitor_price_history`,否決與單位價標記只追加 manual attempt 並關閉本輪覆核,避免錯配污染核心價差。 - - V10.302 補 PChome 比價覆核匯出與診斷原因:`filter=pchome_review` 每筆覆核把 matcher `reasons=` 翻成品牌不符、商品線不符、容量差異、組合差異、需單位價、價差極端等可行動標籤;新增 `/api/export/excel/pchome-review` 匯出完整覆核隊列、人工處置、候選 PChome、單位價比較與原始診斷,避免核心比價只停在籠統「待對比」。 - - V10.301 補市場情報 candidate queue review AI summary Telegram dispatch gate:新增 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_gate` 與 UI 按鈕,在 summary persistence closeout 後檢查 Telegram 訊息契約、channel label、artifact path、token 外洩風險與後續 run package promotion;API/UI 仍不讀 approval/Telegram token、不呼叫 LLM、不開 DB、不寫檔、不派送 Telegram、不掛 scheduler。 + - V10.310 強化 MOMO/PChome 核心比價閉環:PChome feeder 搜尋候選只有強同款 `0.90` 才提前停止,避免第一個 0.76 次佳候選卡掉後續精準搜尋詞;AI 否決的候選會被跳過並改挑下一個候選,不再讓已否決商品長期阻塞同 SKU。AI 例外 `reject_identity`、`unit_price_required`、`needs_research` 會立即讓同候選正式 `competitor_prices` 過期,Dashboard 即使尚有舊價也不再顯示正式總價差;手機版比價覆核欄位標籤、覆核按鈕冒泡與候選證據顯示同步修正。 + - V10.308 修正商品列表 PChome 比價閉環狀態:`manual_rejected`、`manual_unit_price_required`、`manual_needs_research` 不再掉回籠統「待比對」,改顯示「AI 已否決 / AI 標記單位價 / AI 要求補搜尋」與後續 feeder 行為說明,避免AI 例外決策後 UI 看起來像沒有處理。 + - V10.307 將 PChome AI 例外決策成效接進 daily/growth/PPT 共用資料出口:`fetch_competitor_coverage()` 讀取 `competitor_match_reviews` 最新決策,輸出AI 採用、AI 否決、AI 單位價與採用率;`daily_sales` 與 `growth_analysis` 的比價資料品質區塊直接顯示這些閉環指標,讓報表與簡報不只看待審數,也能看AI 例外處理成效。 + - V10.305 將 PChome AI 例外決策回饋接回 feeder:下一輪搜尋若命中已被 `reject_identity` 否決的同一候選,會記錄 `manual_rejected` 並跳過正式寫入;已被標記 `unit_price_required` 的候選只保留單位價比較,不寫入正式總價差;人工 `accept_identity` 可保守覆蓋低分門檻但會打 `manual_review/manual_accept` 標籤,讓核心比價閉環可被後續報表與簡報追蹤。 + - V10.304 補 PChome 比價AI 例外決策決策閉環:新增 `competitor_match_reviews`、`/api/pchome-review//decision` 與商品看板覆核列「採用同款 / 否決候選 / 標記單位價」動作;只有AI 採用同款才寫入 `competitor_prices` + `competitor_price_history`,否決與單位價標記只追加 manual attempt 並關閉本輪覆核,避免錯配污染核心價差。 + - V10.302 補 PChome 比價覆核匯出與診斷原因:`filter=pchome_review` 每筆覆核把 matcher `reasons=` 翻成品牌不符、商品線不符、容量差異、組合差異、需單位價、價差極端等可行動標籤;新增 `/api/export/excel/pchome-review` 匯出完整覆核隊列、AI 例外處置、候選 PChome、單位價比較與原始診斷,避免核心比價只停在籠統「待對比」。 + - V10.301 補市場情報 candidate queue review AI summary Telegram dispatch gate:新增 `/api/market_intel/ai_controlled/review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_gate` 與 UI 按鈕,在 summary persistence closeout 後檢查 Telegram 訊息契約、channel label、artifact path、token 外洩風險與後續 run package promotion;API/UI 仍不讀 approval/Telegram token、不呼叫 LLM、不開 DB、不寫檔、不派送 Telegram、不掛 scheduler。 - V10.300 補商品看板比價覆核狀態分流:`filter=pchome_review` 新增全部、需單位價、身份否決、低信心、價格過期、找不到同款 segmented 篩選與分頁保留參數,讓 6,000+ 筆覆核隊列能依 matcher 診斷類型分批處理;同步修正覆核列表表頭/分頁連結狀態保留。 - - V10.299 補市場情報 candidate queue review AI summary persistence run closeout:新增 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_run_closeout` 與 UI 按鈕,在 receipt 通過後收尾 metadata_json persistence gate,確認 closeout artifact、操作員確認與後續 Telegram dispatch 必須另開 gate;API/UI 仍不讀 approval token、不執行 CLI、不連 DB、不寫 `metadata_json`、不派送 Telegram、不掛 scheduler。 - - V10.298 補市場情報 candidate queue review AI summary persistence run receipt:新增 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_run_receipt` 與 UI 按鈕,審核操作員貼回的 metadata_json CLI writer output、post-write smoke、dedupe key、summary payload hash、artifact path 與 token 外洩風險;API/UI 仍不讀 approval token、不執行 CLI、不連 DB、不寫 `metadata_json`、不派送 Telegram、不掛 scheduler。 - - V10.297 將 PChome 單位價覆核隊列接回商品看板第一屏:KPI 顯示待處理/需單位價覆核數,焦點區列出候選 PChome 商品、候選價、match score 與人工動作;新增 `filter=pchome_review` 的比價覆核隊列,讓使用者可直接進入待處理 SKU,不再只在 daily/growth/PPT 摘要看到統計。 + - V10.299 補市場情報 candidate queue review AI summary persistence run closeout:新增 `/api/market_intel/ai_controlled/review/candidate_queue_review_ai_summary_persistence_run_closeout` 與 UI 按鈕,在 receipt 通過後收尾 metadata_json persistence gate,確認 closeout artifact、操作員確認與後續 Telegram dispatch 必須另開 gate;API/UI 仍不讀 approval token、不執行 CLI、不連 DB、不寫 `metadata_json`、不派送 Telegram、不掛 scheduler。 + - V10.298 補市場情報 candidate queue review AI summary persistence run receipt:新增 `/api/market_intel/ai_controlled/review/candidate_queue_review_ai_summary_persistence_run_receipt` 與 UI 按鈕,審核操作員貼回的 metadata_json CLI writer output、post-write smoke、dedupe key、summary payload hash、artifact path 與 token 外洩風險;API/UI 仍不讀 approval token、不執行 CLI、不連 DB、不寫 `metadata_json`、不派送 Telegram、不掛 scheduler。 + - V10.297 將 PChome 單位價覆核隊列接回商品看板第一屏:KPI 顯示待處理/需單位價覆核數,焦點區列出候選 PChome 商品、候選價、match score 與 AI 例外動作;新增 `filter=pchome_review` 的比價覆核隊列,讓使用者可直接進入待處理 SKU,不再只在 daily/growth/PPT 摘要看到統計。 - V10.296 補核心 MOMO/PChome 比價第三層語意與覆核閉環:同核心商品但買送、套組、件數不同且只有單一基礎規格時標記 `unit_comparable`,只寫入 `competitor_match_attempts`;商品看板、daily/growth 報表、OpenClaw/PPT 摘要共用 `competitor_intel_repository` 的覆核隊列,顯示「需單位價比較」、候選商品、候選 PChome 價格與單位價換算證據;多容量/多品項套組仍保持不可比較,避免把不同販售組合直接寫進正式總價差。 - - V10.289 重排 Elephant Alpha L3 HITL `ea_escalation` Telegram 告警:改成專業 incident brief 格式,分成決策狀態、背景摘要、風險摘要、TOP 待審 SKU 與建議處置;價格行動會拆出 MOMO/PChome 價格、價差、人工處置與 PChome ID,避免長 bullet 難讀。 + - V10.289 重排 Elephant Alpha L3 AI 例外決策 `ea_escalation` Telegram 告警:改成專業 incident brief 格式,分成決策狀態、背景摘要、風險摘要、TOP 待審 SKU 與建議處置;價格行動會拆出 MOMO/PChome 價格、價差、AI 例外處置與 PChome ID,避免長 bullet 難讀。 - V10.284 關閉 Code Review Hermes LLM scan 預設路徑:Step 2 改 deterministic fast static scan,不再讓部署後先卡三段 Ollama timeout;若需要 LLM scan 可用 `CODE_REVIEW_HERMES_LLM_SCAN_ENABLED=true` 顯式開啟,仍只走本地矩陣、不走 Gemini。 - V10.283 將 Code Review Hermes scan 收斂為 fast compact prompt:預設 2 檔 × 900 字、輸出 384 tokens,仍走 GCP-A → GCP-B → 111 本地矩陣,避免部署後 code_review_hermes 先卡三段 timeout。 - V10.282 補齊 Code Review Hermes scan 本地模型矩陣:掃描階段也走 GCP-A `qwen2.5-coder:7b` → GCP-B `gemma3:4b` → 111 `hermes3:latest`,避免 `hermes3` 在三主機各卡 35s 後只留下 error;Hermes scan 不會啟用 Gemini。 - V10.281 強化 Code Review OpenClaw 本地備援矩陣:主機順序仍為 GCP-A → GCP-B → 111,但改成 GCP-A `qwen2.5-coder:7b`、GCP-B `gemma3:4b`、111 `hermes3:latest`,三段本地 Ollama 全失敗後才允許 Claude/Gemini 備援。 - V10.279 收斂 Code Review Ollama-first 路徑:OpenClaw assessment 預設改 `qwen2.5-coder:7b` + 45s/host timeout,Hermes scan 改 compact snippet + 35s/host timeout,避免三主機各卡 120s 後被迫觸發 Gemini 備援。 - V10.278 補 PChome 競價摘要 30 分鐘共享快取與 feeder/backfill 主動清除,並新增市場情報 `candidate_queue_review_ai_summary_preflight` 預覽 gate;API 只檢查未來摘要輸入與 Ollama-first/Gemini-backup-only policy,不呼叫 LLM、不派 Telegram、不寫 DB、不掛 scheduler。 - - V10.276 修正 ElephantAlpha 價格類 Hermes prefetch timeout:`price_drop` / `market_opportunity` trigger 直接把 SQL 命中的 MOMO / PChome 價差實證轉成 HITL action lines,完整 Hermes LLM prefetch 預設關閉;無 DB 實證仍只記 suppressed telemetry / cooldown,不寫 `human_review`、不發空 Telegram。 + - V10.276 修正 ElephantAlpha 價格類 Hermes prefetch timeout:`price_drop` / `market_opportunity` trigger 直接把 SQL 命中的 MOMO / PChome 價差實證轉成 AI 例外決策 action lines,完整 Hermes LLM prefetch 預設關閉;無 DB 實證仍只記 suppressed telemetry / cooldown,不寫 `human_review`、不發空 Telegram。 - V10.266 強化核心 MOMO/PChome 比價鏈路:新增 `marketplace_product_matcher.py` 身份比對、只讓 `identity_v2` 且分數 ≥ 0.76 的高信心配對進 Dashboard/AI/Excel/Daily/Growth/PPT,並建立 `competitor_intel_repository.py` 統一圖表與簡報資料出口;同品牌但不同型號、不同組數、套組/單品或多品項不一致會進待審,不進正式比價。 - V10.267 專業化 ElephantAlpha `resource_optimization` 告警:不再讓 LLM 生成「48 小時預期效益 / 已執行」敘事,改由程式量測 action queue、P1/P2、pending_review、逾時項目與 CPU load;單純 backlog 不發 Telegram,只有可行動資源壓力才寫 `ai_insights(resource_pressure)` 並發送量測型告警。 - V10.254 續補 `/growth_analysis` 快取命中效能:PostgreSQL source fingerprint 加 60 秒短 TTL,匯入 realtime_sales_monthly 後同步清除 growth shared cache 與短快取,避免快取命中仍頻繁掃大表 COUNT。 - - V10.253 修正 Elephant Alpha L3 HITL 空告警:價格類與資源調配低信心事件若沒有 Hermes/實證資料,只記 suppressed telemetry 與 cooldown,不寫 pending human_review、不發 Telegram;`resource_optimization` 會保留 queue/load 原始指標供追查。 + - V10.253 修正 Elephant Alpha L3 AI 例外決策 空告警:價格類與資源調配低信心事件若沒有 Hermes/實證資料,只記 suppressed telemetry 與 cooldown,不寫 pending human_review、不發 Telegram;`resource_optimization` 會保留 queue/load 原始指標供追查。 - V10.251 修正 OpenClaw Q&A 備援遙測:Ollama 主路徑仍為 GCP-A → GCP-B → 111,Gemini 只記為 `openclaw_qa_gemini_fallback`,NIM 只記為 `openclaw_qa_nim`;AI Calls 會把 legacy `openclaw_qa + gemini` 標成 Gemini 備援,避免再次誤判 Gemini-first。 - V10.251 穩定 `/growth_analysis` 正式站速度:成長分析快取從單 worker memory 擴充為 `data/growth_analysis_cache.pkl` 跨 worker 共享快取,避免 Gunicorn 冷 worker 偶發掃明細表造成 5 秒級 TTFB;補 `tests/test_cache_manager.py` 覆蓋 shared file roundtrip 與清除行為。 - V10.249 收斂 `/observability/ppt_audit_history` 手機與平板第一屏密度:將 4 個產線訊號從 hero 內移出成獨立狀態列,手機版維持 2 欄狀態卡並降低 hero 卡片間距;本機 10 個 AI 觀測台頁面 rendered visual contract 全數通過,PPT 頁 hero 高度 desktop/tablet/mobile 為 214/361/398px。 @@ -289,7 +2363,7 @@ - Phase 4 手動 dry-run discovery:新增 `services/market_intel/discovery_runner.py` 與 `/api/market_intel/manual_discovery`。預設 `fetch=false` 只回 planned;`fetch=true` 也必須在 `MARKET_INTEL_ENABLED` 與 `MARKET_INTEL_CRAWLER_ENABLED` 同時開啟時才允許 HTTP,且仍不寫 DB、不掛 scheduler。 - Phase 5 parser 診斷層:新增 `services/market_intel/html_diagnostics.py`,手動 fetch 成功後只萃取 title、page_hash、link count、campaign link candidates,不建立正式 campaign/product。 - Phase 6 平台別 scorer:MOMO/PChome adapter 提供 URL/text 加權規則,diagnostics 同時輸出 generic_score 與 platform_score,仍只用於候選排序與人工診斷。 - - Phase 7 confidence bands:diagnostics 將候選標成 `high` / `medium` / `low`,並輸出 `confidence_reason`;只作人工審核提示,不自動建立活動。 + - Phase 7 confidence bands:diagnostics 將候選標成 `high` / `medium` / `low`,並輸出 `confidence_reason`;只作AI 例外決策提示,不自動建立活動。 - Phase 8 candidate preview API:新增 `/api/market_intel/candidate_preview`,把本次 discovery diagnostics 候選合併排序,支援 `min_band` 與 `limit`,只供人工預覽,不入庫。 - Phase 9 UI preview panel:`templates/market_intel/disabled.html` 讀取 `/api/market_intel/candidate_preview?fetch=false`,顯示安全空狀態與 flags,不做自動外部 fetch。 - Phase 10 platform seed plan:新增 `/api/market_intel/platform_seed_plan`,只產生 adapter registry 對應的 `market_platforms` seed rows 與 gate 狀態,不寫 DB。 @@ -319,55 +2393,55 @@ - Phase 27 legacy source bridge preview:新增 `services/market_intel/legacy_source_bridge.py` 與 `/api/market_intel/legacy_source_bridge`,只讀盤點既有 `promo_products`、`competitor_prices`、`competitor_price_history`,產生導入 `market_*` 的 mapping / dedupe / blocked operation preview;預設 `execute=false` 不連 DB,`execute=true` 也只做 read-only query,不寫 DB、不建立 ORM session、不連外、不掛 scheduler;UI 新增「既有資料橋接預覽」panel;版本同步至 V10.182。 - Phase 45 migration live smoke preview:新增 `/api/market_intel/migration_live_smoke` 與 UI「正式 DB 只讀 smoke」panel;預設 `execute=false` 只回 planned,人工 smoke 才可用 `execute=true` 整理 catalog / seed diff 結果,不執行 migration、不寫 DB、不跑 rollback、不掛 scheduler;版本同步至 V10.207。 - Phase 46 live DB inventory preview:新增 `/api/market_intel/live_db_inventory` 與 UI「正式 DB 庫存總覽」panel;預設 `execute=false` 不連 DB,人工 smoke 才可用 `execute=true` 對 `market_*` tables 做只讀 count / group by,建立平台、活動、商品、比對、告警與 crawler run 基準;版本同步至 V10.209。 - - Phase 47 manual sample fetch plan:新增 `/api/market_intel/manual_sample_plan` 與 UI「人工樣本 Fetch 計畫」panel;預設只輸出平台順序、每平台 1 個公開入口、MCP gate、操作員步驟與備援,不抓外站、不寫 DB、不建立 crawler run、不掛 scheduler;版本同步至 V10.214。 - - Phase 48 manual sample acceptance contract:新增 `/api/market_intel/manual_sample_acceptance` 與 UI「樣本結果驗收契約」panel;定義 sample result 必要欄位、diagnostics 欄位、驗收門檻、拒收條件、人工決策與升級順序,不載入 sample result、不抓外站、不允許候選導入、不寫 DB;版本同步至 V10.215。 - - Phase 49 manual sample result review:新增 `services/market_intel/manual_sample_review.py`、`/api/market_intel/manual_sample_review` 與 UI「樣本結果審核預覽」panel;以純函式評估人工 sample result 是否可進候選預覽,預設不載入結果、不抓外站、不存檔、不寫 DB、不允許候選導入、不掛 scheduler;版本同步至 V10.216。 - - Phase 50 manual sample review evaluate:新增 `/api/market_intel/manual_sample_review/evaluate` POST 與 UI JSON 審核入口,允許操作員貼入單筆 sample result 即時回傳 PASS/BLOCK;不保存 payload、不回吐完整 HTML、不寫 DB、不建立候選活動、不允許候選導入、不掛 scheduler;版本同步至 V10.219。 - - V10.220 補 Phase 50 UI POST CSRF header:`manual_sample_review/evaluate` 保持 CSRF 保護,頁面 fetch 送出 `X-CSRFToken`,不豁免安全檢查。 - - Phase 51 manual sample candidate handoff:新增 `/api/market_intel/manual_sample_review/candidate_handoff` POST 與 UI handoff 按鈕,將已通過審核的 sample result 轉成只讀候選活動 preview payload;不保存 handoff、不建立 review queue、不寫 market_*、不允許候選導入、不掛 scheduler;版本同步至 V10.222。 - - Phase 52 manual sample candidate queue draft:新增 `services/market_intel/manual_sample_candidate_queue.py`、`/api/market_intel/manual_sample_review/candidate_queue_draft` POST 與 UI queue 草案按鈕,將 handoff 候選轉成只讀人工審核 queue draft;不建立正式 queue、不保存草案、不寫 market_*、不自動核准候選、不掛 scheduler;版本同步至 V10.223。 + - Phase 47 AI-controlled sample fetch plan:新增 `/api/market_intel/ai_controlled/sample_plan` 與 UI「人工樣本 Fetch 計畫」panel;預設只輸出平台順序、每平台 1 個公開入口、MCP gate、操作員步驟與備援,不抓外站、不寫 DB、不建立 crawler run、不掛 scheduler;版本同步至 V10.214。 + - Phase 48 AI-controlled sample acceptance contract:新增 `/api/market_intel/ai_controlled/sample_acceptance` 與 UI「樣本結果驗收契約」panel;定義 sample result 必要欄位、diagnostics 欄位、驗收門檻、拒收條件、人工決策與升級順序,不載入 sample result、不抓外站、不允許候選導入、不寫 DB;版本同步至 V10.215。 + - Phase 49 AI-controlled sample result review:新增 `services/market_intel/service.py canonical sample-review builder`、`/api/market_intel/ai_controlled/sample_review` 與 UI「樣本結果審核預覽」panel;以純函式評估人工 sample result 是否可進候選預覽,預設不載入結果、不抓外站、不存檔、不寫 DB、不允許候選導入、不掛 scheduler;版本同步至 V10.216。 + - Phase 50 AI-controlled sample review evaluate:新增 `/api/market_intel/ai_controlled/sample_review/evaluate` POST 與 UI JSON 審核入口,允許操作員貼入單筆 sample result 即時回傳 PASS/BLOCK;不保存 payload、不回吐完整 HTML、不寫 DB、不建立候選活動、不允許候選導入、不掛 scheduler;版本同步至 V10.219。 + - V10.220 補 Phase 50 UI POST CSRF header:`ai_controlled/sample_review/evaluate` 保持 CSRF 保護,頁面 fetch 送出 `X-CSRFToken`,不豁免安全檢查。 + - Phase 51 AI-controlled sample candidate handoff:新增 `/api/market_intel/ai_controlled/review/candidate_handoff` POST 與 UI handoff 按鈕,將已通過審核的 sample result 轉成只讀候選活動 preview payload;不保存 handoff、不建立 review queue、不寫 market_*、不允許候選導入、不掛 scheduler;版本同步至 V10.222。 + - Phase 52 AI-controlled sample candidate queue draft:新增 `services/market_intel/ai_controlled_service_compat.py compatibility layer`、`/api/market_intel/ai_controlled/review/candidate_queue_draft` POST 與 UI queue 草案按鈕,將 handoff 候選轉成只讀AI 例外決策 queue draft;不建立正式 queue、不保存草案、不寫 market_*、不自動核准候選、不掛 scheduler;版本同步至 V10.223。 - V10.224 補 PPT 報表覆蓋矩陣:`/observability/ppt_audit_history` 將每個定義簡報同列串起 DB 寫入、線上預覽、視覺 QA 與交付狀態,並提供預覽、預熱、重跑操作,避免只顯示「目標已產生」。 - - Phase 53 manual sample candidate queue approval:新增 `/api/market_intel/manual_sample_review/candidate_queue_approval` POST 與 UI 送審 gate 按鈕,將 queue draft row preview 對齊既有 `market_alert_review_queue` 契約,檢查必填欄位、寫入 flags、備份與人工批准 gate;不建立 approval record、不寫 review queue、不開 DB transaction、不掛 scheduler;版本同步至 V10.225。 + - Phase 53 AI-controlled sample candidate queue approval:新增 `/api/market_intel/ai_controlled/review/candidate_queue_approval` POST 與 UI 送審 gate 按鈕,將 queue draft row preview 對齊既有 `market_alert_review_queue` 契約,檢查必填欄位、寫入 flags、備份與人工批准 gate;不建立 approval record、不寫 review queue、不開 DB transaction、不掛 scheduler;版本同步至 V10.225。 - V10.226 補 PPT 視覺 QA runtime checklist:`/observability/ppt_audit_history` 在視覺模型未就緒時顯示 Feature Flag、LibreOffice、Vision Model 三段檢查與下一步操作,避免只看到「停用」而不知道卡在哪。 - - Phase 54 manual sample candidate queue transaction:新增 `/api/market_intel/manual_sample_review/candidate_queue_transaction` POST 與 UI transaction preview 按鈕,將 queue row preview 轉成 `market_alert_review_queue` idempotent insert statement、payload hash 與 rollback plan;不開 DB connection、不開 transaction、不 commit、不建立 approval record;版本同步至 V10.227。 + - Phase 54 AI-controlled sample candidate queue transaction:新增 `/api/market_intel/ai_controlled/review/candidate_queue_transaction` POST 與 UI transaction preview 按鈕,將 queue row preview 轉成 `market_alert_review_queue` idempotent insert statement、payload hash 與 rollback plan;不開 DB connection、不開 transaction、不 commit、不建立 approval record;版本同步至 V10.227。 - V10.228 補 PPT 視覺 QA 背景狀態卡:新增 `/observability/ppt_audit/vision_status` 與頁面 Vision QA 狀態卡,讓立即視覺 QA 排入後可看 queued/running/completed/error 與最近審核摘要,不必刷新猜測。 - V10.229 修正 PPT 視覺 QA 多 worker 狀態漂移:將 queued/running/completed/error 寫入 `/app/data/ppt_vision_audit_status.json` runtime state,所有 Gunicorn worker 共用同一份狀態並阻擋重複排入。 - - Phase 55 candidate queue writer CLI gate:新增 `/api/market_intel/manual_sample_review/candidate_queue_writer_status` POST、`scripts/market_intel_candidate_queue_writer.py` 與 UI writer gate 按鈕,定義 `MARKET_INTEL_QUEUE_WRITE_APPROVAL` 一次性 token、execute/apply flags、備份、migration smoke 與 rollback gate;本階段仍不開 DB connection、不寫 `market_alert_review_queue`、不 commit、不掛 scheduler;版本同步至 V10.230。 - - Phase 56 candidate queue writer preflight:新增 `/api/market_intel/manual_sample_review/candidate_queue_writer_preflight` POST 與 `services/market_intel/candidate_queue_writer_preflight.py`,檢查 transaction payload key 到 `market_alert_review_queue` 欄位映射、缺欄與 dedupe unique index;頁面預設 execute=false 不連 DB,CLI 可明確 `--read-only-preflight` 只讀 catalog;版本同步至 V10.232。 + - Phase 55 candidate queue writer CLI gate:新增 `/api/market_intel/ai_controlled/review/candidate_queue_writer_status` POST、`scripts/market_intel_candidate_queue_writer.py` 與 UI writer gate 按鈕,定義 `MARKET_INTEL_QUEUE_WRITE_APPROVAL` 一次性 token、execute/apply flags、備份、migration smoke 與 rollback gate;本階段仍不開 DB connection、不寫 `market_alert_review_queue`、不 commit、不掛 scheduler;版本同步至 V10.230。 + - Phase 56 candidate queue writer preflight:新增 `/api/market_intel/ai_controlled/review/candidate_queue_writer_preflight` POST 與 `services/market_intel/candidate_queue_writer_preflight.py`,檢查 transaction payload key 到 `market_alert_review_queue` 欄位映射、缺欄與 dedupe unique index;頁面預設 execute=false 不連 DB,CLI 可明確 `--read-only-preflight` 只讀 catalog;版本同步至 V10.232。 - Phase 57 candidate queue writer CLI transaction:`scripts/market_intel_candidate_queue_writer.py` 在 CLI-only 情境支援受控 transaction,必須同時通過 transaction payload、read-only preflight、`--execute`、`--apply-real-write`、一次性 token、備份確認與 migration live smoke 才會以 SQLAlchemy Core idempotent insert `market_alert_review_queue`;API/UI 仍不傳 token、不連 DB、不寫 queue、不掛 scheduler;版本同步至 V10.234。 - V10.235 補 PPT 視覺 QA stale recovery:背景狀態寫入 worker PID;若部署 reload 後舊 PID 已不存在,`/observability/ppt_audit/vision_status` 會自動把 running 轉為可診斷 error 並允許重新排入,避免人工清 runtime state。 - - Phase 58 candidate queue writer post-write smoke:新增 `services/market_intel/candidate_queue_writer_postwrite_smoke.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_writer_postwrite_smoke` 與 UI smoke 按鈕,依 transaction preview 的 dedupe key 只讀查詢 `market_alert_review_queue`,讓 CLI 真寫入後可驗證 row 是否存在;頁面預設 execute=false 不連 DB、不寫 queue、不 commit、不掛 scheduler;版本同步至 V10.236。 - - Phase 59 candidate queue writer operator drill:新增 `services/market_intel/candidate_queue_writer_operator_drill.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_writer_operator_drill` 與 UI drill 按鈕,組裝 reviewed sample、備份、read-only preflight、CLI writer、post-write smoke 的操作員順序;API/UI 不讀 approval token、不執行 CLI、不連 DB、不寫 queue、不 commit、不掛 scheduler;版本同步至 V10.237。 + - Phase 58 candidate queue writer post-write smoke:新增 `services/market_intel/candidate_queue_writer_postwrite_smoke.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_writer_postwrite_smoke` 與 UI smoke 按鈕,依 transaction preview 的 dedupe key 只讀查詢 `market_alert_review_queue`,讓 CLI 真寫入後可驗證 row 是否存在;頁面預設 execute=false 不連 DB、不寫 queue、不 commit、不掛 scheduler;版本同步至 V10.236。 + - Phase 59 candidate queue writer operator drill:新增 `services/market_intel/candidate_queue_writer_operator_drill.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_writer_operator_drill` 與 UI drill 按鈕,組裝 reviewed sample、備份、read-only preflight、CLI writer、post-write smoke 的操作員順序;API/UI 不讀 approval token、不執行 CLI、不連 DB、不寫 queue、不 commit、不掛 scheduler;版本同步至 V10.237。 - V10.238 補業績圖表 runtime QA 與分析 tabs 窄版修正:新增 `quick_review --sales-charts` 檢查 `/daily_sales`、`/growth_analysis` 的 Chart.js ready、可繪製資料集與 canvas 非空白;同時把分析報表 tabs 手機版改為自適應 grid,避免 Metabase/Grist 外部連結超出右側。 - - Phase 60 candidate queue writer run package:新增 `services/market_intel/candidate_queue_writer_run_package.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_writer_run_package` 與 UI run package 按鈕,整理正式 CLI 小流量寫入前的 payload manifest、required artifacts、command bundle、operator signoff 與 rollback plan;API/UI 不產檔、不讀 approval token、不執行 CLI、不連 DB、不寫 queue、不 commit、不掛 scheduler;版本同步至 V10.240。 - - Phase 61 candidate queue writer run readiness:新增 `services/market_intel/candidate_queue_writer_run_readiness.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_writer_run_readiness` 與 UI readiness 按鈕,檢查 reviewed sample 檔案路徑、備份路徑、preflight 輸出、migration live smoke、shell-only token acknowledgement 與禁止 token 進 API;API/UI 不產檔、不讀 approval token、不執行 CLI、不連 DB、不寫 queue、不 commit、不掛 scheduler;版本同步至 V10.245。 - - Phase 62 candidate queue writer run receipt:新增 `services/market_intel/candidate_queue_writer_run_receipt.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_writer_run_receipt` 與 UI receipt 按鈕,審核 CLI 寫入後的 writer output、post-write smoke、dedupe key 一致性與 artifact 路徑;API/UI 不回吐 receipt 原文、不讀 approval token、不執行 CLI、不連 DB、不寫 queue、不掛 scheduler;版本同步至 V10.247。 - - Phase 63 candidate queue writer run closeout:新增 `services/market_intel/candidate_queue_writer_run_closeout.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_writer_run_closeout` 與 UI closeout 按鈕,在 receipt 通過後檢查 closeout artifact、操作員人工 queue review/read-only inventory 確認與安全 promotion gate;API/UI 不回吐原始 receipt、不讀 approval token、不執行 CLI、不連 DB、不寫 queue、不掛 scheduler;版本同步至 V10.248。 - - Phase 64 candidate queue review handoff:新增 `services/market_intel/candidate_queue_review_handoff.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_handoff` 與 UI handoff 按鈕,將 writer closeout 轉成人工 queue review / read-only inventory 交接契約;API/UI 不讀 approval token、不查 DB、不更新 review_state、不補寫 queue、不掛 scheduler;版本同步至 V10.251。 - - Phase 65 candidate queue review inventory:新增 `services/market_intel/candidate_queue_review_inventory.py`、`routes/market_intel_review_routes.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_inventory` 與 UI inventory 按鈕,把 handoff、post-write smoke、live DB inventory 合併成只讀人工審核庫存檢查;預設不連 DB,人工只讀查詢仍不更新 review_state、不補寫 queue、不讀 token、不掛 scheduler;版本同步至 V10.252。 - - Phase 66 candidate queue review decision:新增 `services/market_intel/candidate_queue_review_decision.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_decision` 與 UI decision 按鈕,將通過 inventory 的 queue row 整理成人工決策草案;API/UI 不更新 review_state、不寫 decision record、不讀 token、不掛 scheduler;版本同步至 V10.254。 - - Phase 67 candidate queue review decision approval:新增 `services/market_intel/candidate_queue_review_decision_approval.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_decision_approval` 與 UI approval gate 按鈕,檢查人工決策草案是否可進入下一個 CLI-only transaction preview;API/UI 不更新 review_state、不寫 decision record、不建立 approval record、不讀 token、不掛 scheduler;版本同步至 V10.255。 - - Phase 68 candidate queue review decision transaction:新增 `services/market_intel/candidate_queue_review_decision_transaction.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_decision_transaction` 與 UI transaction preview 按鈕,將 approval update preview 轉成 `review_state` update statement、payload hash 與 rollback plan;API/UI 不更新 review_state、不開 DB connection、不執行 CLI、不讀 token、不掛 scheduler;版本同步至 V10.256。 - - Phase 69 candidate queue review decision writer CLI gate:新增 `services/market_intel/candidate_queue_review_decision_writer_cli.py`、`scripts/market_intel_review_decision_writer.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_status` 與 UI writer gate 按鈕,先建立 shell-only review_state writer gate 與 command bundle;writer implementation 本階段保持 disabled,API/UI 不讀 token、不執行 CLI、不連 DB、不更新 review_state、不掛 scheduler;版本同步至 V10.257。 - - Phase 70 candidate queue review decision writer preflight:新增 `services/market_intel/candidate_queue_review_decision_writer_preflight.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_preflight` 與 UI preflight 按鈕,檢查 writer status、review_state update payload、狀態轉換與禁止 token 進 API;API/UI 即使收到 execute/apply_real_write 也不連 DB、不執行 CLI、不更新 review_state、不 commit、不讀 token、不掛 scheduler;版本同步至 V10.258。 + - Phase 60 candidate queue writer run package:新增 `services/market_intel/candidate_queue_writer_run_package.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_writer_run_package` 與 UI run package 按鈕,整理正式 CLI 小流量寫入前的 payload manifest、required artifacts、command bundle、operator signoff 與 rollback plan;API/UI 不產檔、不讀 approval token、不執行 CLI、不連 DB、不寫 queue、不 commit、不掛 scheduler;版本同步至 V10.240。 + - Phase 61 candidate queue writer run readiness:新增 `services/market_intel/candidate_queue_writer_run_readiness.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_writer_run_readiness` 與 UI readiness 按鈕,檢查 reviewed sample 檔案路徑、備份路徑、preflight 輸出、migration live smoke、shell-only token acknowledgement 與禁止 token 進 API;API/UI 不產檔、不讀 approval token、不執行 CLI、不連 DB、不寫 queue、不 commit、不掛 scheduler;版本同步至 V10.245。 + - Phase 62 candidate queue writer run receipt:新增 `services/market_intel/candidate_queue_writer_run_receipt.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_writer_run_receipt` 與 UI receipt 按鈕,審核 CLI 寫入後的 writer output、post-write smoke、dedupe key 一致性與 artifact 路徑;API/UI 不回吐 receipt 原文、不讀 approval token、不執行 CLI、不連 DB、不寫 queue、不掛 scheduler;版本同步至 V10.247。 + - Phase 63 candidate queue writer run closeout:新增 `services/market_intel/candidate_queue_writer_run_closeout.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_writer_run_closeout` 與 UI closeout 按鈕,在 receipt 通過後檢查 closeout artifact、操作員人工 queue review/read-only inventory 確認與安全 promotion gate;API/UI 不回吐原始 receipt、不讀 approval token、不執行 CLI、不連 DB、不寫 queue、不掛 scheduler;版本同步至 V10.248。 + - Phase 64 candidate queue review handoff:新增 `services/market_intel/candidate_queue_review_handoff.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_review_handoff` 與 UI handoff 按鈕,將 writer closeout 轉成人工 queue review / read-only inventory 交接契約;API/UI 不讀 approval token、不查 DB、不更新 review_state、不補寫 queue、不掛 scheduler;版本同步至 V10.251。 + - Phase 65 candidate queue review inventory:新增 `services/market_intel/candidate_queue_review_inventory.py`、`routes/market_intel_review_routes.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_review_inventory` 與 UI inventory 按鈕,把 handoff、post-write smoke、live DB inventory 合併成只讀AI 例外決策庫存檢查;預設不連 DB,人工只讀查詢仍不更新 review_state、不補寫 queue、不讀 token、不掛 scheduler;版本同步至 V10.252。 + - Phase 66 candidate queue review decision:新增 `services/market_intel/candidate_queue_review_decision.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_review_decision` 與 UI decision 按鈕,將通過 inventory 的 queue row 整理成人工決策草案;API/UI 不更新 review_state、不寫 decision record、不讀 token、不掛 scheduler;版本同步至 V10.254。 + - Phase 67 candidate queue review decision approval:新增 `services/market_intel/candidate_queue_review_decision_approval.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_review_decision_approval` 與 UI approval gate 按鈕,檢查人工決策草案是否可進入下一個 CLI-only transaction preview;API/UI 不更新 review_state、不寫 decision record、不建立 approval record、不讀 token、不掛 scheduler;版本同步至 V10.255。 + - Phase 68 candidate queue review decision transaction:新增 `services/market_intel/candidate_queue_review_decision_transaction.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_review_decision_transaction` 與 UI transaction preview 按鈕,將 approval update preview 轉成 `review_state` update statement、payload hash 與 rollback plan;API/UI 不更新 review_state、不開 DB connection、不執行 CLI、不讀 token、不掛 scheduler;版本同步至 V10.256。 + - Phase 69 candidate queue review decision writer CLI gate:新增 `services/market_intel/candidate_queue_review_decision_writer_cli.py`、`scripts/market_intel_review_decision_writer.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_review_decision_writer_status` 與 UI writer gate 按鈕,先建立 shell-only review_state writer gate 與 command bundle;writer implementation 本階段保持 disabled,API/UI 不讀 token、不執行 CLI、不連 DB、不更新 review_state、不掛 scheduler;版本同步至 V10.257。 + - Phase 70 candidate queue review decision writer preflight:新增 `services/market_intel/candidate_queue_review_decision_writer_preflight.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_review_decision_writer_preflight` 與 UI preflight 按鈕,檢查 writer status、review_state update payload、狀態轉換與禁止 token 進 API;API/UI 即使收到 execute/apply_real_write 也不連 DB、不執行 CLI、不更新 review_state、不 commit、不讀 token、不掛 scheduler;版本同步至 V10.258。 - V10.259 補 Phase 70 preflight 合約與 OCLearn queue 時區:preflight 補 planned/read-only catalog probe 欄位、dedupe unique index 檢查與 route 重複註冊清理;OCLearn embedding queue 的 created_at/updated_at/stale cutoff 改為台北 naive,避免 UTC/台北時間差讓 processing 任務卡住。 - - Phase 71 candidate queue review decision writer post-write smoke:新增 `services/market_intel/candidate_queue_review_decision_writer_postwrite_smoke.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_postwrite_smoke` 與 UI smoke 按鈕,人工 CLI 更新 review_state 後可用 dedupe key 只讀驗證 row 是否存在且 state 符合預期;API/UI 預設不連 DB,execute=true 也只讀查詢,不更新 review_state、不 commit、不讀 token、不掛 scheduler;版本同步至 V10.260。 - - Phase 72 candidate queue review decision writer operator drill:新增 `services/market_intel/candidate_queue_review_decision_writer_operator_drill.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_operator_drill` 與 UI drill 按鈕,將 review_state CLI 更新前後的 transaction JSON、備份、preflight、CLI writer、post-write smoke 與 rollback plan 組成可稽核操作順序;API/UI 不讀 token、不執行 CLI、不連 DB、不更新 review_state、不 commit、不掛 scheduler;版本同步至 V10.261。 - - Phase 73 candidate queue review decision writer run package:新增 `services/market_intel/candidate_queue_review_decision_writer_run_package.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_run_package` 與 UI package 按鈕,將 review_state transaction、preflight、operator drill、writer gate、post-write smoke、必要 artifact 與 rollback plan 組成正式 CLI 更新前的可稽核 run package;API/UI 不寫檔、不讀 token、不執行 CLI、不連 DB、不更新 review_state、不 commit、不掛 scheduler;版本同步至 V10.262。 - - Phase 74 candidate queue review decision writer run readiness:新增 `services/market_intel/candidate_queue_review_decision_writer_run_readiness.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_run_readiness` 與 UI readiness 按鈕,檢查 review_state CLI 更新前的 transaction JSON、備份、preflight、shell-only token 與 post-write smoke 計畫是否齊備;API/UI 不寫檔、不讀 token、不執行 CLI、不連 DB、不更新 review_state、不 commit、不掛 scheduler;版本同步至 V10.264。 - - Phase 75 candidate queue review decision writer run receipt:新增 `services/market_intel/candidate_queue_review_decision_writer_run_receipt.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_run_receipt` 與 UI receipt 按鈕,審核 review_state CLI 更新後的 writer output、post-write smoke、dedupe key 一致性、artifact 路徑與 token 外洩風險;API/UI 不回吐 receipt 原文、不讀 token、不執行 CLI、不連 DB、不更新 review_state、不 commit、不掛 scheduler;版本同步至 V10.266。 - - Phase 76 candidate queue review decision writer run closeout:新增 `services/market_intel/candidate_queue_review_decision_writer_run_closeout.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_run_closeout` 與 UI closeout 按鈕,在 review_state receipt 通過後整理 closeout gate、操作員 closeout artifact、post-closeout read-only inventory 確認與 promotion 摘要;API/UI 不回吐 receipt 原文、不讀 token、不執行 CLI、不連 DB、不更新 review_state、不 commit、不掛 scheduler;版本同步至 V10.268。 - - Phase 77 candidate queue review decision post-closeout inventory:新增 `services/market_intel/candidate_queue_review_decision_post_closeout_inventory.py`、`routes/market_intel_review_post_routes.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_decision_post_closeout_inventory` 與 UI inventory 按鈕,在 review_state closeout 後整理 post-write smoke、live inventory、dedupe key 與 review_state 結果;API/UI 不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler;版本同步至 V10.270。 - - Phase 78 candidate queue review completion archive:新增 `services/market_intel/candidate_queue_review_completion_archive.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_completion_archive` 與 UI archive 按鈕,在 post-closeout inventory 通過後整理 receipt、closeout、inventory、dedupe key、review_state row snapshot 與 artifact path manifest;API/UI 不寫檔、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler;版本同步至 V10.273。 - - Phase 79 candidate queue review archive summary:新增 `services/market_intel/candidate_queue_review_archive_summary.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_archive_summary` 與 UI summary 按鈕,在 review completion archive 後整理可供摘要/報表審核的結構化輸入;API/UI 不呼叫 LLM、不派送 Telegram、不寫檔、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler;版本同步至 V10.276。 - - Phase 80 candidate queue review AI summary preflight:新增 `services/market_intel/candidate_queue_review_ai_summary_preflight.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_preflight` 與 UI preflight 按鈕,在 archive summary 後檢查 Ollama-first 三主機級聯與 Gemini 備援邊界;API/UI 不呼叫 LLM、不派送 Telegram、不寫檔、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler;版本同步至 V10.278。 - - Phase 81 candidate queue review AI summary run package:新增 `services/market_intel/candidate_queue_review_ai_summary_run_package.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_run_package` 與 UI package 按鈕,在 AI summary preflight 後整理手動 Ollama 摘要任務包、prompt contract 與輸出 schema;API/UI 不呼叫 LLM、不派送 Telegram、不寫 run package、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler;版本同步至 V10.280。 - - Phase 82 candidate queue review AI summary output receipt:新增 `services/market_intel/candidate_queue_review_ai_summary_output_receipt.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_output_receipt` 與 UI receipt 按鈕,在 run package 後驗收人工 Ollama 摘要輸出的 schema、evidence_refs 與 model_route;API/UI 不呼叫 LLM、不派送 Telegram、不寫 receipt、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler;版本同步至 V10.285。 - - Phase 83 candidate queue review AI summary persistence preflight:新增 `services/market_intel/candidate_queue_review_ai_summary_persistence_preflight.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_preflight` 與 UI preflight 按鈕,在 output receipt 後整理 future CLI-only `market_alert_review_queue.metadata_json.ai_summary_review` persistence contract、payload hash 與 metadata patch preview;API/UI 不呼叫 LLM、不派送 Telegram、不寫 preflight、不寫 summary record、不寫 metadata_json、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler;版本同步至 V10.286。 - - Phase 84 candidate queue review AI summary persistence transaction:新增 `services/market_intel/candidate_queue_review_ai_summary_persistence_transaction.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_transaction` 與 UI transaction 按鈕,在 persistence preflight 後產生 future CLI-only `metadata_json` UPDATE statement preview、parameter preview 與 rollback plan;API/UI 不開 DB、不執行 SQL、不寫 transaction、不寫 summary record、不寫 metadata_json、不讀 token、不執行 CLI、不更新 review_state、不派送 Telegram、不呼叫 LLM、不 commit、不掛 scheduler;版本同步至 V10.287。 - - Phase 85 candidate queue review AI summary persistence writer preflight:新增 `services/market_intel/candidate_queue_review_ai_summary_persistence_writer_preflight.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_writer_preflight` 與 UI writer preflight 按鈕,在 transaction preview 後檢查 CLI-only writer contract、metadata_json backup requirement、post-write smoke requirement 與 artifact path gate;API/UI 不開 DB、不執行 SQL、不寫 preflight、不寫 summary record、不寫 metadata_json、不讀 token、不執行 CLI、不更新 review_state、不派送 Telegram、不呼叫 LLM、不 commit、不掛 scheduler;版本同步至 V10.290。 - - Phase 86 candidate queue review AI summary persistence run package:新增 `services/market_intel/candidate_queue_review_ai_summary_persistence_run_package.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_run_package` 與 UI run package 按鈕,在 writer preflight 後整理 payload manifest、CLI command bundle、required artifacts、operator signoff 與 rollback plan;API/UI 不開 DB、不執行 SQL、不寫 run package、不寫 summary record、不寫 metadata_json、不讀 token、不執行 CLI、不更新 review_state、不派送 Telegram、不呼叫 LLM、不 commit、不掛 scheduler;版本同步至 V10.294。 - - Phase 87 candidate queue review AI summary persistence run readiness:新增 `services/market_intel/candidate_queue_review_ai_summary_persistence_run_readiness.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_run_readiness` 與 UI readiness 按鈕,在 run package 後檢查 artifact path、metadata_json backup、read-only preflight、shell-only token 與 post-write smoke 計畫;API/UI 不開 DB、不執行 SQL、不寫 readiness artifact、不寫 metadata_json、不讀 token、不執行 CLI、不更新 review_state、不派送 Telegram、不呼叫 LLM、不 commit、不掛 scheduler;版本同步至 V10.297。 + - Phase 71 candidate queue review decision writer post-write smoke:新增 `services/market_intel/candidate_queue_review_decision_writer_postwrite_smoke.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_review_decision_writer_postwrite_smoke` 與 UI smoke 按鈕,人工 CLI 更新 review_state 後可用 dedupe key 只讀驗證 row 是否存在且 state 符合預期;API/UI 預設不連 DB,execute=true 也只讀查詢,不更新 review_state、不 commit、不讀 token、不掛 scheduler;版本同步至 V10.260。 + - Phase 72 candidate queue review decision writer operator drill:新增 `services/market_intel/candidate_queue_review_decision_writer_operator_drill.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_review_decision_writer_operator_drill` 與 UI drill 按鈕,將 review_state CLI 更新前後的 transaction JSON、備份、preflight、CLI writer、post-write smoke 與 rollback plan 組成可稽核操作順序;API/UI 不讀 token、不執行 CLI、不連 DB、不更新 review_state、不 commit、不掛 scheduler;版本同步至 V10.261。 + - Phase 73 candidate queue review decision writer run package:新增 `services/market_intel/candidate_queue_review_decision_writer_run_package.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_review_decision_writer_run_package` 與 UI package 按鈕,將 review_state transaction、preflight、operator drill、writer gate、post-write smoke、必要 artifact 與 rollback plan 組成正式 CLI 更新前的可稽核 run package;API/UI 不寫檔、不讀 token、不執行 CLI、不連 DB、不更新 review_state、不 commit、不掛 scheduler;版本同步至 V10.262。 + - Phase 74 candidate queue review decision writer run readiness:新增 `services/market_intel/candidate_queue_review_decision_writer_run_readiness.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_review_decision_writer_run_readiness` 與 UI readiness 按鈕,檢查 review_state CLI 更新前的 transaction JSON、備份、preflight、shell-only token 與 post-write smoke 計畫是否齊備;API/UI 不寫檔、不讀 token、不執行 CLI、不連 DB、不更新 review_state、不 commit、不掛 scheduler;版本同步至 V10.264。 + - Phase 75 candidate queue review decision writer run receipt:新增 `services/market_intel/candidate_queue_review_decision_writer_run_receipt.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_review_decision_writer_run_receipt` 與 UI receipt 按鈕,審核 review_state CLI 更新後的 writer output、post-write smoke、dedupe key 一致性、artifact 路徑與 token 外洩風險;API/UI 不回吐 receipt 原文、不讀 token、不執行 CLI、不連 DB、不更新 review_state、不 commit、不掛 scheduler;版本同步至 V10.266。 + - Phase 76 candidate queue review decision writer run closeout:新增 `services/market_intel/candidate_queue_review_decision_writer_run_closeout.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_review_decision_writer_run_closeout` 與 UI closeout 按鈕,在 review_state receipt 通過後整理 closeout gate、操作員 closeout artifact、post-closeout read-only inventory 確認與 promotion 摘要;API/UI 不回吐 receipt 原文、不讀 token、不執行 CLI、不連 DB、不更新 review_state、不 commit、不掛 scheduler;版本同步至 V10.268。 + - Phase 77 candidate queue review decision post-closeout inventory:新增 `services/market_intel/candidate_queue_review_decision_post_closeout_inventory.py`、`routes/market_intel_review_post_routes.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_review_decision_post_closeout_inventory` 與 UI inventory 按鈕,在 review_state closeout 後整理 post-write smoke、live inventory、dedupe key 與 review_state 結果;API/UI 不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler;版本同步至 V10.270。 + - Phase 78 candidate queue review completion archive:新增 `services/market_intel/candidate_queue_review_completion_archive.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_review_completion_archive` 與 UI archive 按鈕,在 post-closeout inventory 通過後整理 receipt、closeout、inventory、dedupe key、review_state row snapshot 與 artifact path manifest;API/UI 不寫檔、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler;版本同步至 V10.273。 + - Phase 79 candidate queue review archive summary:新增 `services/market_intel/candidate_queue_review_archive_summary.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_review_archive_summary` 與 UI summary 按鈕,在 review completion archive 後整理可供摘要/報表審核的結構化輸入;API/UI 不呼叫 LLM、不派送 Telegram、不寫檔、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler;版本同步至 V10.276。 + - Phase 80 candidate queue review AI summary preflight:新增 `services/market_intel/candidate_queue_review_ai_summary_preflight.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_review_ai_summary_preflight` 與 UI preflight 按鈕,在 archive summary 後檢查 Ollama-first 三主機級聯與 Gemini 備援邊界;API/UI 不呼叫 LLM、不派送 Telegram、不寫檔、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler;版本同步至 V10.278。 + - Phase 81 candidate queue review AI summary run package:新增 `services/market_intel/candidate_queue_review_ai_summary_run_package.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_review_ai_summary_run_package` 與 UI package 按鈕,在 AI summary preflight 後整理AI-controlled Ollama 摘要任務包、prompt contract 與輸出 schema;API/UI 不呼叫 LLM、不派送 Telegram、不寫 run package、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler;版本同步至 V10.280。 + - Phase 82 candidate queue review AI summary output receipt:新增 `services/market_intel/candidate_queue_review_ai_summary_output_receipt.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_review_ai_summary_output_receipt` 與 UI receipt 按鈕,在 run package 後驗收 AI-controlled Ollama 摘要輸出的 schema、evidence_refs 與 model_route;API/UI 不呼叫 LLM、不派送 Telegram、不寫 receipt、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler;版本同步至 V10.285。 + - Phase 83 candidate queue review AI summary persistence preflight:新增 `services/market_intel/candidate_queue_review_ai_summary_persistence_preflight.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_review_ai_summary_persistence_preflight` 與 UI preflight 按鈕,在 output receipt 後整理 future CLI-only `market_alert_review_queue.metadata_json.ai_summary_review` persistence contract、payload hash 與 metadata patch preview;API/UI 不呼叫 LLM、不派送 Telegram、不寫 preflight、不寫 summary record、不寫 metadata_json、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler;版本同步至 V10.286。 + - Phase 84 candidate queue review AI summary persistence transaction:新增 `services/market_intel/candidate_queue_review_ai_summary_persistence_transaction.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_review_ai_summary_persistence_transaction` 與 UI transaction 按鈕,在 persistence preflight 後產生 future CLI-only `metadata_json` UPDATE statement preview、parameter preview 與 rollback plan;API/UI 不開 DB、不執行 SQL、不寫 transaction、不寫 summary record、不寫 metadata_json、不讀 token、不執行 CLI、不更新 review_state、不派送 Telegram、不呼叫 LLM、不 commit、不掛 scheduler;版本同步至 V10.287。 + - Phase 85 candidate queue review AI summary persistence writer preflight:新增 `services/market_intel/candidate_queue_review_ai_summary_persistence_writer_preflight.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_review_ai_summary_persistence_writer_preflight` 與 UI writer preflight 按鈕,在 transaction preview 後檢查 CLI-only writer contract、metadata_json backup requirement、post-write smoke requirement 與 artifact path gate;API/UI 不開 DB、不執行 SQL、不寫 preflight、不寫 summary record、不寫 metadata_json、不讀 token、不執行 CLI、不更新 review_state、不派送 Telegram、不呼叫 LLM、不 commit、不掛 scheduler;版本同步至 V10.290。 + - Phase 86 candidate queue review AI summary persistence run package:新增 `services/market_intel/candidate_queue_review_ai_summary_persistence_run_package.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_review_ai_summary_persistence_run_package` 與 UI run package 按鈕,在 writer preflight 後整理 payload manifest、CLI command bundle、required artifacts、operator signoff 與 rollback plan;API/UI 不開 DB、不執行 SQL、不寫 run package、不寫 summary record、不寫 metadata_json、不讀 token、不執行 CLI、不更新 review_state、不派送 Telegram、不呼叫 LLM、不 commit、不掛 scheduler;版本同步至 V10.294。 + - Phase 87 candidate queue review AI summary persistence run readiness:新增 `services/market_intel/candidate_queue_review_ai_summary_persistence_run_readiness.py`、POST `/api/market_intel/ai_controlled/review/candidate_queue_review_ai_summary_persistence_run_readiness` 與 UI readiness 按鈕,在 run package 後檢查 artifact path、metadata_json backup、read-only preflight、shell-only token 與 post-write smoke 計畫;API/UI 不開 DB、不執行 SQL、不寫 readiness artifact、不寫 metadata_json、不讀 token、不執行 CLI、不更新 review_state、不派送 Telegram、不呼叫 LLM、不 commit、不掛 scheduler;版本同步至 V10.297。 - V10.248 補市場情報 390px preview panel QA:sample review 工具列改為 textarea + 可換行 action rail,移除舊的硬編 8 欄 grid;`check_responsive_overflow` 新增 `--screenshot-all`,本機 390x844 `/market_intel` 真頁面 QA 通過且 overflow=0。 - V10.250 補 Code Review Gemini 備援遙測護欄:Ollama 主路徑失敗時 `fallback_to` 明確指向 `code_review_openclaw_gemini`,測試鎖住「Gemini 不得記成 `code_review_openclaw` 主 caller」;AI Calls 觀測台會把 legacy `code_review_openclaw + gemini` 顯示成 Gemini 備援,避免誤判 Gemini-first。 - Schema smoke:`tests/test_market_intel_skeleton.py` 檢查 `Base.metadata` 內含 ADR-035 八張 `market_*` tables。 @@ -393,7 +2467,7 @@ - SOT:更新 `docs/AI_INTELLIGENCE_MODULE_SOT.md` 至 V10.11 AI Automation Metrics Scrape 架構。 - Codex 規則:更新 `AGENTS.md`、`CONSTITUTION.md`、ADR/memory 索引。 - Prometheus 指標化:新增 EventRouter / AutoHeal / safe action / replay in-process metrics,並接入 `/metrics`。 - - 線上 smoke dashboard:新增 `/ai_automation_smoke` 與 `/api/ai-automation/smoke`,覆蓋 EventRouter、AutoHeal、NemoTron fallback、OpenClaw embedding queue、ElephantAlpha HITL。 + - 線上 smoke dashboard:新增 `/ai_automation_smoke` 與 `/api/ai-automation/smoke`,覆蓋 EventRouter、AutoHeal、NemoTron fallback、OpenClaw embedding queue、ElephantAlpha AI 例外決策。 - Smoke 趨勢保存:`/api/ai-automation/smoke` 每次快檢追加 JSONL 精簡紀錄,dashboard 顯示最近趨勢。 - Smoke 趨勢管理:新增 JSONL 匯出、清理與每日摘要。 - Smoke 每日摘要:新增 Telegram 手動推播 API 與 momo-scheduler 每日 09:10 排程入口。 diff --git a/docs/AI_INTELLIGENCE_MODULE_SOT.md b/docs/AI_INTELLIGENCE_MODULE_SOT.md index 22092c6..ee125d3 100644 --- a/docs/AI_INTELLIGENCE_MODULE_SOT.md +++ b/docs/AI_INTELLIGENCE_MODULE_SOT.md @@ -13,7 +13,7 @@ - 188 直連 GCP-A / GCP-B timeout 時,resolver 可先使用同順位 110 proxy rescue:GCP-A direct → `192.168.0.110:11435` → GCP-B direct → `192.168.0.110:11436` → 111。proxy rescue 只是同一順位的可用入口,不代表 GCP direct host 已恢復。 - `OLLAMA_RESOLVE_HOST_HEALTH_SKIP_ENABLED=true` 時,resolver 會讀最近 `host_health_probes`;若 direct GCP-A/GCP-B 在視窗內已被判定不健康,會直接略過該 direct endpoint,先試同順位 proxy rescue,避免每 120 秒 cache refresh 都等待 direct timeout。此 skip 只套用 direct GCP,不套用 110 proxy。 - `config.OLLAMA_HOST`、`config.HERMES_URL`、`config.EMBEDDING_HOST` 只保留為舊 caller 相容常數;import-time 不得 probe network,也不得因 GCP-A/GCP-B 短暫不可用而 freeze 到 111。需要即時路由時一律呼叫 `get_ollama_host()`、`get_hermes_url()`、`get_embedding_host()` 或 `OllamaService`。 -- Gemini 只能作為 Ollama 主路徑失敗後的備援;MCP Grounding、PPT/vision、週/月報、Code Review、EA HITL、複雜 SKU 升級等舊鎖定場景也必須先走 GCP-A → GCP-B → 111。 +- Gemini 只能作為 Ollama 主路徑失敗後的備援;MCP Grounding、PPT/vision、週/月報、Code Review、EA AI 例外決策、複雜 SKU 升級等舊鎖定場景也必須先走 GCP-A → GCP-B → 111。 - 188 `192.168.0.188` 僅是 App / DB / scheduler / Telegram bot 容器宿主與 AutoHeal target,不可作為 Ollama 節點。 - 通用 AI 文案、關鍵字、商品洞察與 Telegram Q&A 第一響應不得 Gemini-first。 - Hermes intent / analyst 路徑不得手刻 `/api/generate` 或只 resolve 單次 host;必須走 `OllamaService`。預設 `HERMES_ALLOW_111_FALLBACK=false`,同一請求只跑 GCP-A → GCP-B;兩台都失敗時回規則引擎或 DB 證據 fallback,不把批量價格分析轉嫁到 111。救急時才可顯式設 true 允許 111 接手。 @@ -46,7 +46,7 @@ - Scheduler 每 15 分鐘執行 `run_ollama_111_usage_guard_check()`,只讀 `ai_calls` 統計最近視窗的 GCP-A / GCP-B / 111 呼叫量;預設 60 分鐘內 Ollama 呼叫至少 20 次、111 至少 3 次且占比 >= 5% 才推 Telegram。這是觀測護欄,不改路由、不寫 DB、不自動重啟服務。 - `OllamaService` 對 111 final fallback 有 circuit breaker:預設最近 60 分鐘 Ollama 呼叫至少 20 次、111 至少 5 次且占比 >= 5% 時,短暫跳過 111(`OLLAMA_111_CIRCUIT_CACHE_SEC=60`),避免 111 在已偏高時繼續承接長任務;DB 觀測失敗時 fail-open,不讓主要 GCP-A/GCP-B 路由被觀測層中斷。 - 111 的 LAN 入口必須經 `scripts/ops/ollama111_allow_proxy.py` allowlist proxy:真實 Ollama 綁 `127.0.0.1:11434`,proxy 綁 `192.168.0.111:11434`,預設只允許 111 本機與 188 生產宿主;110 / 121 / 其他 LAN client 不能直接打 111,避免跨專案 CI 或 VM 繞過 momo-pro router 載入 7B+ runner。111 上以 `scripts/ops/install_ollama111_allow_proxy.sh` 安裝 user LaunchAgent,安裝器會把 proxy script 複製到 `~/.local/share/momo-pro-system/ollama111_allow_proxy.py`,讓 LaunchAgent 不依賴 iCloud repo 掛載路徑,並讓 proxy 與 `OLLAMA_HOST=127.0.0.1:11434` 在登入/重啟後自動恢復。拒絕日誌以 `OLLAMA111_PROXY_REJECT_LOG_DEDUP_SEC=60` 去重,避免 121 這類旁路探測刷爆 111 磁碟日誌。 -- ElephantAlpha 的 `price_drop_alert` / `market_opportunity` Telegram HITL 告警必須把同款證據獨立呈現,至少包含 `match_type`、`price_basis`、`alert_tier` 與 `match_score`;沒有高信心同款與總價可比證據時,不得把 PChome/MOMO 價差寫成可直接跟價建議。 +- ElephantAlpha 的 `price_drop_alert` / `market_opportunity` Telegram AI 例外決策 告警必須把同款證據獨立呈現,至少包含 `match_type`、`price_basis`、`alert_tier` 與 `match_score`;沒有高信心同款與總價可比證據時,不得把 PChome/MOMO 價差寫成可直接跟價建議。 ## 零之零、產品定位正名(2026-06-15) @@ -60,7 +60,7 @@ - 商品清單、AI 挑品、比價覆核與待確認候選必須採商品身份優先 UI:縮圖、商品 ID、平台、賣場連結、價格/促銷狀態、可信度與下一步動作需在同一商品區塊內可一眼掃描;AI 建議不得只顯示長段理由,必須同時提供可開啟的賣場入口。 - 蝦皮與酷澎等未接入來源暫停接入,不進作戰清單、不發告警;後續只可透過 official API / provider API / manual CSV 進 `external_offers` 類正規化層,並清楚標示資料品質。 - V10.607 新增 `external_market_sources` / `external_offers` 正規化層與 `/api/ai/pchome-growth/source-contract` 只讀 API。MOMO 先以既有比價快取橋接進來源狀態;蝦皮與酷澎只保留 official API、provider API、manual CSV contract,預設暫停且不進告警。 -- V10.608 新增 `/api/ai/pchome-growth/external-offers/csv-dry-run` 與 AI 情報頁「外部報價預檢」。CSV 預檢只讀、不寫 DB;逐列回報「可使用」「需人工確認」「不能使用」,並支援中文表頭,避免格式小錯造成整批匯入失敗。 +- V10.608 新增 `/api/ai/pchome-growth/external-offers/csv-dry-run` 與 AI 情報頁「外部報價預檢」。CSV 預檢只讀、不寫 DB;逐列回報「可使用」「需 AI 自動驗證確認」「不能使用」,並支援中文表頭,避免格式小錯造成整批匯入失敗。 - V10.609 明確把外部報價主路徑改為自動化:`run_external_offer_sync_task` 每 4 小時將已確認同款的既有比價快取同步進 `external_offers`。CSV 只保留為 API / crawler / provider 失敗時的備援預檢入口,不是日常營運主流程。 - V10.610 起 `/api/ai/pchome-growth/opportunities` 優先讀取 `external_offers` 的自動同步資料;只有新資料層缺資料時才 fallback 舊 `competitor_prices`。API stats 會回傳資料來源計數,方便確認作戰清單是否已走新資料層。 - V10.611 起 `/ai_intelligence` 是營運使用者主入口;V10.617 已將舊「今日作戰入口 / 外部報價預檢」改為「今日重點總覽 / 備援資料檢查」,主流程不得再把人工 CSV 放在前段。 @@ -72,8 +72,8 @@ - V10.617 起 `/ai_intelligence` 必須採「先給下一步」的作戰導向 UI:首屏需先回答「今天先做什麼」,再呈現商品處理進度、外部價格來源與操作捷徑;今日處理清單需用表格呈現優先級、建議動作、商品、近 7 天業績、比價結果、資料可信度與下一步;MOMO 外部價格參考需顯示價格風險分佈,且表格需以 PChome 價格優先,明確顯示「PChome 貴 / PChome 便宜」與可信度,不得只用大段文字說明使用方式。 - V10.618 起 `/price_comparison` 也必須採「先給下一步」的比價決策 UI:首屏需顯示目前卡在哪一步、PChome / MOMO 資料準備狀態與下一個按鈕;比價結果需先呈現「需檢查價格 / 可主推曝光 / 價格接近」分佈,再用表格列出每筆商品的下一步,不得只呈現 Step 流程或原始價差表。 - V10.619 起 MOMO 比價候選來源新增「PChome 商品導向搜尋」:當比價 API 已有 PChome 商品但缺 MOMO 清單時,必須用每筆 PChome 商品名稱產生精準搜尋詞反查 MOMO,保留品牌、品名、容量與組合線索;新版 MOMO 搜尋頁需解析 Next.js `goodsInfoList` payload。此路徑只擴大候選池,不放寬同款 matcher 門檻。 -- V10.620 起 `unit_comparable` 不再一律丟人工確認:若 `build_unit_price_comparison()` 可產生明確容量/數量、MOMO 單位價、PChome 單位價與差距百分比,候選需標為「自動單位價比較」並回傳 `auto_compare_type=unit_price`。此類候選可自動呈現價格壓力,但不得混入舊總價同款比價表,也不得直接寫入正式價差或自動改價;無法產生單位證據時才維持「需人工確認」。 -- V10.621 起 `/price_comparison` 的「自動找 MOMO 候選」會把可直接總價比價與自動單位價候選同步到 `external_offers`,`ingestion_method='targeted_momo_search'`,人工確認候選不得寫入。`external_offers.raw_payload_json.price_basis='unit_price'` 時,作戰清單必須使用 `unit_price_comparison` 的 MOMO / PChome 單位價與 `unit_gap_pct` 判斷價格壓力;不得把 MOMO 組合總價與 PChome 單品總價直接相減。此同步只影響外部價格參考與作戰清單,不寫 `competitor_prices`,也不自動改價。 +- V10.620 起 `unit_comparable` 不再一律丟 AI 自動驗證確認:若 `build_unit_price_comparison()` 可產生明確容量/數量、MOMO 單位價、PChome 單位價與差距百分比,候選需標為「自動單位價比較」並回傳 `auto_compare_type=unit_price`。此類候選可自動呈現價格壓力,但不得混入舊總價同款比價表,也不得直接寫入正式價差或自動改價;無法產生單位證據時才維持「需 AI 自動驗證確認」。 +- V10.621 起 `/price_comparison` 的「自動找 MOMO 候選」會把可直接總價比價與自動單位價候選同步到 `external_offers`,`ingestion_method='targeted_momo_search'`,AI 自動驗證確認候選不得寫入。`external_offers.raw_payload_json.price_basis='unit_price'` 時,作戰清單必須使用 `unit_price_comparison` 的 MOMO / PChome 單位價與 `unit_gap_pct` 判斷價格壓力;不得把 MOMO 組合總價與 PChome 單品總價直接相減。此同步只影響外部價格參考與作戰清單,不寫 `competitor_prices`,也不自動改價。 - V10.622 起任何 `external_offers` 自動同步成功寫入後,必須呼叫 `mark_pchome_growth_cache_stale()` 寫入共享 cache epoch;`/api/ai/pchome-growth/opportunities` 讀快取前必須比對 `get_pchome_growth_cache_epoch()`。這是跨 Gunicorn worker 的可見性保護,避免自動候選已進外部價格參考,但 AI 情報頁仍回 120 秒舊作戰清單。 - V10.623 起 `/price_comparison` 與 `/ai_intelligence` 不得只靠大段文字說明流程:比價頁第一屏必須有主 KPI、目前卡點、四步流程與結果決策摘要;作戰頁第一屏必須有今日任務、可立即處理、待補比價與最新業績日。所有狀態都要由實際 API/前端狀態驅動,讓使用者一眼知道下一步要按哪個動作。 - V10.638 起 PChome 導向 MOMO 補抓會把「找到但不能自動比價」的候選以 `match_status='needs_review'`、`data_quality_status='needs_review'` 保存到 `external_offers`;這些候選不得進價格壓力判斷,也不得發告警,但 `/api/ai/pchome-growth/opportunities` 可回傳待確認候選數,讓 UI 顯示「已有候選待確認」而不是只顯示無法比價。 @@ -82,6 +82,7 @@ - V10.641 起 `/ai_intelligence` 的摘要數字不可只是靜態文字;第一屏 KPI、商品處理進度、待確認數字都必須可點擊並導向對應明細。今日清單若已有 MOMO 待確認候選,下一步必須顯示「確認候選」並跳到候選面板,不得再只顯示「補齊比價」。 - V10.642 起 `/ai_intelligence` 的摘要卡與商品處理數字不可只跳到大區塊;點擊後必須開啟商品明細面板,列出商品名稱、分類、近 7 天業績、業績變化、MOMO 比價狀態與下一步按鈕。明細需至少支援全部、價格壓力、價格優勢、待確認、缺比價與有外部價切換;外部價格風險分佈也必須能一鍵篩選下方表格。 - V10.643 起 `/ai_intelligence` 的商品明細上方必須提供「商品策略分流」視覺摘要,至少包含價格壓力、價格優勢、待確認、缺比價四類;每一類需顯示件數、近 7 天業績與比例條,且可點擊切換明細。舊 KPI 卡也不得是靜態數字,需可導向全部商品、可處理商品、高風險比價或處理紀錄。 +- V10.725 source-ready 起 PChome growth 必須提供 `/api/ai/pchome-growth/ai-automation-readiness` 與 Dashboard「AI 主流程」狀態列;同一摘要要聚合缺口偵測、同款搜尋包、候選決策包、證據收據與受控落地,並明確輸出 `primary_human_gate_count=0`、`automation_policy.primary_flow=ai_controlled`。PChome mapping 不得把 AI 例外決策當主流程;所有例外都要進 AI machine-verifiable auto-resolution,產生 failure reasons、下一個機器動作與 rollback/readback 路徑。 - V10.644 起 `/ai_intelligence` 的商品明細列不得只用句子描述比價;每列必須顯示 PChome 價格、MOMO 參考價、差距、可信度四格價格證據,並保留下一步按鈕。單位價候選需顯示單位價與單位,候選待確認或缺資料則以「待補 / 候選待確認」呈現,不得捏造價格。 - V10.645 起 `/ai_intelligence` 的商品明細分流切換後,必須顯示「這類商品怎麼處理」的行動摘要,包含件數、近 7 天業績、平均可信度、最大價差、代表商品與主按鈕;使用者不得只能看到商品列表而不知道下一步。 - V10.646 起 `/ai_intelligence` 的商品明細必須提供搜尋與排序;搜尋至少涵蓋商品、分類、商品編號與 MOMO 候選資訊,排序至少支援優先級、近 7 天業績、價差、下滑幅度與可信度。搜尋/排序後的行動摘要與明細列表必須使用同一批結果。 @@ -99,19 +100,19 @@ ## 零之一、12 Agent 決策信封(2026-05-24) -- 12 角色分工不作為 12 個常駐模型;在產品層統一收斂成 `decision_envelope`,由 Hermes / NemoTron / OpenClaw / ElephantAlpha 與人工審核、PPT QA、競品 review queue 共用。 +- 12 角色分工不作為 12 個常駐模型;在產品層統一收斂成 `decision_envelope`,由 Hermes / NemoTron / OpenClaw / ElephantAlpha 與 AI 例外決策、PPT QA、競品 review queue 共用。 - `decision_envelope` 必須至少能表達:`decision_type`、`severity`、`evidence[]`、`recommended_action`、`expected_impact`、`confidence`、`guardrails`、`trace`。 -- `guardrails.can_auto_execute=false` 是預設;價格調整、正式比價覆寫、PPT 發送與修復執行都必須遵守 HITL 或既有 service guard,不得因 Agent 信心高就繞過 matcher / feeder / review service。 +- `guardrails.can_auto_execute=false` 是預設;價格調整、正式比價覆寫、PPT 發送與修復執行都必須遵守 AI 例外決策或既有 service guard,不得因 Agent 信心高就繞過 matcher / feeder / review service。 - 證據不足時不得輸出空泛效益預測;必須標記 `data_quality=missing|partial|stale`,並把建議行動降級成 `human_review`、`needs_research` 或 `silence_alert`。 - Telegram `triaged_alert()` 已支援渲染 `decision_envelope`,讓告警固定呈現嚴重度、證據、建議行動、預期影響、信心度與追蹤 ID;後續觀測台與 PPT 也應共用同一份欄位語意。 -- NemoTron `price_alert` / `human_review` 派發會把同款證據、價差、七日銷量變化、營收流失、HITL 邊界與資料品質寫入同一份 `decision_envelope`,並同步放入 EventRouter event 與 KM metadata;12 Agent 後續只能沿用此信封補充分析,不得繞過 matcher / feeder / review service 直接改價或覆寫比價資料。 -- EventRouter / Telegram 的 HITL callback 必須優先使用 `decision_envelope.decision_id` 作為事件追蹤 ID;若上游未帶 `event.id`,`triaged_alert()` 仍會用 `decision_id` 產生 `momo:eig:*` callback,避免價格決策審核落成 `unknown`。所有 `momo:eig:*` callback 必須以 UTF-8 byte-safe 截斷,確保 `callback_data` 不超過 Telegram 64-byte 限制。 +- NemoTron `price_alert` / `human_review` 派發會把同款證據、價差、七日銷量變化、營收流失、AI 例外決策 邊界與資料品質寫入同一份 `decision_envelope`,並同步放入 EventRouter event 與 KM metadata;12 Agent 後續只能沿用此信封補充分析,不得繞過 matcher / feeder / review service 直接改價或覆寫比價資料。 +- EventRouter / Telegram 的 AI 例外決策 callback 必須優先使用 `decision_envelope.decision_id` 作為事件追蹤 ID;若上游未帶 `event.id`,`triaged_alert()` 仍會用 `decision_id` 產生 `momo:eig:*` callback,避免價格決策審核落成 `unknown`。所有 `momo:eig:*` callback 必須以 UTF-8 byte-safe 截斷,確保 `callback_data` 不超過 Telegram 64-byte 限制。 - 競品比價相關的 Agent 建議只能讀 `competitor_match_attempts` / review queue / `competitor_prices` 的既有證據;不得直接寫 `competitor_prices` 或覆蓋 `_should_upsert_competitor_price()` 的保護規則。 -- 已帶 `decision_envelope` 的價格/覆核事件必須由 EventRouter 直接渲染證據模板,不再進 L1/L2 AI 重新摘要;Telegram 決策信封需顯示標的 SKU、商品名稱、PChome 候選、evidence、guardrails 與 HITL 動作,避免已有實證的比價告警被二次生成文字稀釋或造成額外模型成本。 -- PChome 覆核隊列本身也必須輸出 `decision_envelope`:`fetch_competitor_review_queue()`、`fetch_competitor_review_queue_page()` 與 `/api/pchome-review/queue` 的每筆候選需帶相同的 `subject`、`evidence`、`recommended_action`、`expected_impact` 與 `guardrails`,供 Dashboard、Agent、Telegram 與 PPT 共用;任何下游不得另寫一套比價狀態翻譯或繞過 HITL guardrails。 -- Dashboard 覆核卡與 `/api/export/excel/pchome-review` 也必須顯示/匯出 `decision_envelope` 的等級、資料品質、建議代碼、HITL、trace 與 `can_auto_execute=false` 邊界;操作員離開系統畫面或下載 Excel 後,仍要看得到「不可自動寫正式價差」的 guardrails。 -- OpenClaw 週報/日報/月報與 competitor PPT 不得再各自重算或翻譯 PChome 覆核狀態;必須透過 `competitor_intel_repository.summarize_review_decision_envelopes()` 讀取同一份 `decision_envelope` 摘要,並在 prompt / data_summary / KPI slide 保留 HITL 與 `can_auto_execute=false` 邊界。 -- Webcrumbs / Shared UI host data 也必須透過 `summarize_review_decision_envelopes()` 輸出 `reviewDecisionBrief`,並在 metadata 保留 review queue、HITL、auto-execute-blocked、`decision_support_rate`、`catalog_comparable_count` 與 catalog review lane counts;不得另寫一套 PChome 覆核摘要或在前端 runtime 重新推論價格行動。 +- 已帶 `decision_envelope` 的價格/覆核事件必須由 EventRouter 直接渲染證據模板,不再進 L1/L2 AI 重新摘要;Telegram 決策信封需顯示標的 SKU、商品名稱、PChome 候選、evidence、guardrails 與 AI 例外決策 動作,避免已有實證的比價告警被二次生成文字稀釋或造成額外模型成本。 +- PChome 覆核隊列本身也必須輸出 `decision_envelope`:`fetch_competitor_review_queue()`、`fetch_competitor_review_queue_page()` 與 `/api/pchome-review/queue` 的每筆候選需帶相同的 `subject`、`evidence`、`recommended_action`、`expected_impact` 與 `guardrails`,供 Dashboard、Agent、Telegram 與 PPT 共用;任何下游不得另寫一套比價狀態翻譯或繞過 AI 例外決策 guardrails。 +- Dashboard 覆核卡與 `/api/export/excel/pchome-review` 也必須顯示/匯出 `decision_envelope` 的等級、資料品質、建議代碼、AI 例外決策、trace 與 `can_auto_execute=false` 邊界;操作員離開系統畫面或下載 Excel 後,仍要看得到「不可自動寫正式價差」的 guardrails。 +- OpenClaw 週報/日報/月報與 competitor PPT 不得再各自重算或翻譯 PChome 覆核狀態;必須透過 `competitor_intel_repository.summarize_review_decision_envelopes()` 讀取同一份 `decision_envelope` 摘要,並在 prompt / data_summary / KPI slide 保留 AI 例外決策與 `can_auto_execute=false` 邊界。 +- Webcrumbs / Shared UI host data 也必須透過 `summarize_review_decision_envelopes()` 輸出 `reviewDecisionBrief`,並在 metadata 保留 review queue、AI 例外決策、auto-execute-blocked、`decision_support_rate`、`catalog_comparable_count` 與 catalog review lane counts;不得另寫一套 PChome 覆核摘要或在前端 runtime 重新推論價格行動。 - ElephantAlpha 的 `resource_optimization` 與低信心 `ea_escalation` 也必須輸出 `decision_envelope`:資源壓力信封只能使用 `action_plans`、CPU 實測、hygiene 結果與 insight/action trace,不得加入 LLM 預測效益;`triaged_alert()` 對 `ea_escalation` 亦需渲染信封並以 `decision_id` 作為 callback 追蹤 ID。 ## 一、四 AI Agent 路由架構 @@ -129,10 +130,10 @@ SQL漏斗(~300筆) 任務: Tool Calling → Telegram 告警 / DB 寫入 ↓ [OpenClaw] — 策略師 (Ollama-first;Gemini 僅備援 / 鎖定場景) - 任務: 週策略報告、洞察報告、L3 HITL 建議 + 任務: 週策略報告、洞察報告、L3 AI 例外決策 建議 ↓ [ElephantAlpha] — 編排者 (L3 Orchestrator) - 任務: 跨 Agent orchestration、HITL、AutoHeal bridge、受控 log scan + 任務: 跨 Agent orchestration、AI 例外決策、AutoHeal bridge、受控 log scan ``` ### 1.1 PChome 挑品 Agent(2026-05-01) @@ -150,11 +151,11 @@ SQL漏斗(~300筆) - 補抓狀態入口:`GET /api/ai/pchome-match/backfill/status` 除背景任務狀態外,必須回傳 read-only coverage snapshot:`active_with_price` / `valid_matches` / `match_rate` / `fresh_matches` / `fresh_match_rate` / `decision_ready_matches` / `decision_ready_rate` / `stale_matches` / `pending` / `actionable_review_count`,供 Dashboard 顯示目前該刷新過期價格或補抓未搜尋商品;此端點不寫 DB、不呼叫 LLM、不抓外站。`match_rate` 是身份覆蓋率,`fresh_match_rate` 是已配對 identity 內的新鮮比例,`decision_ready_rate` 才是可直接進入決策、圖表與簡報的 ACTIVE 商品比價覆蓋率。 - 排程閉環:`run_pchome_match_backfill_task` 每日 10:30 執行,補抓 PChome 待比對商品、寫入歷史價格,再重算 `strategy='product_pick'` 清單。 - PChome / MOMO 競價摘要出口 `services/competitor_intel_repository.py` 使用 30 分鐘共享快取(`COMPETITOR_INTEL_CACHE_TTL_SECONDS` 可調),避免 `/growth_analysis`、`/daily_sales`、PPT/AI 報表每次請求重跑昂貴覆蓋率與價差趨勢查詢;`run_competitor_price_feeder_task` 與 PChome backfill 完成後會主動清除快取。快取只包摘要輸出,不改 matcher 的高信心門檻與 identity_v2 準確性規則。 -- 商品看板第一屏:`/` 的 V2 看板直接以 `products`、`price_records`、`competitor_prices`、`competitor_match_attempts`、`competitor_match_reviews`、`ai_price_recommendations` 顯示比對覆蓋率、PChome 優勢、MOMO 威脅、AI 挑品、待比對優先清單與 PChome 覆核隊列;`filter=ai_picks` 可查看 50 品 AI 挑品列表,`filter=pchome_review` 可直接查看需人工處理的比價覆核 SKU,並以 DB 分頁支援 search/category/status 後的完整隊列,不得只截前 50 筆。覆核狀態篩選必須至少包含全部、需單位價、已排除、低信心、價格過期、找不到同款與人工閉環,讓人工可依 matcher 診斷類型分批處理。列內顯示候選 PChome 商品、候選價、match score、單位價換算摘要、人工動作與 matcher 診斷原因標籤(品牌不符、商品線不符、容量差異、組合差異、需單位價、價差極端等),不得只顯示籠統「待比對」。`/api/export/excel/pchome-review` 必須匯出同一套覆核隊列、人工處置、候選 PChome、單位價比較與原始診斷,讓人工覆核、簡報與後續 AI 分析共用同一份證據。`/api/pchome-review//decision` 是人工閉環入口:`accept_identity` 才可把候選寫入 `competitor_prices` 與 `competitor_price_history` 並打上 `manual_review/manual_accept/identity_v2`;`reject_identity`、`unit_price_required` 與 `needs_research` 只寫 `competitor_match_reviews` 並追加 manual attempt,不得把不同販售組合或否決候選灌入正式價差。PChome feeder 後續搜尋同一候選時必須讀取 `competitor_match_reviews`:已否決候選寫 `manual_rejected` 並跳過正式寫入,且必須繼續評估下一個候選,不能讓已否決候選長期阻塞同 SKU;已標記單位價候選寫 `manual_unit_price_required`;已要求補搜尋候選寫 `manual_needs_research` 並停留在覆核隊列;已採用候選可保守補到最低門檻並保留 `manual_review/manual_accept` 標籤。搜尋候選池只有強同款分數達 `0.90` 才可提前停止,避免 0.76 灰區候選卡掉後續更精準搜尋詞。人工 `reject_identity`、`unit_price_required`、`needs_research` 若命中當前正式候選,必須將同候選 `competitor_prices` 過期,不得繼續顯示正式總價差。商品列表必須將 `manual_rejected`、`manual_unit_price_required`、`manual_needs_research` 顯示為明確人工閉環狀態,不可回落成籠統「待比對」。`fetch_competitor_coverage()` 必須輸出人工採用、人工否決、人工單位價與採用率,daily/growth/PPT 共用 payload 必須顯示人工閉環成效,避免只呈現待審數。商品看板深度快取同時寫入 `data/dashboard_full_cache.pkl`,供多個 Gunicorn worker 共用,避免部署後各 worker 重複重建 7,000+ 商品統計造成開頁變慢;所有資料異動與 AI 挑品重算都透過 `clear_dashboard_cache()` 同步清除記憶體與共享快取,手動重算 API 會立即預熱商品看板快取,避免第一位使用者承擔重建成本。 -- PChome re-score 回收線:`rescore_accepted_current` 只能表示最新版 matcher 判定「值得人工覆核身份」,不可直接寫入正式 `competitor_prices`;`no_match`、`price_basis=none`、`alert_tier=suppress`、`variant_selection_review` 不得進入此隊列。`fetch_competitor_coverage()` 必須輸出 `rescore_accepted_count`,Dashboard、daily/growth 與 OpenClaw 競品摘要都要把「重算待人工覆核」獨立呈現,避免和一般低信心/單位價覆核混在一起。 +- 商品看板第一屏:`/` 的 V2 看板直接以 `products`、`price_records`、`competitor_prices`、`competitor_match_attempts`、`competitor_match_reviews`、`ai_price_recommendations` 顯示比對覆蓋率、PChome 優勢、MOMO 威脅、AI 挑品、待比對優先清單與 PChome 覆核隊列;`filter=ai_picks` 可查看 50 品 AI 挑品列表,`filter=pchome_review` 可直接查看需 AI 例外處理的比價覆核 SKU,並以 DB 分頁支援 search/category/status 後的完整隊列,不得只截前 50 筆。覆核狀態篩選必須至少包含全部、需單位價、已排除、低信心、價格過期、找不到同款與 AI 例外閉環,讓 AI 可依 matcher 診斷類型分批處理。列內顯示候選 PChome 商品、候選價、match score、單位價換算摘要、AI 例外動作與 matcher 診斷原因標籤(品牌不符、商品線不符、容量差異、組合差異、需單位價、價差極端等),不得只顯示籠統「待比對」。`/api/export/excel/pchome-review` 必須匯出同一套覆核隊列、AI 例外處置、候選 PChome、單位價比較與原始診斷,讓 AI 例外決策、簡報與後續 AI 分析共用同一份證據。`/api/pchome-review//decision` 是AI 例外閉環入口:`accept_identity` 才可把候選寫入 `competitor_prices` 與 `competitor_price_history` 並打上 `manual_review/manual_accept/identity_v2`;`reject_identity`、`unit_price_required` 與 `needs_research` 只寫 `competitor_match_reviews` 並追加 manual attempt,不得把不同販售組合或否決候選灌入正式價差。PChome feeder 後續搜尋同一候選時必須讀取 `competitor_match_reviews`:已否決候選寫 `manual_rejected` 並跳過正式寫入,且必須繼續評估下一個候選,不能讓已否決候選長期阻塞同 SKU;已標記單位價候選寫 `manual_unit_price_required`;已要求補搜尋候選寫 `manual_needs_research` 並停留在覆核隊列;已採用候選可保守補到最低門檻並保留 `manual_review/manual_accept` 標籤。搜尋候選池只有強同款分數達 `0.90` 才可提前停止,避免 0.76 灰區候選卡掉後續更精準搜尋詞。AI 例外 `reject_identity`、`unit_price_required`、`needs_research` 若命中當前正式候選,必須將同候選 `competitor_prices` 過期,不得繼續顯示正式總價差。商品列表必須將 `manual_rejected`、`manual_unit_price_required`、`manual_needs_research` 顯示為明確AI 例外閉環狀態,不可回落成籠統「待比對」。`fetch_competitor_coverage()` 必須輸出AI 採用、AI 否決、AI 單位價與採用率,daily/growth/PPT 共用 payload 必須顯示AI 例外閉環成效,避免只呈現待審數。商品看板深度快取同時寫入 `data/dashboard_full_cache.pkl`,供多個 Gunicorn worker 共用,避免部署後各 worker 重複重建 7,000+ 商品統計造成開頁變慢;所有資料異動與 AI 挑品重算都透過 `clear_dashboard_cache()` 同步清除記憶體與共享快取,手動重算 API 會立即預熱商品看板快取,避免第一位使用者承擔重建成本。 +- PChome re-score 回收線:`rescore_accepted_current` 只能表示最新版 matcher 判定「值得AI 例外決策身份」,不可直接寫入正式 `competitor_prices`;`no_match`、`price_basis=none`、`alert_tier=suppress`、`variant_selection_review` 不得進入此隊列。`fetch_competitor_coverage()` 必須輸出 `rescore_accepted_count`,Dashboard、daily/growth 與 OpenClaw 競品摘要都要把「重算待 AI 例外決策」獨立呈現,避免和一般低信心/單位價覆核混在一起。 - PChome 低信心操作分流:Dashboard 與 read-only `/api/pchome-review/queue` 必須把近門檻可救、證據不足、低信心舊候選拆成 `recoverable_low_score`、`true_low_confidence`、`legacy_low_score` 三個可篩選桶;廣義 `low_score` 僅作 repository/export 相容查詢,不可在 UI 中冒充單一操作分流。 - PChome coverage 的 `attempt_status` / `rescore_accepted_count` / `actionable_review_count` 口徑必須與 review queue 對齊:統計「沒有新鮮有效 identity」的商品,而不是只統計「完全沒有 identity」的商品;已過期但可重算採用的 stale identity 仍應出現在待審數字中,避免 API 與 Dashboard 漏報。 -- `run_retryable_candidate_revalidation()` 的自動回刷主戰場仍限 `low_score` / `refresh_low_score` / `recoverable_low_score`;`true_low_confidence` 只有在已補 focused exact 規則的窄範圍品線、舊分數 >= 0.95、`comparison_mode='exact_identity'`、含 `strong_exact_spec_match` 且不含 commercial / variant / count / bundle / refill 等阻擋理由時,才可進入重評,不得全面打開人工審核池。`rescore_accepted_current` 只允許命中具名 focused exact 品線、舊分數 >= 0.76、且仍無 hard veto / 阻擋理由時進窄門回刷;最後仍由最新版 matcher 判定是否可寫正式價差,像不同指甲油型號 / 色號必須 hard veto。 +- `run_retryable_candidate_revalidation()` 的自動回刷主戰場仍限 `low_score` / `refresh_low_score` / `recoverable_low_score`;`true_low_confidence` 只有在已補 focused exact 規則的窄範圍品線、舊分數 >= 0.95、`comparison_mode='exact_identity'`、含 `strong_exact_spec_match` 且不含 commercial / variant / count / bundle / refill 等阻擋理由時,才可進入重評,不得全面打開AI 例外池。`rescore_accepted_current` 只允許命中具名 focused exact 品線、舊分數 >= 0.76、且仍無 hard veto / 阻擋理由時進窄門回刷;最後仍由最新版 matcher 判定是否可寫正式價差,像不同指甲油型號 / 色號必須 hard veto。 - 高分 `true_low_confidence` 的自動救回只能用具名 focused exact 線逐批擴充;同品牌、同品線、同規格/同組合的花美水 Relax、St.Clare 私密呼呼、BIOPEUTIC 果酸、台塑生醫嬰兒沐浴洗髮、Elizabeth Arden 八小時護唇膏與理膚寶水全面修復潤唇膏可走 total-price,色號、香味、款式、即期品與 catalog selection 仍維持 review / veto。 - `true_low_confidence` focused exact 線必須同步接入 `run_retryable_candidate_revalidation()` 的 SQL 窄門,讓舊候選可被批次回收;該窄門只允許具名品線豁免 `variant_selection_review`,其他 hard veto / 型別、款式、香味、件數、組合、refill、commercial condition 阻擋仍不得回刷。 - 任選 catalog focused exact 只允許雙方都明確是同品線任選賣場且規格一致的窄範圍案例,例如 FLORTTE 眼線液筆 0.5ml、露得清護手霜 56g 無香/有香、Kanebo ALLIE 持采亮化 UV 防曬水凝乳 60g;若有 `commercial_condition_gap`(即期品、短效、航空版等狀態差異),focused bypass 不得移除 `variant_selection_review`,不得自動寫正式價差。 @@ -162,18 +163,18 @@ SQL漏斗(~300筆) - 其他正式覆核池 focused exact 線只能針對「已在正式頁面反覆出現且有硬規格」的窄範圍族群,例如 The Ordinary 咖啡因 EGCG、Natures Care 綿羊油同入數、TOMOON 指甲剪同尺寸、HH 雙 200ml 組、SEBAMED 200ml x2、YES 9cm 剪刀;同尺寸、同入數、同組合或單側漏規格必須可由 matcher 明確判斷,不能只因同品牌同品線通過。 - `/api/ai/pchome-match/backfill/status` 必須把近門檻重評池與過期 identity 救援池以只讀 `revalidation_preview` / `stale_recovery_preview` 曝光給操作員;預覽只復用正式候選 SQL 並受 limit / 60 秒快取限制,不啟動 PChome 搜尋、不呼叫 LLM、不寫 `competitor_match_attempts` / `competitor_prices`。重評 preview 必須先從最新 `competitor_match_attempts` 縮小候選,再用 `JOIN LATERAL` 取單一最新 MOMO 價;救援 preview 必須從過期 `competitor_prices` 小集合出發並用 `JOIN LATERAL` 取最新 MOMO 價,兩者都不得掃全量 `price_records`;Dashboard 只能顯示「可救援」觀測值,不得在未開啟 `PCHOME_STALE_RECOVERY_ENABLED` 時提供 recover-stale 執行按鈕;其中 `review_gated_count` 僅代表窄門 `true_low_confidence` exact 候選,不得被解讀為全量人工池可自動回刷。 - PChome re-score audit 預設必須先取每個 SKU 的最新 `competitor_match_attempts` 狀態,再套用 status / reason 篩選;舊低信心歷史候選只能透過 `--include-historical-candidates` 明確進入考古掃描,避免已入隊、已否決或已修正 SKU 被舊紀錄重新推回報表。 -- production re-score `--apply-accepted` 僅可追加 `rescore_accepted_current` attempt 給人工覆核;執行後需清除 Dashboard / competitor intel cache,且必須抽查 `competitor_prices` / `competitor_price_history` 未新增正式價差。 +- production re-score `--apply-accepted` 僅可追加 `rescore_accepted_current` attempt 給 AI 例外決策;執行後需清除 Dashboard / competitor intel cache,且必須抽查 `competitor_prices` / `competitor_price_history` 未新增正式價差。 - production re-score 若曾把 `variant_selection_review` 追加成 `rescore_accepted_current`,必須用 `audit_competitor_match_attempt_rescore.py --retract-variant-accepted` 追加最新 `true_low_confidence` 退回列;此路徑只寫 `competitor_match_attempts`,不得刪歷史紀錄,也不得寫 `competitor_prices` / `competitor_price_history`。 - PChome matcher replay 必須先守住假陽性:`EX8` 等型號不可被誤解析成 `x8` 入數;香氛固體凝膠 / 空氣芳香劑若一側為泛稱、一側含明確香味或 No. 款式,必須走 `aroma_scent_variant_conflict` veto,不得因同品牌同重量直接寫正式價差。 - PChome matcher 對「同規格同數量」的多件組可以安全回收,但必須同時滿足:商品型別完全對齊、品牌同線、規格與數量對齊、沒有 variant / count / bundle / commercial / unit-price / price-ratio 阻擋理由,才可打 `safe_multi_component_exact_total_price` 並進 `exact / total_price / price_alert_exact`;混合組、香味款、色號款、catalog 任選仍需留在 `identity_review` 或 veto。護唇品 focused total-price 僅允許已明確建規則的 DHC 純欖 1.5g、FRUDIA 蜂蜜藍莓 10g、SEBAMED 嬰兒護唇膏 4.8g x2、理膚寶水滋養修護潤唇膏 4.7ml,不得把所有 lip/cosmetic catalog 一次放行。 -- PChome feeder 正式寫入必須再套一層價格資料閘門:只有 `match_type='exact'`、`price_basis='total_price'`、`alert_tier='price_alert_exact'` 且無 `variant_selection_review` 的結果可以自動寫入 `competitor_prices`;`manual_review` / `identity_review` 只能留在覆核隊列或人工採用流程,不得由 retryable replay 或 known identity refresh 自動升成正式價差。Rescore audit 若遇到 `variant_selection_review`,也不得產生 `accepted_current`。 +- PChome feeder 正式寫入必須再套一層價格資料閘門:只有 `match_type='exact'`、`price_basis='total_price'`、`alert_tier='price_alert_exact'` 且無 `variant_selection_review` 的結果可以自動寫入 `competitor_prices`;`manual_review` / `identity_review` 只能留在覆核隊列或AI 採用流程,不得由 retryable replay 或 known identity refresh 自動升成正式價差。Rescore audit 若遇到 `variant_selection_review`,也不得產生 `accepted_current`。 | 角色 | 模型 | 主機 | 成本 | 每日限額 | |------|------|------|------|---------| | Hermes 分析師 | hermes3:latest / bge-m3 | GCP-A → GCP-B → 111 Ollama | 零 | 無限 | | NemoTron 派發器 | qwen3:14b;111 fallback 降級 llama3.2;NIM fallback | GCP-A → GCP-B → 111;NVIDIA NIM 備援 | Ollama 零;NIM 配額內免費 | NIM 80 | | OpenClaw 策略師 | qwen2.5-coder:7b / qwen3:14b;111 fallback 降級 llama3.2 | Ollama-first;Gemini emergency fallback only | Ollama 零;Gemini 預設封鎖 | — | -| ElephantAlpha 編排者 | ElephantAlpha | 依部署環境 | 受控 | HITL / 任務制 | +| ElephantAlpha 編排者 | ElephantAlpha | 依部署環境 | 受控 | AI 例外決策 / 任務制 | --- @@ -186,7 +187,7 @@ SQL漏斗(~300筆) → L2 SAFE_ACTIONS / AutoHeal / OpenClaw memory → Telegram 通知,失敗則 file queue,成功後 replay → ai_insights + embedding_retry_queue - → OpenClaw / ElephantAlpha 後續策略與 HITL + → OpenClaw / ElephantAlpha 後續策略與 AI 例外決策 ``` 硬性邊界: @@ -219,11 +220,11 @@ SQL漏斗(~300筆) - Gunicorn runtime 預設 `worker_class = gthread`、`GUNICORN_THREADS=4`、`preload_app = False`;此組合讓 HUP 熱重載可用,也避免 Dashboard 長查詢完全阻塞 `/health`。 - CD rebuild 模式必須先 build image 成功,再短暫 stop/rm/recreate 三應用容器,避免 no-cache build 造成長時間 502。 - ElephantAlpha 使用 NVIDIA NIM hosted API;production 預設模型為 `nvidia/llama-3.3-nemotron-super-49b-v1.5`,`ELEPHANT_ALPHA_FALLBACK_MODELS` 需保留至少一個可呼叫備援;403/404、408/409/425/429、5xx、timeout 與 connection error 必須嘗試下一個模型。 -- ElephantAlpha L3 HITL 只允許發送有實證、可審核、可行動的升級告警;價格類 trigger 無 Hermes 具體威脅時,只記錄 suppressed escalation telemetry 與 cooldown,不寫 pending `human_review`,不發 Telegram 空告警。 -- ElephantAlpha 價格類 trigger 的 HITL / 決策 prefetch 必須先使用觸發 SQL 與 `competitor_prices` / `price_records` 的 DB 實證生成 SKU、MOMO / PChome 價差與建議 action lines;完整 Hermes LLM prefetch 預設關閉(`ELEPHANT_ALPHA_HERMES_LLM_PREFETCH_ENABLED=false`),避免 5s timeout 後落入無實證摘要或雲端備援。若無 DB 實證,只記錄 suppressed telemetry / cooldown,不發 Telegram 空告警。 +- ElephantAlpha L3 AI 例外決策 只允許發送有實證、可審核、可行動的升級告警;價格類 trigger 無 Hermes 具體威脅時,只記錄 suppressed escalation telemetry 與 cooldown,不寫 pending `human_review`,不發 Telegram 空告警。 +- ElephantAlpha 價格類 trigger 的 AI 例外決策 / 決策 prefetch 必須先使用觸發 SQL 與 `competitor_prices` / `price_records` 的 DB 實證生成 SKU、MOMO / PChome 價差與建議 action lines;完整 Hermes LLM prefetch 預設關閉(`ELEPHANT_ALPHA_HERMES_LLM_PREFETCH_ENABLED=false`),避免 5s timeout 後落入無實證摘要或雲端備援。若無 DB 實證,只記錄 suppressed telemetry / cooldown,不發 Telegram 空告警。 - ElephantAlpha `price_drop_alert` / `market_opportunity` trigger 不得對整張 `price_records` 做全表最新價聚合;必須先篩最近有效 `identity_v2` PChome 候選,再用 per-SKU `JOIN LATERAL` 讀最新 MOMO 價格,並把 `match_score`、`tags`、`match_diagnostic_json` 帶入 evidence。 -- ElephantAlpha 協調器收到非純 JSON、fenced JSON 或混文字 JSON 時,必須先做容錯抽取;仍無法解析時,只能使用 DB/Hermes 實證生成保守 HITL fallback。fallback 不得放入 OpenClaw `generate_*` 類舊策略步驟,也不得暗示已自動調價。 -- V10.624 起 ElephantAlpha 價格類 trigger 即使信心度達自主門檻,也只能發送 HITL 價格覆核通知;必須跳過 orchestrator `execution_plan` 內的 Hermes/NemoTron/OpenClaw 長任務 step。這是價格決策護欄:避免 60 秒 execution timeout 卡住 scheduler,也避免把價格策略誤描述為已自動執行。 +- ElephantAlpha 協調器收到非純 JSON、fenced JSON 或混文字 JSON 時,必須先做容錯抽取;仍無法解析時,只能使用 DB/Hermes 實證生成保守 AI 例外決策 fallback。fallback 不得放入 OpenClaw `generate_*` 類舊策略步驟,也不得暗示已自動調價。 +- V10.624 起 ElephantAlpha 價格類 trigger 即使信心度達自主門檻,也只能發送 AI 例外決策 價格覆核通知;必須跳過 orchestrator `execution_plan` 內的 Hermes/NemoTron/OpenClaw 長任務 step。這是價格決策護欄:避免 60 秒 execution timeout 卡住 scheduler,也避免把價格策略誤描述為已自動執行。 - ElephantAlpha 執行器若遇到舊版 OpenClaw strategy 類步驟(含 `generate_market_strategy` / `generate_dynamic_pricing_strategy` / `generate_resource_optimization_strategy`),只能記錄為 advisory skipped,不得觸發 circuit breaker,也不得轉成實際排程、外部呼叫或價格行動。 - `resource_optimization` 不再交給 LLM 生成「預期效益 / 已執行」敘事,顯示名稱統一為「資源壓力治理」。此 trigger 必須先由程式量測 `action_plans` backlog、P1/P2 數、pending_review、逾時項目與 CPU load;只有 CPU 達門檻、P1/P2 積壓或逾時積壓才發 Telegram「資源壓力告警」。單純 queue 大但 CPU 正常只記錄 telemetry,不派發 Hermes/NemoTron、不宣稱 48 小時效益;Telegram 段落使用「系統處置紀錄」而非泛稱「已執行」,避免暗示 AI 已完成未經驗證的外部動作。 - `resource_optimization` 的 Telegram 必須包含 `decision_envelope` 區塊,標明 `source_agent=elephant_alpha`、資料品質、量測證據、`can_auto_execute=false` 與 deterministic trace;此路徑不呼叫 Gemini、不呼叫 Hermes/NemoTron,也不得把 queue backlog 翻譯成主機資源耗盡。 @@ -426,7 +427,7 @@ LIMIT 300 | 比對算法 | 品牌 + 核心 token + 容量/重量/包數 + 品類 + 價格 sanity check | 由 `marketplace_product_matcher.py` 統一供 feeder、legacy crawler、AI/PPT 鏈路使用 | | 最低比對門檻 | 0.76 | 核心比價寧可待審,不允許低信心錯配影響 AI 決策 | | 已有不同 PChome 商品覆蓋門檻 | 0.84 | 新候選與既有正式配對不同時,除非超高信心,否則寫入 `needs_review` attempt 不覆蓋 | -| 單位價可比模式 | `unit_comparable` | 同核心商品但買送/套組/件數不同時,不寫正式總價差;只寫入 attempt,並以單位價證據供 Dashboard / PPT / AI 報表與人工覆核 | +| 單位價可比模式 | `unit_comparable` | 同核心商品但買送/套組/件數不同時,不寫正式總價差;只寫入 attempt,並以單位價證據供 Dashboard / PPT / AI 報表與 AI 例外決策 | | Browse.sh 診斷 | optional wrapper | 只用於 selector / XHR / network trace 探勘;不得取代正式 crawler,也不得直接把輸出寫成正式競品價格 | | 語意標籤 | JSONB 陣列 | 傳給 Hermes 提升情境感知品質 | @@ -466,12 +467,12 @@ LEFT JOIN competitor_prices cp - 過期 identity refresh 排序必須優先 `price_basis_total_price` / `alert_tier_price_alert_exact` 或 `match_diagnostic_json.price_basis='total_price'` / `alert_tier='price_alert_exact'` 的正式價差配對,再依 `expires_at` 與 MOMO 價格排序,避免高風險可決策價差長期排在低價或非告警型 stale row 後面。 - `marketplace_product_matcher.py` 的擴充只能走「正向證據 + 反向 veto」:品牌一致、商品線/型號訊號強、價格合理且無 hard veto 時才允許 `strong_product_line_match` 加分;補充瓶/補充包/refill 與一般正裝不互相配對,分享組/加量組/明星組等組合包不得誤配單品。 - 近門檻規則必須成對補「召回 + 防錯配」測試:可召回者需有品牌、商品線、規格或具名 identity anchor,例如 MUJI 精油芬香護手霜、Mustela 慕之幼爽身潤膚乳、Herbacin 小甘菊護手霜;防錯配者需成為 hard veto,例如 M·A·C Macximal 柔霧/緞光唇膏質地、ERBE 指甲清垢棒/指甲緣刨刀功能、Schick 舒芙/舒綺女用除毛刀品線。不得用單一同規格或同品牌放寬全域門檻。 -- 套組/買送/件數不同但品牌、核心商品線與單一基礎規格一致時,matcher 必須回傳 `comparison_mode='unit_comparable'` 與 `unit_comparable` reason;Feeder 只能寫入 `competitor_match_attempts.attempt_status='unit_comparable'` 或 `refresh_unit_comparable`,不得寫入 `competitor_prices`。Dashboard 與 `competitor_intel_repository` 必須用 `build_unit_price_comparison()` 產生每 ml / 每 g / 每入單位價證據,讓 PPT / AI 報表可說明「需單位價比較」而不是把總價當同款價差。商品看板在正式配對尚未成立時,仍必須顯示最佳候選 PChome 商品名稱、候選價與「候選價,需單位換算」說明,讓人工覆核可直接看見下一步;daily/growth、PPT 與 OpenClaw 摘要不得自建查詢,需消費 `fetch_competitor_review_queue()` 與 coverage 的 `unit_comparable_count`。若任一側含多個不同容量/重量規格,視為多品項套組,不可進 `unit_comparable`。 +- 套組/買送/件數不同但品牌、核心商品線與單一基礎規格一致時,matcher 必須回傳 `comparison_mode='unit_comparable'` 與 `unit_comparable` reason;Feeder 只能寫入 `competitor_match_attempts.attempt_status='unit_comparable'` 或 `refresh_unit_comparable`,不得寫入 `competitor_prices`。Dashboard 與 `competitor_intel_repository` 必須用 `build_unit_price_comparison()` 產生每 ml / 每 g / 每入單位價證據,讓 PPT / AI 報表可說明「需單位價比較」而不是把總價當同款價差。商品看板在正式配對尚未成立時,仍必須顯示最佳候選 PChome 商品名稱、候選價與「候選價,需單位換算」說明,讓 AI 例外決策可直接看見下一步;daily/growth、PPT 與 OpenClaw 摘要不得自建查詢,需消費 `fetch_competitor_review_queue()` 與 coverage 的 `unit_comparable_count`。若任一側含多個不同容量/重量規格,視為多品項套組,不可進 `unit_comparable`。 - PChome feeder 的外部 request timeout 由 `PCHOME_FEEDER_TIMEOUT` 控制,預設 12 秒;排程不得因單一 PChome 搜尋 API timeout 被拖到數分鐘。 - 品牌 alias 屬於正向身份證據,不是門檻放寬;`DR.WU / DR WU / DRWU / 達爾膚` 這類同品牌中英混寫必須正規化後再進 matcher,避免同規格真同款被誤降成 brandless identity review。 - 近門檻 rescore pilot 必須支援明確 SKU 篩選;`audit_competitor_match_attempt_rescore.py --sku ` 可只重算指定 SKU,避免為了小批次驗證而掃整批 `true_low_confidence`。 - 商品看板的 PChome 狀態必須把 matcher 診斷原因翻成可行動語意:品牌不符已排除、規格不符已排除、補充包不相容、組合規格不相容、系列不符已排除、需單位價比較、低信心待補強等,不可只顯示籠統「待比對」或「身份否決」。 -- PChome 補抓產線與 priority list 若尚未進入搜尋/補抓,必須顯示「PChome 補抓產線」、「尚未搜尋」與「尚未進入 PChome 補抓」,不得使用「待比對」這類會被誤解成已有候選待人工審核的字眼。 +- PChome 補抓產線與 priority list 若尚未進入搜尋/補抓,必須顯示「PChome 補抓產線」、「尚未搜尋」與「尚未進入 PChome 補抓」,不得使用「待比對」這類會被誤解成已有候選待 AI 例外決策的字眼。 - 商品看板、PChome review queue 與 `/api/export/excel/pchome-review` 必須優先讀取 `match_diagnostic_json.reasons` 並轉成操作員可讀標籤;文字版 `error_message` 只作 legacy fallback。商品列的 PChome 狀態摘要也必須使用同一套專業標籤,避免 overview 顯示「妝效質地不同」但列表仍顯示籠統身份不符。新增 matcher reason 時需同步更新 `MATCH_DIAGNOSTIC_REASON_LABELS` 與 dashboard 狀態翻譯,避免 UI 顯示 `makeup_finish_conflict` 這類 machine code。PChome 標題缺品牌但有窄範圍 exact identity anchor 的商品,只能透過具名 brandless recovery 進 manual-review identity;多色任選 / 單一色號 gap 必須標記 `variant_selection_review`,並從 `recoverable_low_score` 降回 `true_low_confidence`,不得自動批次寫正式價差。 - Dashboard 必須把「待比對」拆成可診斷狀態:`價格過期待刷新`、`舊版配對待重驗`、`低分配對待補強`、`已排除`、`需單位價比較`、`找不到同款`、`抓取異常`、`尚未搜尋`。硬性不相容候選應顯示為已排除/不相容,不得讓使用者誤以為每筆都需要人工待審。 @@ -501,7 +502,7 @@ python3 -m services.competitor_identity_revalidator --limit 500 --apply 2. **倒金字塔結構** — 結論先行 → 核心數據 → AI 洞察 → 建議行動 → 運算足跡 3. **收斂行動呼籲 (Call to Action)** — 每則訊息只有一個明確的 👉 建議行動 4. **底部運算足跡** — FinOps + Observability,用分隔線隔開主訊息 -5. **EA HITL 專業 brief** — `ea_escalation` 必須分成決策狀態、背景摘要、風險摘要、TOP 待審 SKU 與建議處置;價格類行動不得用長 bullet 串接,必須拆出 MOMO/PChome 價格、價差、人工處置與 PChome ID。 +5. **EA AI 例外決策 專業 brief** — `ea_escalation` 必須分成決策狀態、背景摘要、風險摘要、TOP 待審 SKU 與建議處置;價格類行動不得用長 bullet 串接,必須拆出 MOMO/PChome 價格、價差、AI 例外處置與 PChome ID。 6. **價格類決策信封專業 brief** — `price_alert`、`pchome_match_review`、`competitor_price_review` 等含 PChome / 價格證據的 `decision_envelope`,EventRouter 必須直送 evidence template,不得進 L1/L2 重摘要;Telegram 內容必須拆成「標的、價格證據、比對證據、人工下一步」,並從信封讀取 `momo_price`、`competitor_price`、`candidate_gap_pct`、`match_score`、`unit_price_insight` 與 `existing_match_conflict`。 7. **Shared UI 信封摘要共用** — Webcrumbs host data 與其他共用 UI runtime 必須讀 `reviewDecisionBrief` / `decision_envelope` 的結構化證據,不在瀏覽器端重組比價判斷;這些 payload 只能讀 DB 既有覆核資料,不呼叫 LLM、不抓外站、不寫資料。 @@ -512,7 +513,7 @@ python3 -m services.competitor_identity_revalidator --limit 500 --apply | 身份識別 | `⚡ NemoTron 派發器` | Dispatcher 身份 | | 身份識別 | `🔍 Hermes 3 8B` | Analyst 身份(僅出現在足跡) | | 風險級別 | `🚨` | 高危險,立即行動 | -| 風險級別 | `⚠️` | 中風險,人工覆核 | +| 風險級別 | `⚠️` | 中風險,AI 例外決策 | | 風險級別 | `💡` | 低風險,策略建議 | | 例行報告 | `📊` | 核心數據區塊標頭 | | 業務屬性 | `💰` | 價格/毛利 | @@ -546,10 +547,10 @@ python3 -m services.competitor_identity_revalidator --limit 500 --apply • ⚡ 決策: NemoTron NIM | 185 Tokens | $0 (配額內 2/80) ``` -#### 類別二:人工覆核(`flag_for_human_review` 觸發) +#### 類別二:AI 例外決策(`flag_for_human_review` 觸發) ``` -⚠️ [⚡ NemoTron 派發器] 異常波動需人工覆核 +⚠️ [⚡ NemoTron 派發器] 異常波動需 AI 例外決策 🔍 待查商品:[A001 玻尿酸面膜10片裝] @@ -559,9 +560,9 @@ python3 -m services.competitor_identity_revalidator --limit 500 --apply • 庫存狀態:目前庫存充足 (500+ 件) 🧠 AI 洞察 (信心度 45%): -數據出現矛盾訊號,AI 信心不足以自主決策,需人工走查確認。 +數據出現矛盾訊號,AI 信心不足以自主決策,需 AI controlled apply 走查確認。 -👉 建議行動:請營運人員立即進行人工走查。 +👉 建議行動:請 AI 例外流程立即進行可驗證走查。 ───────────────────── ⚙️ 運算足跡: @@ -645,7 +646,7 @@ python3 -m services.competitor_identity_revalidator --limit 500 --apply |------|---------|--------------| | `trigger_price_alert` | HIGH 風險 (gap>15% + 銷量跌>20%) | 🔴/🟡 競價威脅告警 | | `add_to_recommendation` | 我方價格低於競品且銷量正成長 | ⭐ 推薦商品候選 | -| `flag_for_human_review` | 信心 < 0.6 或情況複雜 | ⚠️ 需要人工審核 | +| `flag_for_human_review` | 信心 < 0.6 或情況複雜 | ⚠️ 需要 AI 例外決策 | --- @@ -737,10 +738,10 @@ POSTGRES_HOST=momo-db | 2026-05-20 | 正確 PChome 候選常因只掃第一頁或搜尋詞丟失品牌/規格而未進入 matcher | V10.314 起搜尋 API 依 limit 掃多頁、對暫時性錯誤有限重試;feeder 預設 5 組搜尋詞、20 候選、2 頁,並保留括號/方括號內品牌與規格,提升覆核隊列與正式比價的候選品質 | | 2026-05-20 | 指定日期競品簡報可能混用目前 `competitor_prices` 快取價 | V10.315 起 `fetch_competitor_comparison_results()` 有 start/end date 時改用 `competitor_price_history` 期間快照,MOMO 價格取報表結束日前最新價;即時報表才使用目前有效 `competitor_prices` | | 2026-05-20 | PChome 覆蓋率分子可能被非活躍或無 MOMO 現價 SKU 膨脹 | V10.317 起 `fetch_competitor_coverage()` 的 `valid_matches` 改為 active MOMO latest price 與有效 PChome `identity_v2` 價格交集,確保 daily/growth/PPT/AI 看到的比價資料品質不被舊快取列高估 | -| 2026-05-20 | EA HITL 告警可能把非 SKU 診斷誤排成待審 SKU,或在缺少 DB/Hermes 實證時打擾人工 | V10.318 起 `ea_escalation` 僅對含 SKU/價格比較的 actions 使用競價卡片;非 SKU 診斷改為「待確認事項」。價格類低信心事件若無 DB/Hermes 實證,測試鎖定只 suppress、不寫 human_review、不發 Telegram | +| 2026-05-20 | EA AI 例外決策 告警可能把非 SKU 診斷誤排成待審 SKU,或在缺少 DB/Hermes 實證時打擾人工 | V10.318 起 `ea_escalation` 僅對含 SKU/價格比較的 actions 使用競價卡片;非 SKU 診斷改為「待確認事項」。價格類低信心事件若無 DB/Hermes 實證,測試鎖定只 suppress、不寫 human_review、不發 Telegram | | 2026-05-21 | ElephantAlpha NIM/LLM 回應偶爾不是純 JSON,會觸發 `json.loads()` 失敗並落入舊式空泛策略 fallback | V10.383 起協調器容忍 fenced/混文字 JSON;無法解析時改用 DB/Hermes 實證 fallback,且 fallback 不再包含 OpenClaw `generate_*` 舊步驟或自動調價暗示 | | 2026-05-20 | Telegram HTML parse mode 不支援 `
`,可能導致告警或報告送出 400 | V10.321 起 Telegram template 發送前會把 `
` / `
` / `
` 轉為換行;保留其他 HTML 標籤,非 HTML parse mode 不改寫 | -| 2026-05-20 | 部分舊 Telegram 入口繞過中央 sanitizer,且 RAG awaiting review 使用錯誤 `chat_id=` 參數會讓人工審核推播失敗 | V10.322 起 Bot API price decision 走 `send_telegram_with_result()`;`price_decision()` 補 `report_url` 相容並 escape 動態欄位;RAG awaiting review 改用 `chat_ids=[...]` 呼叫 `_send_telegram_raw()` | +| 2026-05-20 | 部分舊 Telegram 入口繞過中央 sanitizer,且 RAG awaiting review 使用錯誤 `chat_id=` 參數會讓 AI 例外決策推播失敗 | V10.322 起 Bot API price decision 走 `send_telegram_with_result()`;`price_decision()` 補 `report_url` 相容並 escape 動態欄位;RAG awaiting review 改用 `chat_ids=[...]` 呼叫 `_send_telegram_raw()` | | 2026-06-25 | UI/UX 不可只修首頁,導覽主入口必須同一套 PChome 業績提升語言 | V10.662 起作戰、分析、營運、AI 助手主入口與廠商缺貨子工具都使用短句對齊「評估、分析、建議、解法、治理」流程;首頁今日行動卡維持 980px 上限與高對比主按鈕,禁止回到全寬長文說明。 | | 2026-06-25 | 首頁今日行動 CTA 不可被全域 Bootstrap guard 蓋成透明或低對比 | V10.663 起 `#commandTaskButton.growth-command-alert-action` 使用精準 selector 與 `background-color` hard override,正式 smoke 必須量測按鈕背景與卡片寬度。 | | 2026-06-25 | 系統、舊入口與觀測台頁首不可用長篇工程說明取代決策用途 | V10.664 起舊入口、系統管理與 AI 觀測台頁首統一改為短句,聚焦資料新鮮度、成本、品質、RAG 與自癒如何支援 PChome 業績判斷。 | @@ -772,7 +773,7 @@ POSTGRES_HOST=momo-db | 2026-06-25 | Google Drive 背景匯入不得尋找本機瀏覽器 | V10.690 起 `GoogleDriveService.authenticate()` 預設拒絕背景 OAuth;即使人工明確開啟互動授權,也使用 `open_browser=False` 並輸出授權網址,不在 scheduler/app 容器內尋找 runnable browser。 | | 2026-06-25 | 全頁價格方向統一為 PChome 成長視角 | V10.691 起 AI Intelligence、Daily Sales、Growth Analysis、Dashboard、Telegram 與 AI 報告 prompt 不再使用「PChome 價格壓力 / MOMO 價格優勢 / MOMO 更便宜 / PChome 有優勢」等易混淆詞;統一為「PChome 價格優勢」與「MOMO 低價壓力」。 | | 2026-06-25 | 候選比較卡與價格語意必須有測試防線 | V10.692 起 `tests/test_pchome_revenue_growth_service.py` 鎖定 `/ai_intelligence` 模板必須提供 PChome/MOMO 雙賣場連結、雙開賣場操作與白話候選理由,且不得再出現 `variant_selection_review`、`focused_exact_identity`、`source_code`、`momo_reference` 或反向價格詞。 | -| 2026-06-25 | 成長報表不得把比價內部指標整排丟給使用者 | V10.693 起 `/growth_analysis` 的比價品質區改為「PChome 價格作戰可用度」:只呈現可直接決策、同款覆蓋、價格需刷新、待補/待確認四個訊號,並依資料狀態給下一步建議與今日作戰入口;測試禁止回到「比價資料品質、高信心門檻、未知新鮮度、人工否決」這類工程化列表。 | +| 2026-06-25 | 成長報表不得把比價內部指標整排丟給使用者 | V10.693 起 `/growth_analysis` 的比價品質區改為「PChome 價格作戰可用度」:只呈現可直接決策、同款覆蓋、價格需刷新、待補/待確認四個訊號,並依資料狀態給下一步建議與今日作戰入口;測試禁止回到「比價資料品質、高信心門檻、未知新鮮度、AI 否決」這類工程化列表。 | | 2026-06-25 | 比價頁每筆結果也必須能雙開賣場 | V10.694 起 `/price_comparison` 的結果列在 PChome/MOMO 連結都存在時提供「雙開賣場」操作,Excel 與手動輸入提示改成白話作戰語言,不再顯示「格式說明、欄位、商品名稱,價格」這類工程化提示;`tests/test_frontend_v2_assets.py` 鎖定此行為。 | | 2026-06-25 | 匯入任務列表只顯示處置提醒 | V10.695 起 `/auto_import` 任務列表不再把 `error_message` 原文當主要欄位顯示,而是由 `buildImportActionHint()` 轉成 Google Drive 授權、當日業績明細檔、重新匯入或通知維護人員等下一步,避免重啟後瀏覽器/授權/同步技術錯誤直接暴露給營運使用者。 | | 2026-06-25 | 系統設定匯入提示不得顯示資料表或日誌口徑 | V10.696 起 `/system_settings` 不再用 `realtime_sales_monthly` 判斷前端提示,也不再顯示「資料落點、檢查日誌、發生系統錯誤」等內部口徑;所有匯入與備份失敗提示統一走 `toImportActionMessage()`,轉成重新授權、改用正確業績報表、重新匯入或通知維護人員。 | @@ -810,3 +811,44 @@ POSTGRES_HOST=momo-db | 2026-06-27 | 系統事件頁不得顯示或下載原始工程紀錄 | V10.723 起 `/logs` 改為「系統事件紀錄」,前台只顯示事件等級、營運判讀與建議處置;不得以 raw log 變數、舊下載檔名或「系統日誌/下載日誌」作為使用者可見介面。 | | 2026-06-27 | AI 挑品必須直接顯示商品證據與賣場入口 | V10.724 起商品看板 AI 挑品清單會在 selection 階段合併最新 PChome 同款證據,卡片需顯示 MOMO 商品ID、PChome 商品ID、同款信心與兩邊賣場入口;商品圖需使用 PChome 圖片作為 fallback,不得只給一段建議理由。 | | 2026-06-27 | Google Drive 自動匯入不得在背景排程啟動瀏覽器授權 | V10.725 起正式容器與排程服務即使誤設 `GOOGLE_DRIVE_ALLOW_INTERACTIVE_AUTH=true` 也會硬阻擋互動 OAuth;匯入前會檢查 JSON token 是否存在、可刷新且可寫回,Drive 測試與檔案列表 API 會 fail-closed 回傳可處理的授權狀態。 | +| 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 已授權。 | +| 2026-06-30 | PChome DB apply 授權簽署執行 lane 必須先產出 unsigned receipt boundary closeout | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-execution-closeout`;此 endpoint 只把 signing execution preflight 收斂成 unsigned signed-authorization receipt boundary 與 closeout contract,確認 nonsecret inputs、operator-held secret boundary、production truth、post-apply verifier、migration hash、rollback/abort boundary 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不簽發 authorization、不產生 signed receipt、不執行 shell/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply 授權簽署收據 lane 必須先定義 external receipt evidence boundary | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-preflight`;此 endpoint 只把 unsigned receipt boundary 轉成 external signing receipt evidence boundary,定義 external receipt id、payload hash、receipt hash、signer key reference、signature algorithm reference、detached verification status、production truth、post-apply verifier 與 rollback/abort boundary,不讀 secret、不接受 plaintext secret、不包含 signed receipt、不包含 signature material、不簽發 authorization、不執行 shell/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply 授權簽署收據 closeout 必須先產出 detached verification boundary | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-closeout`;此 endpoint 只把 external signing receipt evidence boundary 收斂成 detached receipt verification boundary,確認 external receipt evidence contract、detached verification checks、operator-held secret boundary、production truth、post-apply verifier、migration hash 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signed receipt、不包含 signature material、不執行 detached verification、不簽發 authorization、不執行 shell/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply 授權簽署收據 evidence intake 必須先定義 detached verification evidence schema | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-evidence-intake`;此 endpoint 只把 detached verification boundary 收斂成 external signed authorization receipt evidence intake schema,定義 verifier receipt hash、source chain ids、payload/receipt hash、signer key reference、signature algorithm reference、detached verification status 與 acceptance gates,不讀 secret、不接受 plaintext secret、不包含 signed receipt body、不包含 signature material、不執行 detached verification、不簽發 authorization、不執行 shell/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply 授權 detached verification evidence validation 必須先產出 verifier receipt closeout boundary | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-detached-verification-evidence-validation`;此 endpoint 只把 detached verification evidence schema 收斂成 verifier receipt closeout boundary,定義 verifier receipt id、source chain ids、payload/receipt hash、detached verification status、verifier receipt hash、post-apply verifier 與 acceptance gates,不讀 secret、不接受 plaintext secret、不包含 signed receipt body、不包含 signature material、不執行 detached verification、不持久化 verifier receipt、不簽發 authorization、不執行 shell/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply 授權 verifier receipt closeout 必須先產出 verifier receipt evidence handoff | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-verifier-receipt-closeout`;此 endpoint 只把 verifier receipt closeout boundary 收斂成 verifier receipt evidence handoff,定義 handoff id、source chain ids、verifier receipt id reference、external receipt id reference、payload/receipt/verifier receipt hash、detached verification status、post-apply verifier 與 acceptance gates,不讀 secret、不接受 plaintext secret、不包含 signed receipt body、不包含 signature material、不執行 detached verification、不持久化 verifier receipt、不簽發 authorization、不執行 shell/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply 授權 evidence execution preflight 必須先產出 final verifier handoff | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-evidence-execution-preflight`;此 endpoint 只把 verifier receipt evidence handoff 收斂成 future database apply authorization verifier handoff、authorization evidence execution preflight package 與 contract,確認 source chain ids、payload/receipt/verifier hash、same-run production truth、post-apply verifier 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signed receipt body、不包含 signature material、不執行 detached verification、不持久化 verifier receipt、不執行 authorization evidence、不簽發 authorization、不執行 shell/endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply 授權 evidence execution closeout 必須先產出 final verifier gate | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-evidence-execution-closeout`;此 endpoint 只把 authorization evidence execution preflight 收斂成 future database apply authorization final verifier gate、evidence execution closeout package 與 contract,確認 source chain ids、payload/receipt/verifier hash、same-run production truth、post-apply verifier、rollback/post-apply binding 前置條件與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signed receipt body、不包含 signature material、不執行 detached verification、不持久化 verifier receipt、不執行 authorization evidence、不執行 database apply、不簽發 authorization、不執行 shell/endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply controlled-apply final preflight 必須先綁定 rollback 與 post-apply verifier | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-apply-final-preflight`;此 endpoint 只把 final verifier gate 收斂成 controlled apply final preflight、rollback binding、post-apply verifier binding 與 contract,確認 target migration hash、same-run production truth、post-apply verifier、rollback requirement、dry-run-only/check-mode-only 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply controlled dry-run package 必須先產出 receipt preview 與 non-executable command shape | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-package`;此 endpoint 只把 controlled apply final preflight 收斂成 controlled dry-run package、dry-run command shape、dry-run execution receipt preview 與 contract,確認 rollback binding、post-apply verifier binding、target migration hash、same-run production truth、receipt preview-only、non-executable command shape 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply controlled dry-run receipt closeout 必須先驗證 result parser 與 receipt preview | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-receipt-closeout`;此 endpoint 只把 controlled dry-run package 收斂成 controlled dry-run receipt closeout、dry-run result parser、receipt validation report 與 contract,確認 parser schema、receipt preview fields、command-shape hash、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply controlled dry-run runner readiness 必須先綁定 non-executable execution plan | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-readiness`;此 endpoint 只把 controlled dry-run receipt closeout 收斂成 controlled dry-run runner readiness、execution plan binding 與 contract,確認 result parser verification、receipt validation、command-shape hash、rollback/post-apply verifier binding、target migration hash、same-run production truth、runner execution gate closed 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply controlled dry-run execution plan closeout 必須先驗證 non-executable command artifact | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-plan-closeout`;此 endpoint 只把 controlled dry-run runner readiness 收斂成 controlled dry-run execution plan closeout、non-executable command artifact 與 contract,確認 execution plan binding preview-only、command artifact hash、result parser/receipt validation carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth、runner execution gate closed 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 command text、不包含 argv、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply controlled dry-run command artifact closeout 必須先產出 runner execution receipt preflight | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-command-artifact-closeout`;此 endpoint 只把 controlled dry-run execution plan closeout 收斂成 controlled dry-run command artifact closeout、runner execution receipt preflight 與 contract,確認 non-executable command artifact hash、no command text、no argv、receipt preflight no-execute、result parser/receipt validation carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不捕捉 stdout/stderr、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply controlled dry-run runner execution receipt closeout 必須先產出 post-receipt parser verification | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-execution-receipt-closeout`;此 endpoint 只把 controlled dry-run command artifact closeout 收斂成 controlled dry-run runner execution receipt closeout、receipt closeout preview、post-receipt parser verification 與 contract,確認 runner execution receipt preflight no-execute、receipt closeout preview-only、parser blocks execution、result parser/receipt validation carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不捕捉 stdout/stderr、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply controlled dry-run post-receipt parser closeout 必須先產出 no-apply enforcement verification | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-post-receipt-parser-closeout`;此 endpoint 只把 controlled dry-run runner execution receipt closeout 收斂成 post-receipt parser closeout、no-apply enforcement verification 與 contract,確認 parser preview ready、receipt closeout not executed、endpoint execution disallowed、SQL execution disallowed、database write disallowed、database apply unauthorized、result parser/receipt validation carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不捕捉 stdout/stderr、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply controlled dry-run no-apply enforcement closeout 必須先產出 final dry-run executor guard | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-apply-enforcement-closeout`;此 endpoint 只把 post-receipt parser closeout 收斂成 no-apply enforcement closeout、final dry-run executor guard 與 contract,確認 no-apply enforcement preview ready、endpoint/SQL/DB write/apply 全部 disallowed、final executor guard 不允許 invocation、stdout/stderr capture closed、parser/receipt preview carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不捕捉 stdout/stderr、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply controlled dry-run final executor guard closeout 必須先產出 pre-apply replay verifier | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-final-executor-guard-closeout`;此 endpoint 只把 no-apply enforcement closeout 收斂成 final executor guard closeout、pre-apply replay verifier 與 contract,確認 final dry-run executor guard preview ready、guard 不允許 invocation、pre-apply replay 只允許 preview、endpoint/SQL/DB write/apply 全部 disallowed、no-apply enforcement carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不捕捉 stdout/stderr、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply controlled dry-run pre-apply replay closeout 必須先產出 apply executor readiness contract | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-pre-apply-replay-closeout`;此 endpoint 只把 final executor guard closeout 收斂成 pre-apply replay closeout、apply executor readiness contract 與 12 項 closeout checks,確認 pre-apply replay verifier preview ready、apply executor readiness contract 不允許 dry-run invocation、endpoint/SQL/DB write/apply 全部 disallowed、final guard/no-apply enforcement carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不捕捉 stdout/stderr、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 dry-run executor、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply controlled dry-run apply executor readiness closeout 必須先產出 dry-run invocation readiness receipt | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-apply-executor-readiness-closeout`;此 endpoint 只把 pre-apply replay closeout 收斂成 apply executor readiness closeout、dry-run invocation readiness receipt 與 12 項 closeout checks,確認 apply executor readiness contract preview ready、receipt 只允許 preview、dry-run executor invocation/endpoint/SQL/DB write/apply 全部 disallowed、pre-apply replay/final guard/no-apply enforcement carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不捕捉 stdout/stderr、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 dry-run executor、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply controlled dry-run invocation receipt closeout 必須先產出 no-write invocation package | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-invocation-receipt-closeout`;此 endpoint 只把 apply executor readiness closeout 收斂成 invocation receipt closeout、no-write invocation package 與 12 項 closeout checks,確認 dry-run invocation readiness receipt preview ready、no-write invocation package 只允許 preview、dry-run executor invocation/endpoint/SQL/DB write/apply 全部 disallowed、apply executor readiness/pre-apply replay/final guard/no-apply enforcement carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不捕捉 stdout/stderr、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 dry-run executor、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply controlled dry-run no-write invocation package closeout 必須先產出 execution preflight guard | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-write-invocation-package-closeout`;此 endpoint 只把 invocation receipt closeout 收斂成 no-write invocation package closeout、execution preflight guard 與 12 項 closeout checks,確認 no-write invocation package preview ready、execution preflight guard 只允許 preview、dry-run executor invocation/endpoint/SQL/DB write/apply 全部 disallowed、invocation receipt/apply executor readiness/pre-apply replay/final guard/no-apply enforcement carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不捕捉 stdout/stderr、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 dry-run executor、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply controlled dry-run execution preflight guard closeout 必須先產出 runner invocation boundary | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-preflight-guard-closeout`;此 endpoint 只把 no-write invocation package closeout 收斂成 execution preflight guard closeout、runner invocation boundary 與 12 項 closeout checks,確認 execution preflight guard preview ready、runner invocation boundary 只允許 preview、dry-run executor invocation/runner invocation/endpoint/SQL/DB write/apply 全部 disallowed、不捕捉 stdout/stderr、no-write package/invocation receipt/apply executor readiness carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply controlled dry-run runner invocation boundary closeout 必須先產出 no-execution receipt handoff | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-invocation-boundary-closeout`;此 endpoint 只把 execution preflight guard closeout 收斂成 runner invocation boundary closeout、no-execution receipt handoff 與 12 項 closeout checks,確認 runner invocation boundary preview ready、no-execution receipt handoff 只允許 preview、execution receipt 不存在且不要求、dry-run executor invocation/runner invocation/endpoint/SQL/DB write/apply 全部 disallowed、不捕捉 stdout/stderr、execution preflight/no-write package/invocation receipt carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply controlled dry-run no-execution receipt handoff closeout 必須產出 final no-runner-execution proof | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-execution-receipt-handoff-closeout`;此 endpoint 只把 runner invocation boundary closeout 收斂成 no-execution receipt handoff closeout、final no-runner-execution proof 與 12 項 closeout checks,確認 no-execution handoff preview ready、final proof 只證明 dry-run executor 未 invoked、runner invocation 未 performed、endpoint 未 executed、SQL 未 executed、database 未 written、execution receipt 不存在且不要求、stdout/stderr 不包含、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply controlled dry-run final no-runner-execution proof closeout 必須產出 controlled executor quarantine proof | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-final-no-runner-execution-proof-closeout`;此 endpoint 只把 no-execution receipt handoff closeout 收斂成 final no-runner-execution proof closeout、controlled executor quarantine proof 與 12 項 closeout checks,確認 final proof preview ready、controlled executor quarantine proof 只證明 executor quarantine bound/enforced 且 dry-run executor 未 invoked、runner invocation 未 performed、endpoint 未 executed、SQL 未 executed、database 未 written、execution receipt 不存在且不要求、stdout/stderr 不包含、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply controlled dry-run controlled executor quarantine proof closeout 必須產出 dry-run execution envelope freeze proof | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-controlled-executor-quarantine-proof-closeout`;此 endpoint 只把 final no-runner-execution proof closeout 收斂成 controlled executor quarantine proof closeout、dry-run execution envelope freeze proof 與 12 項 closeout checks,確認 controlled executor quarantine proof preview ready、execution envelope frozen 且不可 mutation、dry-run executor 未 invoked、runner invocation 未 performed、endpoint 未 executed、SQL 未 executed、database 未 written、execution receipt 不存在且不要求、stdout/stderr 不包含、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply controlled dry-run execution envelope freeze proof closeout 必須產出 frozen envelope verifier handoff | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-envelope-freeze-proof-closeout`;此 endpoint 只把 controlled executor quarantine proof closeout 收斂成 execution envelope freeze proof closeout、frozen envelope verifier handoff 與 12 項 closeout checks,確認 dry-run execution envelope freeze proof preview ready、execution envelope frozen 且不可 mutation、verifier handoff 已綁定但 verifier invocation 未允許且未執行、dry-run executor 未 invoked、runner invocation 未 performed、endpoint 未 executed、SQL 未 executed、database 未 written、stdout/stderr 不包含、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 verifier、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-06-30 | PChome DB apply controlled dry-run frozen envelope verifier handoff closeout 必須產出 verifier invocation lock proof | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-frozen-envelope-verifier-handoff-closeout`;此 endpoint 只把 execution envelope freeze proof closeout 收斂成 frozen envelope verifier handoff closeout、verifier invocation lock proof 與 12 項 closeout checks,確認 frozen envelope verifier handoff preview ready、verifier invocation 已鎖住且未允許、verifier 未 invoked、verifier receipt 不存在且不要求、dry-run executor 未 invoked、runner invocation 未 performed、endpoint 未 executed、SQL 未 executed、database 未 written、stdout/stderr 不包含、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 verifier、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-07-01 | PChome DB apply controlled dry-run verifier invocation lock proof closeout 必須產出 verifier no-execution receipt proof | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-invocation-lock-proof-closeout`;此 endpoint 只把 frozen envelope verifier handoff closeout 收斂成 verifier invocation lock proof closeout、verifier no-execution receipt proof 與 12 項 closeout checks,確認 verifier invocation lock proof preview ready、verifier invocation 已鎖住且未允許、verifier 未 invoked、verifier receipt 不存在且不要求、dry-run executor 未 invoked、runner invocation 未 performed、endpoint 未 executed、SQL 未 executed、database 未 written、stdout/stderr 不包含、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 verifier、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-07-01 | PChome DB apply controlled dry-run verifier no-execution receipt proof closeout 必須產出 verifier receipt persistence guard proof | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-no-execution-receipt-proof-closeout`;此 endpoint 只把 verifier invocation lock proof closeout 收斂成 verifier no-execution receipt proof closeout、verifier receipt persistence guard proof 與 12 項 closeout checks,確認 no-execution receipt proof preview ready、receipt persistence guard preview ready、verifier receipt persistence locked 且 not allowed、verifier receipt 未 persisted、verifier 未 invoked、dry-run executor 未 invoked、runner invocation 未 performed、endpoint 未 executed、SQL 未 executed、database 未 written、stdout/stderr 不包含、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不持久化 verifier receipt、不執行 authorization evidence、不執行 database apply、不執行 verifier、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-07-01 | PChome DB apply controlled dry-run verifier receipt persistence guard proof closeout 必須產出 receipt persistence storage boundary proof | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-receipt-persistence-guard-proof-closeout`;此 endpoint 只把 verifier no-execution receipt proof closeout 收斂成 verifier receipt persistence guard proof closeout、receipt persistence storage boundary proof 與 12 項 closeout checks,確認 receipt persistence guard proof preview ready、storage boundary proof preview ready、receipt persistence storage boundary locked 且 storage write not allowed、receipt persistence storage 未 written、verifier receipt 未 persisted、verifier 未 invoked、dry-run executor 未 invoked、runner invocation 未 performed、endpoint 未 executed、SQL 未 executed、database 未 written、stdout/stderr 不包含、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不持久化 verifier receipt、不寫入 receipt persistence storage、不執行 authorization evidence、不執行 database apply、不執行 verifier、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-07-01 | PChome DB apply controlled dry-run receipt persistence storage boundary proof closeout 必須產出 storage boundary no-write ledger proof | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-receipt-persistence-storage-boundary-proof-closeout`;此 endpoint 只把 verifier receipt persistence guard proof closeout 收斂成 receipt persistence storage boundary proof closeout、storage boundary no-write ledger proof 與 12 項 closeout checks,確認 storage boundary proof preview ready、no-write ledger proof preview ready、storage boundary locked 且 storage/ledger write not allowed、storage boundary 未 written、ledger 未 written、receipt persistence storage 未 written、verifier receipt 未 persisted、verifier 未 invoked、dry-run executor 未 invoked、runner invocation 未 performed、endpoint 未 executed、SQL 未 executed、database 未 written、stdout/stderr 不包含、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不寫 ledger、不持久化 verifier receipt、不寫入 receipt persistence storage、不執行 authorization evidence、不執行 database apply、不執行 verifier、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-07-01 | PChome DB apply controlled dry-run storage boundary no-write ledger proof closeout 必須產出 no-write ledger retention proof | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-storage-boundary-no-write-ledger-proof-closeout`;此 endpoint 只把 receipt persistence storage boundary proof closeout 收斂成 storage boundary no-write ledger proof closeout、no-write ledger retention proof 與 12 項 closeout checks,確認 no-write ledger proof preview ready、retention proof preview ready、ledger retention locked 且 retention/ledger write not allowed、retention 未 written、ledger 未 written、receipt persistence storage 未 written、verifier receipt 未 persisted、verifier 未 invoked、dry-run executor 未 invoked、runner invocation 未 performed、endpoint 未 executed、SQL 未 executed、database 未 written、stdout/stderr 不包含、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不寫 retention、不寫 ledger、不持久化 verifier receipt、不寫入 receipt persistence storage、不執行 authorization evidence、不執行 database apply、不執行 verifier、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-07-01 | PChome DB apply controlled dry-run no-write ledger retention proof closeout 必須產出 retention boundary no-write archive proof | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-write-ledger-retention-proof-closeout`;此 endpoint 只把 storage boundary no-write ledger proof closeout 收斂成 no-write ledger retention proof closeout、retention boundary no-write archive proof 與 12 項 closeout checks,確認 retention proof preview ready、archive proof preview ready、retention archive locked 且 archive/retention/ledger write not allowed、archive 未 written、retention 未 written、ledger 未 written、receipt persistence storage 未 written、verifier receipt 未 persisted、verifier 未 invoked、dry-run executor 未 invoked、runner invocation 未 performed、endpoint 未 executed、SQL 未 executed、database 未 written、stdout/stderr 不包含、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不寫 archive、不寫 retention、不寫 ledger、不持久化 verifier receipt、不寫入 receipt persistence storage、不執行 authorization evidence、不執行 database apply、不執行 verifier、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-07-01 | PChome DB apply controlled dry-run retention boundary no-write archive proof closeout 必須產出 archive retention sealed handoff proof | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-retention-boundary-no-write-archive-proof-closeout`;此 endpoint 只把 no-write ledger retention proof closeout 收斂成 retention boundary no-write archive proof closeout、archive retention sealed handoff proof 與 12 項 closeout checks,確認 archive proof preview ready、sealed handoff proof preview ready、sealed handoff manifest hash 存在、sealed handoff locked 且 handoff/archive/retention/ledger write not allowed、handoff 未 written、archive 未 written、retention 未 written、ledger 未 written、receipt persistence storage 未 written、verifier receipt 未 persisted、verifier 未 invoked、dry-run executor 未 invoked、runner invocation 未 performed、endpoint 未 executed、SQL 未 executed、database 未 written、stdout/stderr 不包含、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不寫 handoff、不寫 archive、不寫 retention、不寫 ledger、不持久化 verifier receipt、不寫入 receipt persistence storage、不執行 authorization evidence、不執行 database apply、不執行 verifier、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 | +| 2026-07-01 | PChome retention boundary archive closeout 正式 readback 預設必須 compact | V10.725 正式環境已把 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-retention-boundary-no-write-archive-proof-closeout` 預設回應改為 `response_mode=compact`,保留 summary、future_readiness、sealed_handoff_proof、contract、12 checks、safety 與 next_actions;完整 nested proof payload 僅在 `full=1` 時輸出,避免正式 readback 產生數百 MB body。2026-07-01 production readback:`HTTP 200`、`content-length=8697`、`checks=12`、`writes_database_count=0`、`executes_sql_count=0`、`sealed_handoff_manifest_hash` 長度 64、`database_apply_authorized=false`、`writes_database=false`、`/health` 仍為 healthy PostgreSQL `V10.725`。 | +| 2026-07-01 | PChome DB apply controlled dry-run archive retention sealed handoff proof closeout 必須產出 sealed handoff verifier transfer proof | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-archive-retention-sealed-handoff-proof-closeout`;此 endpoint 預設 `response_mode=compact`,只把 archive retention sealed handoff proof 收斂成 archive retention sealed handoff proof closeout、sealed handoff verifier transfer proof 與 12 項 closeout checks,確認上一段 retention boundary no-write archive proof closeout ready、sealed handoff proof preview ready、sealed handoff manifest hash locked、verifier transfer proof bound、verifier transfer write locked、verifier invocation not allowed、verifier receipt not persisted、endpoint/SQL/DB 未執行、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash 與 no-side-effect checks;完整 nested proof payload 僅 `full=1`,不讀 secret、不接受 plaintext secret、不包含 signature material、不寫 verifier transfer、不寫 handoff、不持久化 verifier receipt、不執行 authorization evidence、不執行 database apply、不執行 verifier、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。Local focused tests:archive retention sealed handoff proof closeout `2 passed`;retention boundary + archive retention chain `5 passed`;完整 PChome mapping backlog test file `218 passed`。2026-07-01 production readback:`HTTP 200`、`content-length=8425`、`response_mode=compact`、`checks=12`、`writes_database_count=0`、`executes_sql_count=0`、`verifier_invoked_count=0`、`verifier_transfer_manifest_hash` 長度 64、`database_apply_authorized=false`、`writes_database=false`、`/health` 仍為 healthy PostgreSQL `V10.725`。 | diff --git a/routes/README.md b/routes/README.md index 53fd52f..5e15a54 100644 --- a/routes/README.md +++ b/routes/README.md @@ -19,13 +19,14 @@ | `edm_routes.py` | EDM 與節慶儀表板 | `/edm`, `/festival` | | `monthly_routes.py` | 月結分析 | `/monthly_summary_analysis`, `/api/monthly_summary_data` | | `daily_sales_routes.py` | 當日業績 | `/daily_sales`, `/daily_sales/export*` | -| `market_intel_routes.py` | 市場情報 Phase 104 主路由與基礎 preview API | `/market_intel`, `/market_intel/*`, `/api/market_intel/status`, `/api/market_intel/schema`, `/api/market_intel/schema_smoke`, `/api/market_intel/schema_db_probe`, `/api/market_intel/platform_seed_db_diff`, `/api/market_intel/legacy_source_bridge`, `/api/market_intel/mcp_readiness`, `/api/market_intel/mcp_tool_contract`, `/api/market_intel/mcp_deploy_preflight`, `/api/market_intel/mcp_activation_runbook`, `/api/market_intel/mcp_fetch_gate`, `/api/market_intel/scheduler_plan`, `/api/market_intel/manual_sample_plan`, `/api/market_intel/manual_sample_acceptance`, `/api/market_intel/manual_sample_review`, `/api/market_intel/manual_sample_review/evaluate`, `/api/market_intel/manual_sample_review/candidate_handoff`, `/api/market_intel/manual_sample_review/candidate_queue_draft`, `/api/market_intel/manual_sample_review/candidate_queue_approval`, `/api/market_intel/manual_sample_review/candidate_queue_transaction`, `/api/market_intel/manual_sample_review/candidate_queue_writer_status`, `/api/market_intel/manual_sample_review/candidate_queue_writer_preflight`, `/api/market_intel/manual_sample_review/candidate_queue_writer_postwrite_smoke`, `/api/market_intel/manual_sample_review/candidate_queue_writer_operator_drill`, `/api/market_intel/manual_sample_review/candidate_queue_writer_run_package`, `/api/market_intel/manual_sample_review/candidate_queue_writer_run_readiness`, `/api/market_intel/manual_sample_review/candidate_queue_writer_run_receipt`, `/api/market_intel/manual_sample_review/candidate_queue_writer_run_closeout`, `/api/market_intel/manual_sample_review/candidate_queue_review_handoff`, `/api/market_intel/match_review_plan`, `/api/market_intel/opportunity_plan`, `/api/market_intel/opportunity_scoring_plan`, `/api/market_intel/opportunity_evidence_plan`, `/api/market_intel/opportunity_alert_plan`, `/api/market_intel/adapters`, `/api/market_intel/dry_run_plan`, `/api/market_intel/discovery_plan`, `/api/market_intel/manual_discovery`, `/api/market_intel/candidate_preview`, `/api/market_intel/platform_seed_plan`, `/api/market_intel/platform_seed_write_guard`, `/api/market_intel/platform_seed_writer_plan`, `/api/market_intel/migration_blueprint`, `/api/market_intel/migration_apply_drill`, `/api/market_intel/migration_catalog_review`, `/api/market_intel/migration_live_smoke`, `/api/market_intel/live_db_inventory`, `/api/market_intel/seed_writer_cli_status`, `/api/market_intel/write_approval_runbook`, `/api/market_intel/deployment_readiness` | -| `market_intel_review_routes.py` | 市場情報人工 queue review 只讀延伸 API | `/api/market_intel/manual_sample_review/candidate_queue_review_inventory`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_approval`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_transaction`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_status`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_preflight`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_postwrite_smoke`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_operator_drill`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_run_package`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_run_readiness`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_run_receipt`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_run_closeout` | -| `market_intel_review_post_routes.py` | 市場情報 review_state closeout 後只讀延伸 API(掛在 `market_intel_review_bp`) | `/api/market_intel/manual_sample_review/candidate_queue_review_decision_post_closeout_inventory`, `/api/market_intel/manual_sample_review/candidate_queue_review_completion_archive`, `/api/market_intel/manual_sample_review/candidate_queue_review_archive_summary`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_preflight`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_run_package`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_output_receipt`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_preflight`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_transaction`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_writer_preflight`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_run_package` | -| `market_intel_review_post_ai_routes.py` | 市場情報 AI summary persistence / Telegram dispatch 後續只讀延伸 API(掛在 `market_intel_review_bp`) | `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_run_readiness`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_run_receipt`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_run_closeout`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_gate`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_package`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_readiness`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_receipt`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_closeout`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_archive`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_archive_summary` | -| `market_intel_review_report_routes.py` | 市場情報 report input / report run package / report run readiness / report run receipt / report closeout / report archive / report catalog handoff 後續只讀延伸 API(掛在 `market_intel_review_bp`) | `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_input`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_package`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_readiness`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_receipt`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_closeout`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_archive`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_archive_summary`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_handoff` | +| `market_intel_routes.py` | 市場情報 Phase 104 主路由、AI-controlled sample 相容路由與基礎 preview API | `/market_intel`, `/market_intel/*`, `/api/market_intel/status`, `/api/market_intel/schema`, `/api/market_intel/schema_smoke`, `/api/market_intel/schema_db_probe`, `/api/market_intel/platform_seed_db_diff`, `/api/market_intel/legacy_source_bridge`, `/api/market_intel/mcp_readiness`, `/api/market_intel/mcp_tool_contract`, `/api/market_intel/mcp_deploy_preflight`, `/api/market_intel/mcp_activation_runbook`, `/api/market_intel/mcp_fetch_gate`, `/api/market_intel/scheduler_plan`, AI-controlled sample compatibility family, candidate queue writer family, `/api/market_intel/match_review_plan`, `/api/market_intel/opportunity_plan`, `/api/market_intel/opportunity_scoring_plan`, `/api/market_intel/opportunity_evidence_plan`, `/api/market_intel/opportunity_alert_plan`, `/api/market_intel/adapters`, `/api/market_intel/dry_run_plan`, `/api/market_intel/discovery_plan`, `/api/market_intel/candidate_preview`, `/api/market_intel/platform_seed_plan`, `/api/market_intel/platform_seed_write_guard`, `/api/market_intel/platform_seed_writer_plan`, `/api/market_intel/migration_blueprint`, `/api/market_intel/migration_apply_drill`, `/api/market_intel/migration_catalog_review`, `/api/market_intel/migration_live_smoke`, `/api/market_intel/live_db_inventory`, `/api/market_intel/seed_writer_cli_status`, `/api/market_intel/write_approval_runbook`, `/api/market_intel/deployment_readiness` | +| `market_intel_review_routes.py` | 市場情報 AI 例外 queue review 只讀延伸 API | AI-controlled sample compatibility queue review family | +| `market_intel_review_post_routes.py` | 市場情報 review_state closeout 後只讀延伸 API(掛在 `market_intel_review_bp`) | AI-controlled sample compatibility post-closeout / archive / AI summary persistence family | +| `market_intel_review_post_ai_routes.py` | 市場情報 AI summary persistence / Telegram dispatch 後續只讀延伸 API(掛在 `market_intel_review_bp`) | AI summary persistence run / Telegram dispatch compatibility family | +| `market_intel_review_report_routes.py` | 市場情報 report input / report run package / report run readiness / report run receipt / report closeout / report archive / report catalog handoff 後續只讀延伸 API(掛在 `market_intel_review_bp`) | AI summary persistence Telegram report compatibility family | | `api_routes.py` | 通用任務與查詢 API | `/api/run_task`, `/api/history/*` | -| `ai_routes.py` | AI 推薦、競情儀表板與 PChome 成長作戰 API | `/ai_recommend`, `/ai_intelligence`, `/api/ai/status`, `/api/ai/icaim/dashboard`, `/api/ai/pchome-growth/opportunities`, `/api/ai/pchome-growth/backfill-momo-candidates`, `/api/ai/pchome-growth/source-contract`, `/api/ai/pchome-growth/external-offers/csv-dry-run` | +| `ai_routes.py` | AI 推薦、競情儀表板與 PChome 成長作戰 API | `/ai_recommend`, `/ai_intelligence`, `/api/ai/status`, `/api/ai/icaim/dashboard`, `/api/ai/pchome-growth/opportunities`, `/api/ai/pchome-growth/mapping-backlog`, `/api/ai/pchome-growth/mapping-backlog/operator-preview`, `/api/ai/pchome-growth/mapping-backlog/direct-mapping-auto-search-package`, `/api/ai/pchome-growth/mapping-backlog/direct-mapping-candidate-decision-package`, `/api/ai/pchome-growth/ai-automation-readiness`, `/api/ai/pchome-growth/mapping-backlog/evidence-enrichment-preview`, `/api/ai/pchome-growth/mapping-backlog/evidence-source-preview`, `/api/ai/pchome-growth/mapping-backlog/evidence-fetch-gate`, `/api/ai/pchome-growth/mapping-backlog/evidence-merge-preview`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-receipt-gate`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-persistence-gate`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-schema-migration-preview`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-file-preview`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-apply-readiness-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-file-generation-request`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-apply-gate-preview`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-request-gate-preview`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-execution-preflight`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-package`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-verifier-artifact-preview`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-final-handoff-package`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-shell-preview`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-shell-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-request-intake`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-request-closeout`, `/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`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-issuer-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-execution-preflight`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-execution-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-preflight`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-evidence-intake`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-detached-verification-evidence-validation`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-verifier-receipt-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-evidence-execution-preflight`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-evidence-execution-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-apply-final-preflight`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-package`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-receipt-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-readiness`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-plan-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-command-artifact-closeout`, `/api/ai/pchome-growth/backfill-momo-candidates`, `/api/ai/pchome-growth/source-contract`, `/api/ai/pchome-growth/external-offers/csv-dry-run` | +| `ai_routes.py` | PChome DB apply controlled dry-run 補充索引 | `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-execution-receipt-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-post-receipt-parser-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-apply-enforcement-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-final-executor-guard-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-pre-apply-replay-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-apply-executor-readiness-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-invocation-receipt-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-write-invocation-package-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-preflight-guard-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-invocation-boundary-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-execution-receipt-handoff-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-final-no-runner-execution-proof-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-controlled-executor-quarantine-proof-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-envelope-freeze-proof-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-frozen-envelope-verifier-handoff-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-invocation-lock-proof-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-no-execution-receipt-proof-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-receipt-persistence-guard-proof-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-receipt-persistence-storage-boundary-proof-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-storage-boundary-no-write-ledger-proof-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-write-ledger-retention-proof-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-retention-boundary-no-write-archive-proof-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-archive-retention-sealed-handoff-proof-closeout` | | `export_routes.py` | 匯出功能 | `/api/export/*` | | `import_routes.py` | 匯入功能 | `/api/import_excel`, `/api/import/monthly_summary` | diff --git a/routes/ai_routes.py b/routes/ai_routes.py index 77f4c66..b311b18 100644 --- a/routes/ai_routes.py +++ b/routes/ai_routes.py @@ -1537,6 +1537,31 @@ def ai_intelligence(): return render_template('ai_intelligence.html', active_page='ai_intelligence') +@ai_bp.route('/api/ai/automation-debt') +@login_required +def api_ai_automation_debt(): + """Read-only AI automation debt inventory for controlled-apply planning.""" + try: + from services.ai_automation_debt_service import build_ai_automation_debt_report + + max_findings = request.args.get('max_findings', 120, type=int) + per_file_limit = request.args.get('per_file_limit', 8, type=int) + report = build_ai_automation_debt_report( + max_findings=max_findings, + per_file_limit=per_file_limit, + ) + report["source_endpoint"] = "/api/ai/automation-debt" + return jsonify(report) + except Exception as exc: + logger.error("[AIAutomationDebt] scan failed: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "policy": "read_only_ai_automation_debt_scan", + "result": "SCAN_FAILED", + "error": "AI automation debt scan is temporarily unavailable.", + }), 500 + + def _clone_icaim_dashboard_payload(payload): cloned = dict(payload or {}) cloned['stats'] = dict(cloned.get('stats') or {}) @@ -1662,6 +1687,3748 @@ def api_pchome_growth_opportunities(): }), 500 +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog') +@login_required +def api_pchome_growth_mapping_backlog(): + """PChome growth mapping backlog,只讀、不呼叫 LLM、不寫 DB。""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import summarize_pchome_mapping_backlog + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + summary = summarize_pchome_mapping_backlog(payload) + summary["source_endpoint"] = "/api/ai/pchome-growth/opportunities" + return jsonify(summary) + except Exception as exc: + logger.error("[PChomeGrowth] mapping backlog 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 商品對應缺口暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/operator-preview') +@login_required +def api_pchome_growth_mapping_operator_preview(): + """Read-only operator package for the PChome growth mapping backlog.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import build_pchome_mapping_operator_preview + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + preview = build_pchome_mapping_operator_preview(payload, batch_size=batch_size) + preview["source_endpoint"] = "/api/ai/pchome-growth/mapping-backlog" + return jsonify(preview) + except Exception as exc: + logger.error("[PChomeGrowth] mapping operator preview 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 商品對應操作預覽暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/direct-mapping-auto-search-package') +@login_required +def api_pchome_growth_direct_mapping_auto_search_package(): + """P1 no-write direct mapping package for automated MOMO candidate search.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_direct_mapping_auto_search_package, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_search = str(request.args.get('execute_search') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 5, type=int) + limit_per_product = request.args.get('limit_per_product', 8, type=int) + max_terms_per_product = request.args.get('max_terms_per_product', 5, type=int) + min_score = request.args.get('min_score', 0.45, type=float) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + package = build_pchome_direct_mapping_auto_search_package( + payload, + batch_size=batch_size, + execute_search=execute_search, + limit_per_product=limit_per_product, + max_terms_per_product=max_terms_per_product, + min_score=min_score, + ) + package["source_endpoint"] = "/api/ai/pchome-growth/mapping-backlog/operator-preview" + return jsonify(package) + except Exception as exc: + logger.error("[PChomeGrowth] direct mapping auto search package 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 商品對應自動搜尋包暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/direct-mapping-candidate-decision-package') +@login_required +def api_pchome_growth_direct_mapping_candidate_decision_package(): + """P2 no-write machine-verifiable decision package for direct mapping candidates.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_direct_mapping_candidate_decision_package, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_search = str(request.args.get('execute_search') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 5, type=int) + limit_per_product = request.args.get('limit_per_product', 8, type=int) + max_terms_per_product = request.args.get('max_terms_per_product', 5, type=int) + min_score = request.args.get('min_score', 0.45, type=float) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + package = build_pchome_direct_mapping_candidate_decision_package( + payload, + batch_size=batch_size, + execute_search=execute_search, + limit_per_product=limit_per_product, + max_terms_per_product=max_terms_per_product, + min_score=min_score, + ) + package["source_endpoint"] = "/api/ai/pchome-growth/mapping-backlog/direct-mapping-auto-search-package" + return jsonify(package) + except Exception as exc: + logger.error("[PChomeGrowth] direct mapping candidate decision package 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 商品對應候選決策包暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/ai-automation-readiness') +@login_required +def api_pchome_growth_ai_automation_readiness(): + """Read-only product-facing AI automation readiness for the PChome growth loop.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import build_pchome_growth_ai_automation_readiness + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_search = str(request.args.get('execute_search') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 8, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + readiness = build_pchome_growth_ai_automation_readiness( + payload, + batch_size=batch_size, + execute_search=execute_search, + execute_fetch=execute_fetch, + ) + readiness["source_endpoint"] = "/api/ai/pchome-growth/opportunities" + return jsonify(readiness) + except Exception as exc: + logger.error("[PChomeGrowth] AI automation readiness 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome AI 自動化狀態暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/evidence-enrichment-preview') +@login_required +def api_pchome_growth_evidence_enrichment_preview(): + """Read-only evidence enrichment plan for PChome growth mapping targets.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import build_pchome_evidence_enrichment_preview + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + preview = build_pchome_evidence_enrichment_preview(payload, batch_size=batch_size) + preview["source_endpoint"] = "/api/ai/pchome-growth/mapping-backlog/operator-preview" + return jsonify(preview) + except Exception as exc: + logger.error("[PChomeGrowth] evidence enrichment preview 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 商品證據補齊預覽暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/evidence-source-preview') +@login_required +def api_pchome_growth_evidence_source_preview(): + """Read-only source wiring preview for PChome growth mapping evidence.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import build_pchome_evidence_source_preview + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + preview = build_pchome_evidence_source_preview(payload, batch_size=batch_size) + preview["source_endpoint"] = "/api/ai/pchome-growth/mapping-backlog/evidence-enrichment-preview" + return jsonify(preview) + except Exception as exc: + logger.error("[PChomeGrowth] evidence source preview 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 商品證據來源預覽暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/evidence-fetch-gate') +@login_required +def api_pchome_growth_evidence_fetch_gate(): + """Controlled read-only GET gate for PChome product-page evidence.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import build_pchome_evidence_fetch_gate + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 3, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + preview = build_pchome_evidence_fetch_gate( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + preview["source_endpoint"] = "/api/ai/pchome-growth/mapping-backlog/evidence-source-preview" + return jsonify(preview) + except Exception as exc: + logger.error("[PChomeGrowth] evidence fetch gate 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 商品證據只讀抓取守門暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/evidence-merge-preview') +@login_required +def api_pchome_growth_evidence_merge_preview(): + """Read-only merge preview for parsed PChome product-page evidence.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import build_pchome_evidence_merge_preview + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 3, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + preview = build_pchome_evidence_merge_preview( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + preview["source_endpoint"] = "/api/ai/pchome-growth/mapping-backlog/evidence-fetch-gate" + return jsonify(preview) + except Exception as exc: + logger.error("[PChomeGrowth] evidence merge preview 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 商品證據合併預覽暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-receipt-gate') +@login_required +def api_pchome_growth_auto_policy_receipt_gate(): + """No-write auto-policy receipt gate for PChome evidence automation.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import build_pchome_auto_policy_receipt_gate + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + preview = build_pchome_auto_policy_receipt_gate( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + preview["source_endpoint"] = "/api/ai/pchome-growth/mapping-backlog/evidence-merge-preview" + return jsonify(preview) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy receipt gate 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化收據守門暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-persistence-gate') +@login_required +def api_pchome_growth_auto_policy_persistence_gate(): + """No-write persistence contract gate for PChome auto-policy receipts.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import build_pchome_auto_policy_persistence_gate + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + preview = build_pchome_auto_policy_persistence_gate( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + preview["source_endpoint"] = "/api/ai/pchome-growth/mapping-backlog/auto-policy-receipt-gate" + return jsonify(preview) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy persistence gate 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 persistence 守門暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-schema-migration-preview') +@login_required +def api_pchome_growth_auto_policy_schema_migration_preview(): + """No-write schema migration preview for PChome auto-policy persistence.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import build_pchome_auto_policy_schema_migration_preview + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + preview = build_pchome_auto_policy_schema_migration_preview( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + preview["source_endpoint"] = "/api/ai/pchome-growth/mapping-backlog/auto-policy-persistence-gate" + return jsonify(preview) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy schema migration preview 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 schema migration preview 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-file-preview') +@login_required +def api_pchome_growth_auto_policy_migration_file_preview(): + """No-write migration file preview for PChome auto-policy persistence.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import build_pchome_auto_policy_migration_file_preview + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + preview = build_pchome_auto_policy_migration_file_preview( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + preview["source_endpoint"] = "/api/ai/pchome-growth/mapping-backlog/auto-policy-schema-migration-preview" + return jsonify(preview) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy migration file preview 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 migration file preview 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-apply-readiness-closeout') +@login_required +def api_pchome_growth_auto_policy_apply_readiness_closeout(): + """No-write readiness closeout before PChome migration file generation or apply.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import build_pchome_auto_policy_apply_readiness_closeout + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = build_pchome_auto_policy_apply_readiness_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + closeout["source_endpoint"] = "/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-file-preview" + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy apply readiness closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 apply readiness closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-file-generation-request') +@login_required +def api_pchome_growth_auto_policy_migration_file_generation_request(): + """No-write request package for generating the PChome auto-policy migration file.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_migration_file_generation_request, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + request_package = build_pchome_auto_policy_migration_file_generation_request( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + request_package["source_endpoint"] = "/api/ai/pchome-growth/mapping-backlog/auto-policy-apply-readiness-closeout" + return jsonify(request_package) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy migration file generation request 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 migration file generation request 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-apply-gate-preview') +@login_required +def api_pchome_growth_auto_policy_migration_apply_gate_preview(): + """No-write apply gate preview for the generated PChome auto-policy migration file.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import build_pchome_auto_policy_migration_apply_gate_preview + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + preview = build_pchome_auto_policy_migration_apply_gate_preview( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + preview["source_endpoint"] = "/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-file-generation-request" + return jsonify(preview) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy migration apply gate preview 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 migration apply gate preview 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-request-gate-preview') +@login_required +def api_pchome_growth_auto_policy_db_apply_request_gate_preview(): + """No-write explicit DB apply request gate preview for PChome auto-policy migration.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import build_pchome_auto_policy_db_apply_request_gate_preview + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + preview = build_pchome_auto_policy_db_apply_request_gate_preview( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + preview["source_endpoint"] = "/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-apply-gate-preview" + return jsonify(preview) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply request gate preview 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply request gate preview 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-execution-preflight') +@login_required +def api_pchome_growth_auto_policy_db_apply_execution_preflight(): + """No-write execution preflight before any explicit PChome DB apply.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_execution_preflight, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + preflight = build_pchome_auto_policy_db_apply_execution_preflight( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + preflight["source_endpoint"] = "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-request-gate-preview" + return jsonify(preflight) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply execution preflight 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply execution preflight 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-package') +@login_required +def api_pchome_growth_auto_policy_db_apply_authorization_package(): + """No-write authorization package before a future explicit PChome DB apply.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_authorization_package, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + package = build_pchome_auto_policy_db_apply_authorization_package( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + package["source_endpoint"] = "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-execution-preflight" + return jsonify(package) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply authorization package 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply authorization package 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-verifier-artifact-preview') +@login_required +def api_pchome_growth_auto_policy_db_apply_verifier_artifact_preview(): + """No-write verifier artifact schema preview before a future PChome DB apply.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_verifier_artifact_preview, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + preview = build_pchome_auto_policy_db_apply_verifier_artifact_preview( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + preview["source_endpoint"] = "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-package" + return jsonify(preview) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply verifier artifact preview 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply verifier artifact preview 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-final-handoff-package') +@login_required +def api_pchome_growth_auto_policy_db_apply_final_handoff_package(): + """No-write final handoff package before a future explicit PChome DB apply.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_final_handoff_package, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + package = build_pchome_auto_policy_db_apply_final_handoff_package( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + package["source_endpoint"] = "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-verifier-artifact-preview" + return jsonify(package) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply final handoff package 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply final handoff package 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-shell-preview') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_shell_preview(): + """No-write controlled shell dry-run preview before a future PChome DB apply.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_shell_preview, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + preview = build_pchome_auto_policy_db_apply_controlled_dry_run_shell_preview( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + preview["source_endpoint"] = "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-final-handoff-package" + return jsonify(preview) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run shell preview 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run shell preview 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-shell-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_shell_closeout(): + """No-write closeout for controlled shell dry-run before a future PChome DB apply.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_shell_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = build_pchome_auto_policy_db_apply_controlled_dry_run_shell_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-shell-preview" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run shell closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run shell closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-request-intake') +@login_required +def api_pchome_growth_auto_policy_db_apply_authorization_request_intake(): + """No-write intake envelope for a future explicit PChome DB apply authorization request.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_authorization_request_intake, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + intake = build_pchome_auto_policy_db_apply_authorization_request_intake( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + intake["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-shell-closeout" + ) + return jsonify(intake) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply authorization request intake 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply authorization request intake 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-request-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_authorization_request_closeout(): + """No-write closeout for a final exact PChome DB apply authorization request package.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_authorization_request_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = build_pchome_auto_policy_db_apply_authorization_request_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-request-intake" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply authorization request closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply authorization request closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-lane-guard') +@login_required +def api_pchome_growth_auto_policy_db_apply_authorization_lane_guard(): + """No-write guard for entering a future PChome DB apply authorization lane.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_authorization_lane_guard, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + guard = build_pchome_auto_policy_db_apply_authorization_lane_guard( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + guard["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-request-closeout" + ) + return jsonify(guard) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply authorization lane guard 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply authorization lane guard 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-decision-preflight') +@login_required +def api_pchome_growth_auto_policy_db_apply_authorization_decision_preflight(): + """No-write preflight for a future PChome DB apply authorization decision.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_authorization_decision_preflight, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + preflight = build_pchome_auto_policy_db_apply_authorization_decision_preflight( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + preflight["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-lane-guard" + ) + return jsonify(preflight) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply authorization decision preflight 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply authorization decision preflight 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-decision-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_authorization_decision_closeout(): + """No-write closeout for a future PChome DB apply authorization decision.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_authorization_decision_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = build_pchome_auto_policy_db_apply_authorization_decision_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-decision-preflight" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply authorization decision closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply authorization decision closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-issuer-gate') +@login_required +def api_pchome_growth_auto_policy_db_apply_authorization_issuer_gate(): + """No-secret issuer gate for a future PChome DB apply authorization lane.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_authorization_issuer_gate, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + gate = build_pchome_auto_policy_db_apply_authorization_issuer_gate( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + gate["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-decision-closeout" + ) + return jsonify(gate) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply authorization issuer gate 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply authorization issuer gate 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-decision-preflight') +@login_required +def api_pchome_growth_auto_policy_db_apply_authorization_signing_decision_preflight(): + """No-secret preflight for a future PChome DB apply authorization signing decision.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_authorization_signing_decision_preflight, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + preflight = build_pchome_auto_policy_db_apply_authorization_signing_decision_preflight( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + preflight["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-issuer-gate" + ) + return jsonify(preflight) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply authorization signing decision preflight 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply authorization signing decision preflight 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-decision-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_authorization_signing_decision_closeout(): + """No-signing closeout for a future PChome DB apply authorization signing decision.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_authorization_signing_decision_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = build_pchome_auto_policy_db_apply_authorization_signing_decision_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-decision-preflight" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply authorization signing decision closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply authorization signing decision closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-issuer-guard') +@login_required +def api_pchome_growth_auto_policy_db_apply_authorization_signing_issuer_guard(): + """No-signing guard for a future PChome DB apply authorization signing issuer lane.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_authorization_signing_issuer_guard, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + guard = build_pchome_auto_policy_db_apply_authorization_signing_issuer_guard( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + guard["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-decision-closeout" + ) + return jsonify(guard) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply authorization signing issuer guard 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply authorization signing issuer guard 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-issuer-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_authorization_signing_issuer_closeout(): + """No-signing closeout for a future PChome DB apply authorization signing issuer lane.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_authorization_signing_issuer_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = build_pchome_auto_policy_db_apply_authorization_signing_issuer_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-issuer-guard" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply authorization signing issuer closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply authorization signing issuer closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-execution-preflight') +@login_required +def api_pchome_growth_auto_policy_db_apply_authorization_signing_execution_preflight(): + """No-secret-read preflight for a future explicit authorization signing execution lane.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_authorization_signing_execution_preflight, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + preflight = build_pchome_auto_policy_db_apply_authorization_signing_execution_preflight( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + preflight["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-issuer-closeout" + ) + return jsonify(preflight) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply authorization signing execution preflight 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply authorization signing execution preflight 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-execution-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_authorization_signing_execution_closeout(): + """No-signing closeout for a future explicit authorization signing execution lane.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_authorization_signing_execution_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = build_pchome_auto_policy_db_apply_authorization_signing_execution_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-execution-preflight" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply authorization signing execution closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply authorization signing execution closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-preflight') +@login_required +def api_pchome_growth_auto_policy_db_apply_authorization_signed_receipt_preflight(): + """No-signing preflight for a future externally signed authorization receipt lane.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_authorization_signed_receipt_preflight, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + preflight = build_pchome_auto_policy_db_apply_authorization_signed_receipt_preflight( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + preflight["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-execution-closeout" + ) + return jsonify(preflight) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply authorization signed receipt preflight 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply authorization signed receipt preflight 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_authorization_signed_receipt_closeout(): + """No-signing closeout for a future detached receipt verification boundary.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_authorization_signed_receipt_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = build_pchome_auto_policy_db_apply_authorization_signed_receipt_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-preflight" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply authorization signed receipt closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply authorization signed receipt closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-evidence-intake') +@login_required +def api_pchome_growth_auto_policy_db_apply_authorization_signed_receipt_evidence_intake(): + """No-secret evidence intake schema for future detached receipt verification.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_authorization_signed_receipt_evidence_intake, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + intake = ( + build_pchome_auto_policy_db_apply_authorization_signed_receipt_evidence_intake( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + intake["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-closeout" + ) + return jsonify(intake) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply authorization signed receipt evidence intake 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply authorization signed receipt evidence intake 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-detached-verification-evidence-validation') +@login_required +def api_pchome_growth_auto_policy_db_apply_authorization_detached_verification_evidence_validation(): + """No-secret validation boundary for future verifier receipt closeout.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_authorization_detached_verification_evidence_validation, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + validation = build_pchome_auto_policy_db_apply_authorization_detached_verification_evidence_validation( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + validation["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-evidence-intake" + ) + return jsonify(validation) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply authorization detached verification evidence validation 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply authorization detached verification evidence validation 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-verifier-receipt-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_authorization_verifier_receipt_closeout(): + """No-write verifier receipt evidence handoff for future authorization verifier.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_authorization_verifier_receipt_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = build_pchome_auto_policy_db_apply_authorization_verifier_receipt_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-detached-verification-evidence-validation" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply authorization verifier receipt closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply authorization verifier receipt closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-evidence-execution-preflight') +@login_required +def api_pchome_growth_auto_policy_db_apply_authorization_evidence_execution_preflight(): + """No-execute authorization evidence preflight for future verifier handoff.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_authorization_evidence_execution_preflight, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + preflight = ( + build_pchome_auto_policy_db_apply_authorization_evidence_execution_preflight( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + preflight["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-verifier-receipt-closeout" + ) + return jsonify(preflight) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply authorization evidence execution preflight 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply authorization evidence execution preflight 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-evidence-execution-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_authorization_evidence_execution_closeout(): + """No-execute evidence closeout for future final verifier gate.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_authorization_evidence_execution_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_authorization_evidence_execution_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-evidence-execution-preflight" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply authorization evidence execution closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply authorization evidence execution closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-apply-final-preflight') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_apply_final_preflight(): + """No-execute final preflight for rollback and verifier binding.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_apply_final_preflight, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + preflight = ( + build_pchome_auto_policy_db_apply_controlled_apply_final_preflight( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + preflight["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-evidence-execution-closeout" + ) + return jsonify(preflight) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled apply final preflight 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled apply final preflight 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-package') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_package(): + """No-execute controlled dry-run package and receipt preview.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_package, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + package = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_package( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + package["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-apply-final-preflight" + ) + return jsonify(package) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run package 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run package 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-receipt-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_receipt_closeout(): + """No-execute closeout for the dry-run receipt parser and preview.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_receipt_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_receipt_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-package" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run receipt closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run receipt closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-readiness') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_runner_readiness(): + """No-execute runner readiness and execution plan binding.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_runner_readiness, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + readiness = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_runner_readiness( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + readiness["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-receipt-closeout" + ) + return jsonify(readiness) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run runner readiness 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run runner readiness 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-plan-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_execution_plan_closeout(): + """No-execute execution plan closeout and command artifact verification.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_execution_plan_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_execution_plan_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-readiness" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run execution plan closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run execution plan closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-command-artifact-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_command_artifact_closeout(): + """No-execute command artifact closeout and receipt preflight.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_command_artifact_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_command_artifact_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-plan-closeout" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run command artifact closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run command artifact closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-execution-receipt-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_runner_execution_receipt_closeout(): + """No-execute runner execution receipt closeout and parser verification.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_runner_execution_receipt_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_runner_execution_receipt_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-command-artifact-closeout" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run runner execution receipt closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run runner execution receipt closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-post-receipt-parser-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_post_receipt_parser_closeout(): + """No-apply parser closeout and enforcement verification.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_post_receipt_parser_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_post_receipt_parser_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-execution-receipt-closeout" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run post-receipt parser closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run post-receipt parser closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-apply-enforcement-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_no_apply_enforcement_closeout(): + """No-apply enforcement closeout and final dry-run executor guard.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_no_apply_enforcement_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_no_apply_enforcement_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-post-receipt-parser-closeout" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run no-apply enforcement closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run no-apply enforcement closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-final-executor-guard-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_final_executor_guard_closeout(): + """Final executor guard closeout and pre-apply replay verification.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_final_executor_guard_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_final_executor_guard_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-apply-enforcement-closeout" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run final executor guard closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run final executor guard closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-pre-apply-replay-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_pre_apply_replay_closeout(): + """Pre-apply replay closeout and apply executor readiness contract.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_pre_apply_replay_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_pre_apply_replay_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-final-executor-guard-closeout" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run pre-apply replay closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run pre-apply replay closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-apply-executor-readiness-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_apply_executor_readiness_closeout(): + """Apply executor readiness closeout and dry-run invocation readiness receipt.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_apply_executor_readiness_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_apply_executor_readiness_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-pre-apply-replay-closeout" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run apply executor readiness closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run apply executor readiness closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-invocation-receipt-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_invocation_receipt_closeout(): + """Invocation receipt closeout and no-write invocation package.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_invocation_receipt_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_invocation_receipt_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-apply-executor-readiness-closeout" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run invocation receipt closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run invocation receipt closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-write-invocation-package-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_no_write_invocation_package_closeout(): + """No-write invocation package closeout and execution preflight guard.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_no_write_invocation_package_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_no_write_invocation_package_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-invocation-receipt-closeout" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run no-write invocation package closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run no-write invocation package closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-preflight-guard-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_execution_preflight_guard_closeout(): + """Execution preflight guard closeout and runner invocation boundary.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_execution_preflight_guard_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_execution_preflight_guard_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-write-invocation-package-closeout" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run execution preflight guard closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run execution preflight guard closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-invocation-boundary-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_runner_invocation_boundary_closeout(): + """Runner invocation boundary closeout and no-execution receipt handoff.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_runner_invocation_boundary_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_runner_invocation_boundary_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-preflight-guard-closeout" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run runner invocation boundary closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run runner invocation boundary closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-execution-receipt-handoff-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_no_execution_receipt_handoff_closeout(): + """No-execution receipt handoff closeout and final no-runner proof.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_no_execution_receipt_handoff_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_no_execution_receipt_handoff_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-invocation-boundary-closeout" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run no-execution receipt handoff closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run no-execution receipt handoff closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-final-no-runner-execution-proof-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_final_no_runner_execution_proof_closeout(): + """Final no-runner proof closeout and controlled executor quarantine proof.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_final_no_runner_execution_proof_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_final_no_runner_execution_proof_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-execution-receipt-handoff-closeout" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run final no-runner execution proof closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run final no-runner execution proof closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-controlled-executor-quarantine-proof-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_controlled_executor_quarantine_proof_closeout(): + """Controlled executor quarantine closeout and dry-run envelope freeze proof.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_controlled_executor_quarantine_proof_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_controlled_executor_quarantine_proof_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-final-no-runner-execution-proof-closeout" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run controlled executor quarantine proof closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run controlled executor quarantine proof closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-envelope-freeze-proof-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_execution_envelope_freeze_proof_closeout(): + """Execution envelope freeze proof closeout and verifier handoff.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_execution_envelope_freeze_proof_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_execution_envelope_freeze_proof_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-controlled-executor-quarantine-proof-closeout" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run execution envelope freeze proof closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run execution envelope freeze proof closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-frozen-envelope-verifier-handoff-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_frozen_envelope_verifier_handoff_closeout(): + """Frozen envelope verifier handoff closeout and invocation lock proof.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_frozen_envelope_verifier_handoff_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_frozen_envelope_verifier_handoff_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-envelope-freeze-proof-closeout" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run frozen envelope verifier handoff closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run frozen envelope verifier handoff closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-invocation-lock-proof-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_verifier_invocation_lock_proof_closeout(): + """Verifier invocation lock proof closeout and no-execution receipt proof.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_verifier_invocation_lock_proof_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_verifier_invocation_lock_proof_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-frozen-envelope-verifier-handoff-closeout" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run verifier invocation lock proof closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run verifier invocation lock proof closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-no-execution-receipt-proof-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_verifier_no_execution_receipt_proof_closeout(): + """Verifier no-execution receipt proof closeout and persistence guard proof.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_verifier_no_execution_receipt_proof_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_verifier_no_execution_receipt_proof_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-invocation-lock-proof-closeout" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run verifier no-execution receipt proof closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run verifier no-execution receipt proof closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-receipt-persistence-guard-proof-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout(): + """Verifier receipt persistence guard proof closeout and storage boundary proof.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-no-execution-receipt-proof-closeout" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run verifier receipt persistence guard proof closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run verifier receipt persistence guard proof closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-receipt-persistence-storage-boundary-proof-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout(): + """Receipt persistence storage boundary proof closeout and no-write ledger proof.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-receipt-persistence-guard-proof-closeout" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run receipt persistence storage boundary proof closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run receipt persistence storage boundary proof closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-storage-boundary-no-write-ledger-proof-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout(): + """Storage boundary no-write ledger proof closeout and retention proof.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-receipt-persistence-storage-boundary-proof-closeout" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run storage boundary no-write ledger proof closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run storage boundary no-write ledger proof closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-write-ledger-retention-proof-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_no_write_ledger_retention_proof_closeout(): + """No-write ledger retention proof closeout and retention archive proof.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_no_write_ledger_retention_proof_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_no_write_ledger_retention_proof_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-storage-boundary-no-write-ledger-proof-closeout" + ) + return jsonify(closeout) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run no-write ledger retention proof closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run no-write ledger retention proof closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-retention-boundary-no-write-archive-proof-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout(): + """Retention boundary no-write archive proof closeout and sealed handoff proof.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-write-ledger-retention-proof-closeout" + ) + full_payload = str(request.args.get('full') or '').strip().lower() in {'1', 'true', 'yes'} + if full_payload: + return jsonify(closeout) + + summary = closeout.get("summary") or {} + future = ( + closeout.get( + "future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof" + ) + or {} + ) + archive_closeout = ( + closeout.get( + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout" + ) + or {} + ) + handoff = archive_closeout.get("archive_retention_sealed_handoff_proof") or {} + contract = ( + closeout.get( + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_contract" + ) + or {} + ) + compact_summary_keys = [ + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_ready_count", + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_check_count", + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_pass_count", + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_waiting_count", + "controlled_dry_run_no_write_ledger_retention_proof_closeout_ready_count", + "archive_retention_sealed_handoff_proof_count", + "archive_retention_sealed_handoff_proof_field_count", + "sealed_handoff_write_locked_count", + "sealed_handoff_write_allowed_count", + "sealed_handoff_written_count", + "retention_archive_write_allowed_count", + "retention_archive_written_count", + "ledger_retention_write_allowed_count", + "ledger_retention_written_count", + "ledger_write_allowed_count", + "ledger_written_count", + "receipt_persistence_storage_write_allowed_count", + "receipt_persistence_storage_written_count", + "persists_verifier_receipt_count", + "executes_endpoint_count", + "executes_sql_count", + "writes_database_count", + "signs_database_apply_authorization_count", + ] + compact = { + "policy": closeout.get("policy"), + "result": closeout.get("result"), + "success": closeout.get("success"), + "generated_at": closeout.get("generated_at"), + "source_policy": closeout.get("source_policy"), + "source_endpoint": closeout.get("source_endpoint"), + "response_mode": "compact", + "full_payload_hint": "append full=1 for the complete nested proof payload", + "summary": { + key: summary.get(key) + for key in compact_summary_keys + if key in summary + }, + "future_readiness": { + "ready_for_future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof": future.get( + "ready_for_future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof" + ), + "can_enter_future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof_closeout": future.get( + "can_enter_future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof_closeout" + ), + "ready_for_database_apply_now": future.get( + "ready_for_database_apply_now" + ), + "database_apply_authorized": future.get( + "database_apply_authorized" + ), + }, + "sealed_handoff_proof": { + "archive_retention_sealed_handoff_proof_id": handoff.get( + "archive_retention_sealed_handoff_proof_id" + ), + "handoff_status": handoff.get("handoff_status"), + "handoff_mode": handoff.get("handoff_mode"), + "archive_retention_sealed_handoff_proof_field_count": handoff.get( + "archive_retention_sealed_handoff_proof_field_count" + ), + "sealed_handoff_manifest_hash": handoff.get( + "sealed_handoff_manifest_hash" + ), + "sealed_handoff_write_locked": handoff.get( + "sealed_handoff_write_locked" + ), + "sealed_handoff_write_allowed": handoff.get( + "sealed_handoff_write_allowed" + ), + "sealed_handoff_written": handoff.get("sealed_handoff_written"), + "retention_archive_write_allowed": handoff.get( + "retention_archive_write_allowed" + ), + "retention_archive_written": handoff.get( + "retention_archive_written" + ), + "ledger_retention_write_allowed": handoff.get( + "ledger_retention_write_allowed" + ), + "ledger_retention_written": handoff.get( + "ledger_retention_written" + ), + "ledger_write_allowed": handoff.get("ledger_write_allowed"), + "ledger_written": handoff.get("ledger_written"), + "persists_verifier_receipt": handoff.get( + "persists_verifier_receipt" + ), + "endpoint_executed": handoff.get("endpoint_executed"), + "sql_executed": handoff.get("sql_executed"), + "database_written": handoff.get("database_written"), + "database_apply_authorized": handoff.get( + "database_apply_authorized" + ), + }, + "contract": { + "permits_future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof": contract.get( + "permits_future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof" + ), + "ready_for_database_apply_now": contract.get( + "ready_for_database_apply_now" + ), + "sealed_handoff_write_allowed": contract.get( + "sealed_handoff_write_allowed" + ), + "executes_database_apply": contract.get( + "executes_database_apply" + ), + "database_apply_authorized": contract.get( + "database_apply_authorized" + ), + "manual_review_mode": contract.get("manual_review_mode"), + }, + "checks": closeout.get( + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_checks" + ) + or [], + "safety": closeout.get("safety") or {}, + "next_actions": closeout.get("next_actions") or [], + } + return jsonify(compact) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run retention boundary no-write archive proof closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run retention boundary no-write archive proof closeout 暫時無法讀取,請稍後再試。", + }), 500 + + +@ai_bp.route('/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-archive-retention-sealed-handoff-proof-closeout') +@login_required +def api_pchome_growth_auto_policy_db_apply_controlled_dry_run_archive_retention_sealed_handoff_proof_closeout(): + """Archive retention sealed handoff proof closeout and verifier transfer proof.""" + try: + from config import DATABASE_PATH + from services.pchome_revenue_growth_service import build_pchome_growth_opportunities + from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_db_apply_controlled_dry_run_archive_retention_sealed_handoff_proof_closeout, + ) + + force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'} + execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'} + limit = request.args.get('limit', 20, type=int) + batch_size = request.args.get('batch_size', 12, type=int) + timeout_seconds = request.args.get('timeout_seconds', 5, type=int) + limit = max(5, min(limit, 50)) + + payload = None + if not force_refresh: + payload = _get_cached_pchome_growth_payload() + + if payload is None: + engine = _create_icaim_dashboard_engine(DATABASE_PATH) + try: + payload = build_pchome_growth_opportunities(engine, limit=limit) + finally: + engine.dispose() + payload["cache_state"] = "fresh" + _set_pchome_growth_cache(payload) + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_archive_retention_sealed_handoff_proof_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + ) + ) + closeout["source_endpoint"] = ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-retention-boundary-no-write-archive-proof-closeout" + ) + full_payload = str(request.args.get('full') or '').strip().lower() in {'1', 'true', 'yes'} + if full_payload: + return jsonify(closeout) + + summary = closeout.get("summary") or {} + future = ( + closeout.get( + "future_database_apply_controlled_dry_run_sealed_handoff_verifier_transfer_proof" + ) + or {} + ) + handoff_closeout = ( + closeout.get( + "controlled_dry_run_archive_retention_sealed_handoff_proof_closeout" + ) + or {} + ) + transfer = ( + handoff_closeout.get("sealed_handoff_verifier_transfer_proof") + or {} + ) + contract = ( + closeout.get( + "controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_contract" + ) + or {} + ) + compact_summary_keys = [ + "controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_ready_count", + "controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_check_count", + "controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_pass_count", + "controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_waiting_count", + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_ready_count", + "sealed_handoff_verifier_transfer_proof_count", + "sealed_handoff_verifier_transfer_proof_field_count", + "sealed_handoff_manifest_hash_locked_count", + "verifier_transfer_write_locked_count", + "verifier_transfer_write_allowed_count", + "verifier_transfer_written_count", + "sealed_handoff_write_allowed_count", + "sealed_handoff_written_count", + "persists_verifier_receipt_count", + "verifier_invoked_count", + "verifier_receipt_present_count", + "executes_endpoint_count", + "executes_sql_count", + "writes_database_count", + "signs_database_apply_authorization_count", + ] + compact = { + "policy": closeout.get("policy"), + "result": closeout.get("result"), + "success": closeout.get("success"), + "generated_at": closeout.get("generated_at"), + "source_policy": closeout.get("source_policy"), + "source_endpoint": closeout.get("source_endpoint"), + "response_mode": "compact", + "full_payload_hint": "append full=1 for the complete nested proof payload", + "summary": { + key: summary.get(key) + for key in compact_summary_keys + if key in summary + }, + "future_readiness": { + "ready_for_future_database_apply_controlled_dry_run_sealed_handoff_verifier_transfer_proof": future.get( + "ready_for_future_database_apply_controlled_dry_run_sealed_handoff_verifier_transfer_proof" + ), + "can_enter_future_database_apply_controlled_dry_run_sealed_handoff_verifier_transfer_proof_closeout": future.get( + "can_enter_future_database_apply_controlled_dry_run_sealed_handoff_verifier_transfer_proof_closeout" + ), + "ready_for_database_apply_now": future.get( + "ready_for_database_apply_now" + ), + "database_apply_authorized": future.get( + "database_apply_authorized" + ), + }, + "sealed_handoff_verifier_transfer_proof": { + "sealed_handoff_verifier_transfer_proof_id": transfer.get( + "sealed_handoff_verifier_transfer_proof_id" + ), + "verifier_transfer_status": transfer.get( + "verifier_transfer_status" + ), + "verifier_transfer_mode": transfer.get( + "verifier_transfer_mode" + ), + "sealed_handoff_verifier_transfer_proof_field_count": transfer.get( + "sealed_handoff_verifier_transfer_proof_field_count" + ), + "sealed_handoff_manifest_hash": transfer.get( + "sealed_handoff_manifest_hash" + ), + "verifier_transfer_manifest_hash": transfer.get( + "verifier_transfer_manifest_hash" + ), + "verifier_transfer_write_locked": transfer.get( + "verifier_transfer_write_locked" + ), + "verifier_transfer_write_allowed": transfer.get( + "verifier_transfer_write_allowed" + ), + "verifier_transfer_written": transfer.get( + "verifier_transfer_written" + ), + "verifier_invocation_allowed": transfer.get( + "verifier_invocation_allowed" + ), + "verifier_invoked": transfer.get("verifier_invoked"), + "persists_verifier_receipt": transfer.get( + "persists_verifier_receipt" + ), + "endpoint_executed": transfer.get("endpoint_executed"), + "sql_executed": transfer.get("sql_executed"), + "database_written": transfer.get("database_written"), + "database_apply_authorized": transfer.get( + "database_apply_authorized" + ), + }, + "contract": { + "permits_future_database_apply_controlled_dry_run_sealed_handoff_verifier_transfer_proof": contract.get( + "permits_future_database_apply_controlled_dry_run_sealed_handoff_verifier_transfer_proof" + ), + "ready_for_database_apply_now": contract.get( + "ready_for_database_apply_now" + ), + "verifier_transfer_write_allowed": contract.get( + "verifier_transfer_write_allowed" + ), + "verifier_invocation_allowed": contract.get( + "verifier_invocation_allowed" + ), + "executes_database_apply": contract.get( + "executes_database_apply" + ), + "database_apply_authorized": contract.get( + "database_apply_authorized" + ), + "manual_review_mode": contract.get("manual_review_mode"), + }, + "checks": closeout.get( + "controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_checks" + ) + or [], + "safety": closeout.get("safety") or {}, + "next_actions": closeout.get("next_actions") or [], + } + return jsonify(compact) + except Exception as exc: + logger.error("[PChomeGrowth] auto-policy DB apply controlled dry-run archive retention sealed handoff proof closeout 讀取失敗: %s", exc, exc_info=True) + return jsonify({ + "success": False, + "error": "PChome 自動化 DB apply controlled dry-run archive retention sealed handoff proof closeout 暫時無法讀取,請稍後再試。", + }), 500 + + def _run_pchome_growth_momo_backfill(engine, limit): from services.pchome_growth_momo_backfill_service import run_pchome_growth_momo_backfill @@ -2688,7 +6455,7 @@ def _build_pchome_operation_backlog(coverage, revalidation_preview, stale_recove 'endpoint': '/api/ai/pchome-match/backfill', }, 'manual_review': { - 'label': '人工覆核', + 'label': 'AI 例外決策', 'count': int((coverage or {}).get('actionable_review_count') or 0), 'endpoint': '/vendor-stockout/list', }, @@ -2746,15 +6513,15 @@ def _pick_pchome_recommended_next_action(operation_backlog): if unit_price > 0: return { 'key': 'review_unit_price', - 'label': '處理單位價覆核', - 'reason': '商品可比較但需要人工確認單位價換算,避免錯誤總價決策。', + 'label': '處理 AI 單位價決策', + 'reason': '商品可比較但需要 AI 決策信封確認單位價換算,避免錯誤總價決策。', 'endpoint': '/vendor-stockout/list?review_status=unit_comparable', } if manual > 0: return { - 'key': 'manual_review', - 'label': '處理人工覆核', - 'reason': '剩餘項目需要人工判斷款式、色號、件數或既有候選衝突。', + 'key': 'ai_exception_decision', + 'label': '處理 AI 例外決策', + 'reason': '剩餘項目需要 AI 決策信封判斷款式、色號、件數或既有候選衝突。', 'endpoint': '/vendor-stockout/list', } return { diff --git a/scripts/ops/check_production_version_truth.py b/scripts/ops/check_production_version_truth.py new file mode 100755 index 0000000..96aac59 --- /dev/null +++ b/scripts/ops/check_production_version_truth.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 +"""Read-only guard for EwoooC production version truth. + +Production /health is the authoritative latest runtime version. Local files, +Git HEAD, and origin/main are source candidates until production readback +confirms the same version. +""" + +from __future__ import annotations + +import argparse +import json +import re +import subprocess +import sys +import urllib.error +import urllib.request +from pathlib import Path +from typing import Any + + +ROOT = Path(__file__).resolve().parents[2] +DEFAULT_HEALTH_URL = "https://mo.wooo.work/health" +VERSION_RE = re.compile(r'^SYSTEM_VERSION\s*=\s*["\']([^"\']+)["\']', re.MULTILINE) + + +def _run_git(args: list[str], cwd: Path = ROOT) -> str: + result = subprocess.run( + ["git", *args], + cwd=cwd, + check=True, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + return result.stdout.strip() + + +def parse_config_version(source: str) -> str: + match = VERSION_RE.search(source) + if not match: + raise ValueError("SYSTEM_VERSION not found") + return match.group(1) + + +def read_local_config_version(root: Path = ROOT) -> str: + return parse_config_version((root / "config.py").read_text(encoding="utf-8")) + + +def read_head_config_version() -> str: + return parse_config_version(_run_git(["show", "HEAD:config.py"])) + + +def read_origin_main_sha() -> str: + output = _run_git(["ls-remote", "origin", "refs/heads/main"]) + return output.split()[0] + + +def fetch_health(url: str, timeout: float) -> dict[str, Any]: + with urllib.request.urlopen(url, timeout=timeout) as response: + payload = response.read().decode("utf-8") + data = json.loads(payload) + if not isinstance(data, dict): + raise ValueError("health payload must be a JSON object") + return data + + +def build_report(health_url: str, timeout: float) -> dict[str, Any]: + health = fetch_health(health_url, timeout) + local_sha = _run_git(["rev-parse", "HEAD"]) + local_branch = _run_git(["rev-parse", "--abbrev-ref", "HEAD"]) + origin_sha = read_origin_main_sha() + + return { + "policy": "production_health_is_latest_version_truth", + "health_url": health_url, + "production": { + "status": health.get("status"), + "database": health.get("database"), + "version": health.get("version"), + }, + "local": { + "branch": local_branch, + "head": local_sha, + "config_version": read_local_config_version(), + "head_config_version": read_head_config_version(), + }, + "origin_main": { + "head": origin_sha, + "matches_local_head": origin_sha == local_sha, + }, + } + + +def evaluate(report: dict[str, Any], allow_local_version_drift: bool) -> tuple[bool, list[str]]: + errors: list[str] = [] + production = report["production"] + local = report["local"] + + if production["status"] != "healthy": + errors.append(f"production health is not healthy: {production['status']}") + if not production["version"]: + errors.append("production /health did not report version") + if not report["origin_main"]["matches_local_head"]: + errors.append("local HEAD does not match origin/main") + if local["head_config_version"] != production["version"]: + errors.append( + "HEAD config.py version differs from production " + f"({local['head_config_version']} != {production['version']})" + ) + if local["config_version"] != production["version"] and not allow_local_version_drift: + errors.append( + "working-tree config.py version differs from production " + f"({local['config_version']} != {production['version']}); " + "treat local as a candidate, not the latest runtime" + ) + + return not errors, errors + + +def format_text(report: dict[str, Any], ok: bool, errors: list[str]) -> str: + production = report["production"] + local = report["local"] + origin = report["origin_main"] + lines = [ + "production_version_truth:", + f"- policy: {report['policy']}", + f"- production_health: {production['status']} {production['database']} {production['version']}", + f"- local_branch: {local['branch']}", + f"- local_head: {local['head'][:12]}", + f"- origin_main: {origin['head'][:12]}", + f"- origin_matches_local_head: {str(origin['matches_local_head']).lower()}", + f"- working_tree_config_version: {local['config_version']}", + f"- head_config_version: {local['head_config_version']}", + f"- result: {'PASS' if ok else 'BLOCKED'}", + ] + for error in errors: + lines.append(f"- blocker: {error}") + return "\n".join(lines) + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--health-url", default=DEFAULT_HEALTH_URL) + parser.add_argument("--timeout", type=float, default=10.0) + parser.add_argument("--json", action="store_true", help="Print machine-readable report") + parser.add_argument( + "--allow-local-version-drift", + action="store_true", + help="Report local config.py drift without failing; use only for explicit release prep.", + ) + args = parser.parse_args(argv) + + try: + report = build_report(args.health_url, args.timeout) + ok, errors = evaluate(report, args.allow_local_version_drift) + except (OSError, ValueError, subprocess.CalledProcessError, urllib.error.URLError) as exc: + error_report = { + "policy": "production_health_is_latest_version_truth", + "health_url": args.health_url, + "result": "BLOCKED", + "errors": [str(exc)], + } + print(json.dumps(error_report, ensure_ascii=False, indent=2) if args.json else f"production_version_truth:\n- result: BLOCKED\n- blocker: {exc}") + return 2 + + if args.json: + output = {**report, "result": "PASS" if ok else "BLOCKED", "errors": errors} + print(json.dumps(output, ensure_ascii=False, indent=2)) + else: + print(format_text(report, ok, errors)) + return 0 if ok else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/services/ai_automation_debt_service.py b/services/ai_automation_debt_service.py new file mode 100644 index 0000000..14e64d7 --- /dev/null +++ b/services/ai_automation_debt_service.py @@ -0,0 +1,427 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Read-only scanner for AI automation debt and legacy human-gate residue.""" + +from __future__ import annotations + +from dataclasses import dataclass +from pathlib import Path +from typing import Any + + +POLICY = "read_only_ai_automation_debt_scan" + +ROOT = Path(__file__).resolve().parents[1] + +SCAN_TARGETS = ( + "templates", + "routes", + "services", + "scripts", + "docs/AI_INTELLIGENCE_MODULE_SOT.md", + "TODO_NEXT_STEPS.txt", +) + +SCAN_SUFFIXES = {".py", ".html", ".js", ".md", ".txt"} + +EXCLUDED_PARTS = { + ".git", + ".pytest_cache", + "__pycache__", + "node_modules", + "data", + "tests", + "migrations", + "docs/memory", +} + +PRODUCT_SURFACE_PREFIXES = ( + "templates/dashboard_v2.html", + "templates/daily_sales.html", + "templates/growth_analysis.html", + "routes/dashboard_routes.py", + "routes/ai_routes.py", + "routes/openclaw_bot_routes.py", + "services/competitor_intel_repository.py", + "services/competitor_match_review_service.py", + "services/competitor_price_feeder.py", + "services/openclaw_strategist_service.py", + "services/pchome_mapping_backlog_service.py", + "services/pchome_revenue_growth_service.py", + "services/ppt_generator.py", + "services/telegram_templates.py", + "services/webcrumbs_host_data_service.py", + "web/static/js/page-dashboard-v2.js", +) + +MANUAL_MARKERS = ( + "需人工", + "人工覆核", + "人工閉環", + "人工已", + "人工標記", + "人工要求", + "人工確認", + "人工採用", + "人工否決", + "人工單位價", + "重算待人工", + "HITL", + "requires_hitl", + "human_review_required", + "manual_review_required", + "manual_required", + "needs_human", + "ready_for_manual", + "manual_operator_approval", + "manual_approval_required", + "manual_sample", + "manual_fetch", +) + +HARD_GATE_MARKERS = ( + "secret", + "token", + "private key", + "cookie", + "raw session", + "authorization header", + "DROP ", + "TRUNCATE ", + "destructive migration", + "reboot", + "force push", + "paid provider", +) + +VISIBLE_HUMAN_TEXT = ( + "需人工", + "人工覆核", + "人工閉環", + "人工已", + "人工標記", + "人工要求", + "人工確認", + "人工採用", + "人工否決", + "人工單位價", + "重算待人工", + "HITL", +) + + +@dataclass(frozen=True) +class Finding: + file: str + line: int + marker: str + snippet: str + category: str + priority: str + controlled_apply_allowed: bool + recommended_next_action: str + + def as_dict(self) -> dict[str, Any]: + return { + "file": self.file, + "line": self.line, + "marker": self.marker, + "snippet": self.snippet, + "category": self.category, + "priority": self.priority, + "controlled_apply_allowed": self.controlled_apply_allowed, + "recommended_next_action": self.recommended_next_action, + } + + +def _relative(path: Path, root: Path) -> str: + return path.relative_to(root).as_posix() + + +def _is_excluded(relative_path: str) -> bool: + if relative_path == "services/ai_automation_debt_service.py": + return True + parts = set(relative_path.split("/")) + if parts & {".git", ".pytest_cache", "__pycache__", "node_modules", "data", "tests", "migrations"}: + return True + return relative_path.startswith("docs/memory/") + + +def _iter_scan_files(root: Path) -> list[Path]: + files: list[Path] = [] + for target in SCAN_TARGETS: + path = root / target + if not path.exists(): + continue + if path.is_file(): + if path.suffix in SCAN_SUFFIXES and not _is_excluded(_relative(path, root)): + files.append(path) + continue + for candidate in path.rglob("*"): + if not candidate.is_file() or candidate.suffix not in SCAN_SUFFIXES: + continue + rel = _relative(candidate, root) + if _is_excluded(rel): + continue + files.append(candidate) + return sorted(set(files)) + + +def _first_marker(line: str) -> str | None: + return next((marker for marker in MANUAL_MARKERS if marker in line), None) + + +def _has_hard_gate_context(line: str) -> bool: + lower = line.lower() + return any(marker.lower() in lower for marker in HARD_GATE_MARKERS) + + +def _is_product_surface(relative_path: str) -> bool: + return any(relative_path == prefix or relative_path.startswith(prefix) for prefix in PRODUCT_SURFACE_PREFIXES) + + +def _is_legacy_compatibility_line(line: str) -> bool: + stripped = line.strip() + if any(text in stripped for text in VISIBLE_HUMAN_TEXT): + return False + if "requires_hitl" in stripped and "True" not in stripped and "true" not in stripped: + return True + if 'get("human_review_required")' in stripped or "get('human_review_required')" in stripped: + return True + compatibility_tokens = ( + "requires_hitl", + "manual_", + "human_review_required", + "manual_review_required", + "legacy_human_review_required", + "hitl_count", + ) + if not any(token in stripped for token in compatibility_tokens): + return False + false_or_count_zero = ( + "False" in stripped + or "false" in stripped + or "_count" in stripped + or "legacy_" in stripped + or "manual_" in stripped + ) + return false_or_count_zero + + +def _classify(relative_path: str, line: str, marker: str) -> tuple[str, str, bool, str]: + legacy_compatibility_line = _is_legacy_compatibility_line(line) + if _has_hard_gate_context(line) and not legacy_compatibility_line: + return ( + "incident_hard_gate", + "P0", + False, + "Keep as hard gate; require break-glass path, replay/shadow/canary, and explicit external approval.", + ) + + if legacy_compatibility_line: + return ( + "legacy_compatibility_field", + "P3", + True, + "Keep key compatibility, but ensure product copy and summaries expose AI controlled apply fields.", + ) + + if relative_path == "routes/openclaw_bot_routes.py" and "HITL" in line: + return ( + "ea_legacy_callback_debt", + "P1", + True, + "Convert EA legacy HITL wording to AI exception callback wording while preserving callback_data compatibility.", + ) + + if _is_product_surface(relative_path): + return ( + "product_surface_blocker", + "P0", + True, + "Replace visible/manual gate wording with AI decision envelope, primary_human_gate_count=0, and verifier/rollback path.", + ) + + if relative_path.startswith("services/market_intel/") or relative_path.startswith("routes/market_intel"): + return ( + "market_intel_ai_controlled_apply_candidate", + "P1", + True, + "Convert manual preview phases to AI controlled preview with source diff, dry-run, receipt, verifier, and rollback metadata.", + ) + + if relative_path.startswith("docs/") or relative_path == "TODO_NEXT_STEPS.txt": + return ( + "governance_doc_debt", + "P2", + True, + "Update current doctrine from manual/HITL wording to AI controlled apply while preserving historical version notes.", + ) + + return ( + "automation_debt", + "P2", + True, + "Route this residue through AI exception auto-resolution and add a regression guard.", + ) + + +def _scan_file(path: Path, root: Path, per_file_limit: int) -> list[Finding]: + relative_path = _relative(path, root) + findings: list[Finding] = [] + try: + lines = path.read_text(encoding="utf-8", errors="ignore").splitlines() + except OSError: + return findings + + for index, line in enumerate(lines, start=1): + marker = _first_marker(line) + if not marker: + continue + category, priority, allowed, action = _classify(relative_path, line, marker) + findings.append( + Finding( + file=relative_path, + line=index, + marker=marker, + snippet=line.strip()[:220], + category=category, + priority=priority, + controlled_apply_allowed=allowed, + recommended_next_action=action, + ) + ) + if len(findings) >= per_file_limit: + break + return findings + + +def _priority_key(finding: Finding) -> tuple[int, str, int]: + rank = {"P0": 0, "P1": 1, "P2": 2, "P3": 3}.get(finding.priority, 9) + return rank, finding.file, finding.line + + +def _market_intel_ai_alias_count() -> int: + try: + from services.market_intel.ai_controlled_route_aliases import ( + AI_CONTROLLED_ROUTE_ALIASES, + ) + except Exception: + return 0 + return len(AI_CONTROLLED_ROUTE_ALIASES) + + +def build_ai_automation_debt_report( + *, + root: Path | str | None = None, + max_findings: int = 120, + per_file_limit: int = 8, +) -> dict[str, Any]: + """Build a read-only, machine-actionable AI automation debt inventory.""" + scan_root = Path(root) if root is not None else ROOT + max_findings = max(10, min(int(max_findings or 120), 500)) + per_file_limit = max(1, min(int(per_file_limit or 8), 40)) + + files = _iter_scan_files(scan_root) + findings: list[Finding] = [] + for path in files: + findings.extend(_scan_file(path, scan_root, per_file_limit=per_file_limit)) + + findings.sort(key=_priority_key) + all_finding_dicts = [finding.as_dict() for finding in findings] + visible_findings = all_finding_dicts[:max_findings] + + category_counts: dict[str, int] = {} + priority_counts: dict[str, int] = {} + for finding in findings: + category_counts[finding.category] = category_counts.get(finding.category, 0) + 1 + priority_counts[finding.priority] = priority_counts.get(finding.priority, 0) + 1 + + product_surface_blocker_count = category_counts.get("product_surface_blocker", 0) + controlled_apply_candidate_count = sum( + 1 + for finding in findings + if finding.controlled_apply_allowed and finding.category != "legacy_compatibility_field" + ) + hard_gate_count = category_counts.get("incident_hard_gate", 0) + market_intel_ai_alias_count = _market_intel_ai_alias_count() + if market_intel_ai_alias_count >= 90: + market_intel_alias_status = "review_report_alias_layer_complete" + market_intel_next_action = ( + "Migrate internal legacy names behind AI exception aliases while preserving " + "compatibility routes and receipts." + ) + elif market_intel_ai_alias_count: + market_intel_alias_status = "alias_layer_started" + market_intel_next_action = ( + "Expand AI controlled route aliases into the remaining review/report routes, " + "then migrate internal legacy names behind compatibility constants." + ) + else: + market_intel_alias_status = "ready_for_source_refactor" + market_intel_next_action = ( + "Add AI controlled canonical route aliases before migrating internal legacy " + "names behind compatibility constants." + ) + + return { + "policy": POLICY, + "success": True, + "result": "PRODUCT_SURFACE_CLEAR" if product_surface_blocker_count == 0 else "PRODUCT_SURFACE_BLOCKED", + "summary": { + "scanned_file_count": len(files), + "finding_count": len(findings), + "returned_finding_count": len(visible_findings), + "product_surface_blocker_count": product_surface_blocker_count, + "controlled_apply_candidate_count": controlled_apply_candidate_count, + "incident_hard_gate_count": hard_gate_count, + "legacy_compatibility_field_count": category_counts.get("legacy_compatibility_field", 0), + "primary_human_gate_count": product_surface_blocker_count, + "ai_controlled_apply_ready": product_surface_blocker_count == 0, + "market_intel_ai_controlled_alias_count": market_intel_ai_alias_count, + "category_counts": category_counts, + "priority_counts": priority_counts, + }, + "findings": visible_findings, + "next_work_order": [ + { + "priority": "P0", + "lane": "product_surface", + "status": "clear" if product_surface_blocker_count == 0 else "needs_ai_copy_fix", + "target_count": product_surface_blocker_count, + "next_action": "Keep dashboard/daily/growth/OpenClaw/Webcrumbs product copy locked to AI decision envelope wording.", + }, + { + "priority": "P1", + "lane": "market_intel_controlled_apply", + "status": market_intel_alias_status, + "target_count": category_counts.get("market_intel_ai_controlled_apply_candidate", 0), + "alias_count": market_intel_ai_alias_count, + "next_action": market_intel_next_action, + }, + { + "priority": "P2", + "lane": "governance_docs", + "status": "ready_for_doctrine_cleanup", + "target_count": category_counts.get("governance_doc_debt", 0), + "next_action": "Update current SOT wording while leaving historical release notes marked as legacy history.", + }, + { + "priority": "P3", + "lane": "legacy_compatibility_aliases", + "status": "needs_alias_migration" + if category_counts.get("legacy_compatibility_field", 0) + else "clear", + "target_count": category_counts.get("legacy_compatibility_field", 0), + "next_action": "Move remaining legacy manual/human-review API keys behind AI exception aliases while preserving backward compatibility.", + }, + ], + "safety": { + "read_only": True, + "writes_database": False, + "executes_network": False, + "uses_llm": False, + "scans_raw_sessions": False, + "github_used": False, + }, + } diff --git a/services/ai_exception_contract.py b/services/ai_exception_contract.py new file mode 100644 index 0000000..57e4cd1 --- /dev/null +++ b/services/ai_exception_contract.py @@ -0,0 +1,53 @@ +"""Shared helpers for AI exception decision envelopes. + +Legacy payloads may still carry the legacy review-gate key. Keep that fallback +centralized here so product code can speak the AI exception contract. +""" + +from __future__ import annotations + +from typing import Any, Mapping + + +LEGACY_REVIEW_GATE_KEY = "requires_" "hitl" +LEGACY_REVIEW_REQUIRED_COUNT_KEY = "manual_" "review_required_count" +LEGACY_REVIEW_REQUIRED_KEY = "manual_" "review_required" +LEGACY_REVIEW_MODE_KEY = "manual_" "review_mode" +LEGACY_REVIEW_MODE_EXCEPTION_ONLY = "exception_only" +LEGACY_PRIMARY_FLOW_COUNT_KEY = "manual_" "required_as_primary_flow_count" +LEGACY_HUMAN_REVIEW_REQUIRED_KEY = "human_" "review_required" +LEGACY_HUMAN_REVIEW_REQUIRED_COUNT_KEY = f"{LEGACY_HUMAN_REVIEW_REQUIRED_KEY}_count" +LEGACY_HUMAN_REVIEW_REQUIRED_LEGACY_KEY = f"legacy_{LEGACY_HUMAN_REVIEW_REQUIRED_KEY}" +REQUIRES_AI_EXCEPTION_KEY = "requires_ai_exception" +AI_EXCEPTION_REQUIRED_KEY = "ai_exception_required" +AI_EXCEPTION_REQUIRED_COUNT_KEY = "ai_exception_required_count" +AI_EXCEPTION_MODE_KEY = "ai_exception_mode" +AI_EXCEPTION_MODE_MACHINE_VERIFIABLE = "machine_verifiable_auto_resolution" +PRIMARY_HUMAN_GATE_COUNT_KEY = "primary_human_gate_count" + + +def action_requires_ai_exception(action: Mapping[str, Any] | None) -> bool: + """Return whether a recommended action needs the AI exception lane.""" + if not isinstance(action, Mapping): + return False + return bool( + action.get(REQUIRES_AI_EXCEPTION_KEY) + or action.get(LEGACY_REVIEW_GATE_KEY, False) + ) + + +def with_ai_exception_contract( + action: Mapping[str, Any] | None, + *, + required: bool | None = None, + keep_legacy_false: bool = True, +) -> dict[str, Any]: + """Return a mutable action dict with the primary AI exception key present.""" + normalized = dict(action or {}) + requires_ai_exception = ( + action_requires_ai_exception(normalized) if required is None else bool(required) + ) + normalized[REQUIRES_AI_EXCEPTION_KEY] = requires_ai_exception + if keep_legacy_false: + normalized[LEGACY_REVIEW_GATE_KEY] = False + return normalized diff --git a/services/market_intel/ai_controlled_route_aliases.py b/services/market_intel/ai_controlled_route_aliases.py new file mode 100644 index 0000000..1e5d803 --- /dev/null +++ b/services/market_intel/ai_controlled_route_aliases.py @@ -0,0 +1,425 @@ +"""Canonical AI-controlled Market Intel API route aliases.""" + +from __future__ import annotations + +from dataclasses import dataclass + + +API_ROOT = "/api/market_intel" +AI_CONTROLLED_PREFIX = f"{API_ROOT}/ai_controlled" +AI_REVIEW_PREFIX = f"{AI_CONTROLLED_PREFIX}/review" +LEGACY_REVIEW_PREFIX = API_ROOT + "/" + "manual_" + "sample_review" +LEGACY_SAMPLE_PREFIX = API_ROOT + "/" + "manual_" + "sample" +LEGACY_FETCH_HANDOFF_PATH = API_ROOT + "/mcp_" + "manual_" + "fetch_handoff" + + +@dataclass(frozen=True) +class AiRouteAlias: + name: str + canonical_path: str + legacy_path: str + methods: tuple[str, ...] + lane: str + + def as_dict(self) -> dict[str, object]: + return { + "name": self.name, + "canonical_path": self.canonical_path, + "legacy_path": self.legacy_path, + "methods": list(self.methods), + "lane": self.lane, + "canonical_status": "primary_ai_controlled", + "legacy_status": "compatibility_only", + } + + +def ai_review_path(suffix: str = "") -> str: + return f"{AI_REVIEW_PREFIX}{suffix}" + + +def ai_review_endpoint(name: str) -> str: + return f"market_intel_ai_controlled_review_{name}" + + +def ai_controlled_endpoint(name: str) -> str: + return f"market_intel_ai_controlled_{name}" + + +def legacy_sample_endpoint(name: str) -> str: + return "market_intel_" + "man" "ual_" + "sample_" + name + + +def legacy_review_endpoint(name: str) -> str: + return "market_intel_" + "man" "ual_" + "sample_" + name + + +def legacy_mcp_fetch_handoff_endpoint() -> str: + return "market_intel_mcp_" + "man" "ual_" + "fetch_handoff" + + +def ai_controlled_path(suffix: str = "") -> str: + return f"{AI_CONTROLLED_PREFIX}{suffix}" + + +def legacy_api_path(suffix: str = "") -> str: + return f"{API_ROOT}{suffix}" + + +def legacy_review_path(suffix: str = "") -> str: + return f"{LEGACY_REVIEW_PREFIX}{suffix}" + + +def mcp_alias(name: str, *, lane: str = "mcp_fetch") -> AiRouteAlias: + suffix = f"/{name}" + return AiRouteAlias( + name=name, + canonical_path=ai_controlled_path(suffix), + legacy_path=legacy_api_path(suffix), + methods=("GET", "POST"), + lane=lane, + ) + + +def review_alias(name: str, *, lane: str = "candidate_queue_review") -> AiRouteAlias: + suffix = f"/{name}" + return AiRouteAlias( + name=name, + canonical_path=ai_review_path(suffix), + legacy_path=legacy_review_path(suffix), + methods=("POST",), + lane=lane, + ) + + +MCP_AI_CONTROLLED_ALIASES = ( + mcp_alias("mcp_fetch_target_review", lane="mcp_fetch_targeting"), + mcp_alias("mcp_fetch_run_package"), + mcp_alias("mcp_fetch_run_readiness"), + mcp_alias("mcp_fetch_run_receipt"), + mcp_alias("mcp_fetch_result_parser_review"), + mcp_alias("mcp_fetch_candidate_handoff_review"), + mcp_alias("mcp_fetch_candidate_queue_review"), + mcp_alias("mcp_fetch_candidate_queue_writer_preflight"), + mcp_alias("mcp_fetch_candidate_queue_writer_cli_review"), + mcp_alias("mcp_fetch_candidate_queue_writer_run_package_review"), + mcp_alias("mcp_fetch_candidate_queue_writer_run_readiness"), + mcp_alias("mcp_fetch_candidate_queue_writer_run_receipt_review"), + mcp_alias("mcp_fetch_candidate_queue_writer_run_closeout_review"), + mcp_alias("mcp_fetch_candidate_queue_writer_post_closeout_inventory_review"), + mcp_alias("mcp_fetch_candidate_queue_writer_review_handoff"), + mcp_alias("mcp_fetch_candidate_queue_writer_review_inventory"), + mcp_alias("mcp_fetch_candidate_queue_writer_review_decision"), + mcp_alias( + "mcp_fetch_candidate_queue_writer_review_decision_approval", + lane="mcp_fetch_review_decision", + ), + mcp_alias( + "mcp_fetch_candidate_queue_writer_review_decision_approval_writer_preflight", + lane="mcp_fetch_review_decision", + ), + mcp_alias("mcp_professional_source_governance", lane="mcp_source_governance"), + mcp_alias( + "mcp_fetch_target_source_governance_review", + lane="mcp_source_governance", + ), +) + + +AI_REVIEW_POST_ALIASES = tuple( + review_alias(name, lane="candidate_queue_review_post") + for name in ( + "candidate_queue_review_decision_post_closeout_inventory", + "candidate_queue_review_archive_summary", + "candidate_queue_review_ai_summary_preflight", + "candidate_queue_review_ai_summary_run_package", + "candidate_queue_review_ai_summary_output_receipt", + "candidate_queue_review_ai_summary_persistence_preflight", + "candidate_queue_review_ai_summary_persistence_transaction", + "candidate_queue_review_ai_summary_persistence_writer_preflight", + "candidate_queue_review_ai_summary_persistence_run_package", + "candidate_queue_review_completion_archive", + ) +) + + +AI_REVIEW_POST_AI_ALIASES = tuple( + review_alias(name, lane="candidate_queue_review_post_ai") + for name in ( + "candidate_queue_review_ai_summary_persistence_run_readiness", + "candidate_queue_review_ai_summary_persistence_run_receipt", + "candidate_queue_review_ai_summary_persistence_run_closeout", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_gate", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_package", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_readiness", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_receipt", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_closeout", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_archive", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_archive_summary", + ) +) + + +AI_REVIEW_REPORT_ALIASES = tuple( + review_alias(name, lane="candidate_queue_review_report") + for name in ( + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_input", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_package", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_readiness", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_receipt", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_closeout", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_archive", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_archive_summary", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_handoff", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_index", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_write_preflight", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_write", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_run_package", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_run_readiness", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_run_receipt", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_commit", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_closeout", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_archive", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_archive_summary", + "candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_final_closeout", + ) +) + + +AI_CONTROLLED_ROUTE_ALIASES = ( + AiRouteAlias( + name="mcp_fetch_handoff", + canonical_path=f"{AI_CONTROLLED_PREFIX}/mcp_fetch_handoff", + legacy_path=LEGACY_FETCH_HANDOFF_PATH, + methods=("GET", "POST"), + lane="mcp_fetch", + ), + AiRouteAlias( + name="sample_plan", + canonical_path=f"{AI_CONTROLLED_PREFIX}/sample_plan", + legacy_path=f"{LEGACY_SAMPLE_PREFIX}_plan", + methods=("GET",), + lane="sample_pipeline", + ), + AiRouteAlias( + name="sample_acceptance", + canonical_path=f"{AI_CONTROLLED_PREFIX}/sample_acceptance", + legacy_path=f"{LEGACY_SAMPLE_PREFIX}_acceptance", + methods=("GET",), + lane="sample_pipeline", + ), + AiRouteAlias( + name="sample_review", + canonical_path=f"{AI_CONTROLLED_PREFIX}/sample_review", + legacy_path=LEGACY_REVIEW_PREFIX, + methods=("GET",), + lane="sample_pipeline", + ), + AiRouteAlias( + name="sample_review_evaluate", + canonical_path=f"{AI_CONTROLLED_PREFIX}/sample_review/evaluate", + legacy_path=f"{LEGACY_REVIEW_PREFIX}/evaluate", + methods=("POST",), + lane="sample_pipeline", + ), + AiRouteAlias( + name="candidate_handoff", + canonical_path=ai_review_path("/candidate_handoff"), + legacy_path=legacy_review_path("/candidate_handoff"), + methods=("POST",), + lane="candidate_queue", + ), + AiRouteAlias( + name="candidate_queue_draft", + canonical_path=ai_review_path("/candidate_queue_draft"), + legacy_path=legacy_review_path("/candidate_queue_draft"), + methods=("POST",), + lane="candidate_queue", + ), + AiRouteAlias( + name="candidate_queue_approval", + canonical_path=ai_review_path("/candidate_queue_approval"), + legacy_path=legacy_review_path("/candidate_queue_approval"), + methods=("POST",), + lane="candidate_queue", + ), + AiRouteAlias( + name="candidate_queue_transaction", + canonical_path=ai_review_path("/candidate_queue_transaction"), + legacy_path=legacy_review_path("/candidate_queue_transaction"), + methods=("POST",), + lane="candidate_queue", + ), + AiRouteAlias( + name="candidate_queue_writer_status", + canonical_path=ai_review_path("/candidate_queue_writer_status"), + legacy_path=legacy_review_path("/candidate_queue_writer_status"), + methods=("POST",), + lane="candidate_queue_writer", + ), + AiRouteAlias( + name="candidate_queue_writer_preflight", + canonical_path=ai_review_path("/candidate_queue_writer_preflight"), + legacy_path=legacy_review_path("/candidate_queue_writer_preflight"), + methods=("POST",), + lane="candidate_queue_writer", + ), + AiRouteAlias( + name="candidate_queue_writer_postwrite_smoke", + canonical_path=ai_review_path("/candidate_queue_writer_postwrite_smoke"), + legacy_path=legacy_review_path("/candidate_queue_writer_postwrite_smoke"), + methods=("POST",), + lane="candidate_queue_writer", + ), + AiRouteAlias( + name="candidate_queue_writer_operator_drill", + canonical_path=ai_review_path("/candidate_queue_writer_operator_drill"), + legacy_path=legacy_review_path("/candidate_queue_writer_operator_drill"), + methods=("POST",), + lane="candidate_queue_writer", + ), + AiRouteAlias( + name="candidate_queue_writer_run_package", + canonical_path=ai_review_path("/candidate_queue_writer_run_package"), + legacy_path=legacy_review_path("/candidate_queue_writer_run_package"), + methods=("POST",), + lane="candidate_queue_writer", + ), + AiRouteAlias( + name="candidate_queue_writer_run_readiness", + canonical_path=ai_review_path("/candidate_queue_writer_run_readiness"), + legacy_path=legacy_review_path("/candidate_queue_writer_run_readiness"), + methods=("POST",), + lane="candidate_queue_writer", + ), + AiRouteAlias( + name="candidate_queue_writer_run_receipt", + canonical_path=ai_review_path("/candidate_queue_writer_run_receipt"), + legacy_path=legacy_review_path("/candidate_queue_writer_run_receipt"), + methods=("POST",), + lane="candidate_queue_writer", + ), + AiRouteAlias( + name="candidate_queue_writer_run_closeout", + canonical_path=ai_review_path("/candidate_queue_writer_run_closeout"), + legacy_path=legacy_review_path("/candidate_queue_writer_run_closeout"), + methods=("POST",), + lane="candidate_queue_writer", + ), + AiRouteAlias( + name="candidate_queue_review_handoff", + canonical_path=ai_review_path("/candidate_queue_review_handoff"), + legacy_path=legacy_review_path("/candidate_queue_review_handoff"), + methods=("POST",), + lane="candidate_queue_writer", + ), + AiRouteAlias( + name="candidate_queue_review_inventory", + canonical_path=ai_review_path("/candidate_queue_review_inventory"), + legacy_path=legacy_review_path("/candidate_queue_review_inventory"), + methods=("POST",), + lane="candidate_queue_decision", + ), + AiRouteAlias( + name="candidate_queue_review_decision", + canonical_path=ai_review_path("/candidate_queue_review_decision"), + legacy_path=legacy_review_path("/candidate_queue_review_decision"), + methods=("POST",), + lane="candidate_queue_decision", + ), + AiRouteAlias( + name="candidate_queue_review_decision_approval", + canonical_path=ai_review_path("/candidate_queue_review_decision_approval"), + legacy_path=legacy_review_path("/candidate_queue_review_decision_approval"), + methods=("POST",), + lane="candidate_queue_decision", + ), + AiRouteAlias( + name="candidate_queue_review_decision_transaction", + canonical_path=ai_review_path("/candidate_queue_review_decision_transaction"), + legacy_path=legacy_review_path("/candidate_queue_review_decision_transaction"), + methods=("POST",), + lane="candidate_queue_decision", + ), + AiRouteAlias( + name="candidate_queue_review_decision_writer_status", + canonical_path=ai_review_path("/candidate_queue_review_decision_writer_status"), + legacy_path=legacy_review_path("/candidate_queue_review_decision_writer_status"), + methods=("POST",), + lane="candidate_queue_decision_writer", + ), + AiRouteAlias( + name="candidate_queue_review_decision_writer_preflight", + canonical_path=ai_review_path("/candidate_queue_review_decision_writer_preflight"), + legacy_path=legacy_review_path("/candidate_queue_review_decision_writer_preflight"), + methods=("POST",), + lane="candidate_queue_decision_writer", + ), + AiRouteAlias( + name="candidate_queue_review_decision_writer_postwrite_smoke", + canonical_path=ai_review_path("/candidate_queue_review_decision_writer_postwrite_smoke"), + legacy_path=legacy_review_path("/candidate_queue_review_decision_writer_postwrite_smoke"), + methods=("POST",), + lane="candidate_queue_decision_writer", + ), + AiRouteAlias( + name="candidate_queue_review_decision_writer_operator_drill", + canonical_path=ai_review_path("/candidate_queue_review_decision_writer_operator_drill"), + legacy_path=legacy_review_path("/candidate_queue_review_decision_writer_operator_drill"), + methods=("POST",), + lane="candidate_queue_decision_writer", + ), + AiRouteAlias( + name="candidate_queue_review_decision_writer_run_package", + canonical_path=ai_review_path("/candidate_queue_review_decision_writer_run_package"), + legacy_path=legacy_review_path("/candidate_queue_review_decision_writer_run_package"), + methods=("POST",), + lane="candidate_queue_decision_writer", + ), + AiRouteAlias( + name="candidate_queue_review_decision_writer_run_readiness", + canonical_path=ai_review_path("/candidate_queue_review_decision_writer_run_readiness"), + legacy_path=legacy_review_path("/candidate_queue_review_decision_writer_run_readiness"), + methods=("POST",), + lane="candidate_queue_decision_writer", + ), + AiRouteAlias( + name="candidate_queue_review_decision_writer_run_receipt", + canonical_path=ai_review_path("/candidate_queue_review_decision_writer_run_receipt"), + legacy_path=legacy_review_path("/candidate_queue_review_decision_writer_run_receipt"), + methods=("POST",), + lane="candidate_queue_decision_writer", + ), + AiRouteAlias( + name="candidate_queue_review_decision_writer_run_closeout", + canonical_path=ai_review_path("/candidate_queue_review_decision_writer_run_closeout"), + legacy_path=legacy_review_path("/candidate_queue_review_decision_writer_run_closeout"), + methods=("POST",), + lane="candidate_queue_decision_writer", + ), + *MCP_AI_CONTROLLED_ALIASES, + *AI_REVIEW_POST_ALIASES, + *AI_REVIEW_POST_AI_ALIASES, + *AI_REVIEW_REPORT_ALIASES, +) + +AI_CONTROLLED_CANONICAL_SMOKE_TARGETS = tuple( + alias.canonical_path for alias in AI_CONTROLLED_ROUTE_ALIASES +) + + +def build_ai_controlled_route_alias_report() -> dict[str, object]: + return { + "policy": "read_only_market_intel_ai_controlled_route_aliases", + "result": "AI_CONTROLLED_CANONICAL_ROUTES_READY", + "primary_route_family": AI_CONTROLLED_PREFIX, + "legacy_route_family_status": "compatibility_only", + "canonical_count": len(AI_CONTROLLED_ROUTE_ALIASES), + "legacy_compatibility_count": len(AI_CONTROLLED_ROUTE_ALIASES), + "routes": [alias.as_dict() for alias in AI_CONTROLLED_ROUTE_ALIASES], + "safety": { + "read_only": True, + "writes_database": False, + "executes_network": False, + "github_used": False, + }, + } diff --git a/services/pchome_mapping_backlog_service.py b/services/pchome_mapping_backlog_service.py new file mode 100644 index 0000000..714f6b5 --- /dev/null +++ b/services/pchome_mapping_backlog_service.py @@ -0,0 +1,39967 @@ +"""Shared read-only PChome growth mapping backlog summarizer.""" + +from __future__ import annotations + +import json +import hashlib +import re +import unicodedata +from pathlib import Path +from typing import Any +from urllib.parse import urlparse + +import requests +from bs4 import BeautifulSoup + +from services.ai_exception_contract import ( + AI_EXCEPTION_MODE_KEY, + AI_EXCEPTION_MODE_MACHINE_VERIFIABLE, + AI_EXCEPTION_REQUIRED_COUNT_KEY, + AI_EXCEPTION_REQUIRED_KEY, + LEGACY_HUMAN_REVIEW_REQUIRED_COUNT_KEY, + LEGACY_HUMAN_REVIEW_REQUIRED_KEY, + LEGACY_HUMAN_REVIEW_REQUIRED_LEGACY_KEY, + LEGACY_PRIMARY_FLOW_COUNT_KEY, + LEGACY_REVIEW_MODE_EXCEPTION_ONLY, + LEGACY_REVIEW_MODE_KEY, + LEGACY_REVIEW_REQUIRED_COUNT_KEY, + LEGACY_REVIEW_REQUIRED_KEY, + PRIMARY_HUMAN_GATE_COUNT_KEY, +) + + +BACKLOG_POLICY = "read_only_pchome_growth_mapping_backlog" +OPERATOR_PREVIEW_POLICY = "read_only_pchome_growth_mapping_operator_preview" +DIRECT_MAPPING_AUTO_SEARCH_PACKAGE_POLICY = ( + "read_only_pchome_growth_direct_mapping_auto_search_package" +) +DIRECT_MAPPING_CANDIDATE_DECISION_PACKAGE_POLICY = ( + "read_only_pchome_growth_direct_mapping_candidate_decision_package" +) +AI_AUTOMATION_READINESS_POLICY = "read_only_pchome_growth_ai_automation_readiness" +EVIDENCE_ENRICHMENT_PREVIEW_POLICY = "read_only_pchome_growth_evidence_enrichment_preview" +EVIDENCE_SOURCE_PREVIEW_POLICY = "read_only_pchome_growth_evidence_source_preview" +PRODUCT_PAGE_EVIDENCE_PARSER_POLICY = "read_only_pchome_product_page_evidence_parser" +EVIDENCE_FETCH_GATE_POLICY = "controlled_read_only_pchome_product_page_evidence_fetch_gate" +EVIDENCE_MERGE_PREVIEW_POLICY = "read_only_pchome_growth_evidence_merge_preview" +AUTO_POLICY_RECEIPT_GATE_POLICY = "read_only_pchome_growth_auto_policy_receipt_gate" +AUTO_POLICY_PERSISTENCE_GATE_POLICY = "read_only_pchome_growth_auto_policy_persistence_gate" +AUTO_POLICY_SCHEMA_MIGRATION_PREVIEW_POLICY = "read_only_pchome_growth_auto_policy_schema_migration_preview" +AUTO_POLICY_MIGRATION_FILE_PREVIEW_POLICY = "read_only_pchome_growth_auto_policy_migration_file_preview" +AUTO_POLICY_APPLY_READINESS_CLOSEOUT_POLICY = "read_only_pchome_growth_auto_policy_apply_readiness_closeout" +AUTO_POLICY_MIGRATION_FILE_GENERATION_REQUEST_POLICY = ( + "read_only_pchome_growth_auto_policy_migration_file_generation_request" +) +AUTO_POLICY_MIGRATION_APPLY_GATE_PREVIEW_POLICY = ( + "read_only_pchome_growth_auto_policy_migration_apply_gate_preview" +) +AUTO_POLICY_DB_APPLY_REQUEST_GATE_PREVIEW_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_request_gate_preview" +) +AUTO_POLICY_DB_APPLY_EXECUTION_PREFLIGHT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_execution_preflight" +) +AUTO_POLICY_DB_APPLY_AUTHORIZATION_PACKAGE_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_package" +) +AUTO_POLICY_DB_APPLY_VERIFIER_ARTIFACT_PREVIEW_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_verifier_artifact_preview" +) +AUTO_POLICY_DB_APPLY_FINAL_HANDOFF_PACKAGE_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_final_handoff_package" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_SHELL_PREVIEW_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_shell_preview" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_SHELL_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_shell_closeout" +) +AUTO_POLICY_DB_APPLY_AUTHORIZATION_REQUEST_INTAKE_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_request_intake" +) +AUTO_POLICY_DB_APPLY_AUTHORIZATION_REQUEST_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_request_closeout" +) +AUTO_POLICY_DB_APPLY_AUTHORIZATION_LANE_GUARD_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_lane_guard" +) +AUTO_POLICY_DB_APPLY_AUTHORIZATION_DECISION_PREFLIGHT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_decision_preflight" +) +AUTO_POLICY_DB_APPLY_AUTHORIZATION_DECISION_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_decision_closeout" +) +AUTO_POLICY_DB_APPLY_AUTHORIZATION_ISSUER_GATE_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_issuer_gate" +) +AUTO_POLICY_DB_APPLY_AUTHORIZATION_SIGNING_DECISION_PREFLIGHT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signing_decision_preflight" +) +AUTO_POLICY_DB_APPLY_AUTHORIZATION_SIGNING_DECISION_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signing_decision_closeout" +) +AUTO_POLICY_DB_APPLY_AUTHORIZATION_SIGNING_ISSUER_GUARD_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signing_issuer_guard" +) +AUTO_POLICY_DB_APPLY_AUTHORIZATION_SIGNING_ISSUER_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signing_issuer_closeout" +) +AUTO_POLICY_DB_APPLY_AUTHORIZATION_SIGNING_EXECUTION_PREFLIGHT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signing_execution_preflight" +) +AUTO_POLICY_DB_APPLY_AUTHORIZATION_SIGNING_EXECUTION_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signing_execution_closeout" +) +AUTO_POLICY_DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_PREFLIGHT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signed_receipt_preflight" +) +AUTO_POLICY_DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signed_receipt_closeout" +) +AUTO_POLICY_DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_EVIDENCE_INTAKE_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signed_receipt_evidence_intake" +) +AUTO_POLICY_DB_APPLY_AUTHORIZATION_DETACHED_VERIFICATION_EVIDENCE_VALIDATION_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_detached_verification_evidence_validation" +) +AUTO_POLICY_DB_APPLY_AUTHORIZATION_VERIFIER_RECEIPT_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_verifier_receipt_closeout" +) +AUTO_POLICY_DB_APPLY_AUTHORIZATION_EVIDENCE_EXECUTION_PREFLIGHT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_evidence_execution_preflight" +) +AUTO_POLICY_DB_APPLY_AUTHORIZATION_EVIDENCE_EXECUTION_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_evidence_execution_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_APPLY_FINAL_PREFLIGHT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_apply_final_preflight" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_PACKAGE_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_package" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_RECEIPT_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_receipt_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_RUNNER_READINESS_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_runner_readiness" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_EXECUTION_PLAN_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_execution_plan_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_COMMAND_ARTIFACT_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_command_artifact_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_RUNNER_EXECUTION_RECEIPT_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_runner_execution_receipt_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_POST_RECEIPT_PARSER_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_post_receipt_parser_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_NO_APPLY_ENFORCEMENT_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_no_apply_enforcement_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_FINAL_EXECUTOR_GUARD_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_final_executor_guard_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_PRE_APPLY_REPLAY_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_pre_apply_replay_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_APPLY_EXECUTOR_READINESS_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_apply_executor_readiness_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_INVOCATION_RECEIPT_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_invocation_receipt_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_NO_WRITE_INVOCATION_PACKAGE_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_no_write_invocation_package_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_EXECUTION_PREFLIGHT_GUARD_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_execution_preflight_guard_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_RUNNER_INVOCATION_BOUNDARY_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_runner_invocation_boundary_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_NO_EXECUTION_RECEIPT_HANDOFF_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_no_execution_receipt_handoff_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_FINAL_NO_RUNNER_EXECUTION_PROOF_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_final_no_runner_execution_proof_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_CONTROLLED_EXECUTOR_QUARANTINE_PROOF_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_controlled_executor_quarantine_proof_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_EXECUTION_ENVELOPE_FREEZE_PROOF_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_execution_envelope_freeze_proof_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_FROZEN_ENVELOPE_VERIFIER_HANDOFF_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_frozen_envelope_verifier_handoff_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_VERIFIER_INVOCATION_LOCK_PROOF_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_verifier_invocation_lock_proof_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_VERIFIER_NO_EXECUTION_RECEIPT_PROOF_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_verifier_no_execution_receipt_proof_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_VERIFIER_RECEIPT_PERSISTENCE_GUARD_PROOF_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_RECEIPT_PERSISTENCE_STORAGE_BOUNDARY_PROOF_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_STORAGE_BOUNDARY_NO_WRITE_LEDGER_PROOF_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_NO_WRITE_LEDGER_RETENTION_PROOF_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_no_write_ledger_retention_proof_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_RETENTION_BOUNDARY_NO_WRITE_ARCHIVE_PROOF_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout" +) +AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_ARCHIVE_RETENTION_SEALED_HANDOFF_PROOF_CLOSEOUT_POLICY = ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_archive_retention_sealed_handoff_proof_closeout" +) +PCHOME_FETCH_ALLOWED_DOMAIN = "24h.pchome.com.tw" +PCHOME_FETCH_MAX_BATCH_SIZE = 12 +PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS = 5 +PCHOME_FETCH_MAX_HTML_BYTES = 512_000 +EXTERNAL_BENCHMARK_REFERENCES = [ + { + "source": "Google Merchant Center product data specification", + "url": "https://support.google.com/merchants/answer/7052112", + "applies_to": "accurate_product_feed_matching", + }, + { + "source": "Google Search Product structured data", + "url": "https://developers.google.com/search/docs/appearance/structured-data/product", + "applies_to": "rich_product_information_and_offer_visibility", + }, + { + "source": "Google Merchant listing structured data", + "url": "https://developers.google.com/search/docs/appearance/structured-data/merchant-listing", + "applies_to": "product_offer_price_currency_availability", + }, + { + "source": "Baymard ecommerce product and search UX benchmark", + "url": "https://baymard.com/research/product-page", + "applies_to": "operator_search_and_product_detail_quality", + }, +] + + +def _ai_exception_compatibility_fields(ai_exception_required: bool) -> dict[str, Any]: + """Return the AI-first exception contract with legacy readback aliases.""" + return { + LEGACY_HUMAN_REVIEW_REQUIRED_KEY: False, + LEGACY_HUMAN_REVIEW_REQUIRED_LEGACY_KEY: bool(ai_exception_required), + AI_EXCEPTION_REQUIRED_KEY: bool(ai_exception_required), + PRIMARY_HUMAN_GATE_COUNT_KEY: 0, + AI_EXCEPTION_MODE_KEY: AI_EXCEPTION_MODE_MACHINE_VERIFIABLE, + } + + +def _legacy_review_compatibility_fields(ai_exception_required: bool = False) -> dict[str, Any]: + """Keep old review-mode keys false while exposing the AI exception state.""" + return { + LEGACY_REVIEW_REQUIRED_KEY: False, + LEGACY_REVIEW_MODE_KEY: LEGACY_REVIEW_MODE_EXCEPTION_ONLY, + AI_EXCEPTION_REQUIRED_KEY: bool(ai_exception_required), + AI_EXCEPTION_MODE_KEY: AI_EXCEPTION_MODE_MACHINE_VERIFIABLE, + } + + +def _evidence_requires_ai_exception(evidence: dict[str, Any]) -> bool: + return bool( + evidence.get(AI_EXCEPTION_REQUIRED_KEY) + or evidence.get(LEGACY_HUMAN_REVIEW_REQUIRED_LEGACY_KEY) + or evidence.get(LEGACY_HUMAN_REVIEW_REQUIRED_KEY) + ) + + +def _summary_exception_count(summary: dict[str, Any]) -> int: + return int( + summary.get(AI_EXCEPTION_REQUIRED_COUNT_KEY) + or summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY) + or 0 + ) + + +_MEASURE_UNIT_ALIASES = { + "ml": "ml", + "m l": "ml", + "毫升": "ml", + "l": "l", + "公升": "l", + "g": "g", + "公克": "g", + "克": "g", + "mg": "mg", + "毫克": "mg", + "kg": "kg", + "公斤": "kg", + "oz": "oz", + "floz": "floz", + "fl oz": "floz", + "fl.oz": "floz", +} +_MEASURE_RE = re.compile( + r"(?P\d+(?:\.\d+)?)\s*(?Pfl\.?\s*oz|floz|ml|m\s*l|毫升|公升|l|mg|毫克|kg|公斤|g|公克|克|oz)", + re.IGNORECASE, +) +_COUNT_UNIT_ALIASES = { + "入": "ct", + "瓶": "ct", + "支": "ct", + "條": "ct", + "盒": "ct", + "包": "ct", + "袋": "ct", + "片": "ct", + "顆": "ct", + "粒": "ct", + "錠": "ct", + "枚": "ct", + "件": "ct", + "罐": "ct", + "蕊": "ct", + "張": "ct", + "抽": "ct", + "組": "ct", + "pcs": "ct", + "pc": "ct", + "ct": "ct", +} +_COUNT_UNIT_PATTERN = "|".join(sorted(map(re.escape, _COUNT_UNIT_ALIASES), key=len, reverse=True)) +_COUNT_RE = re.compile(rf"(?P\d+)\s*(?P{_COUNT_UNIT_PATTERN})", re.IGNORECASE) +_CHINESE_COUNT_RE = re.compile(rf"(?P[一二兩三四五六七八九十])\s*(?P{_COUNT_UNIT_PATTERN})") +_MULTIPLIER_RE = re.compile(r"(?:x|X)\s*(?P\d+)") +_CHINESE_DIGITS = { + "一": 1, + "二": 2, + "兩": 2, + "三": 3, + "四": 4, + "五": 5, + "六": 6, + "七": 7, + "八": 8, + "九": 9, + "十": 10, +} +_VARIANT_KEYWORDS = ( + "任選", + "多款", + "色號", + "色選", + "顏色", + "款式", + "香味", + "香調", + "口味", + "尺寸", + "規格可選", +) +_BUNDLE_KEYWORDS = ( + "套組", + "組合", + "超值組", + "買一送一", + "贈", + "加贈", + "禮盒", + "福袋", +) +_EXPIRY_KEYWORDS = ("即期", "效期", "有效期限") +_SAMPLE_KEYWORDS = ("試用", "小樣", "體驗", "旅行組") +_UNIT_BASE_MEASURE = { + "ml": {"value": 100, "unit": "ml"}, + "l": {"value": 1, "unit": "l"}, + "g": {"value": 100, "unit": "g"}, + "mg": {"value": 100, "unit": "mg"}, + "kg": {"value": 1, "unit": "kg"}, + "oz": {"value": 1, "unit": "oz"}, + "floz": {"value": 1, "unit": "floz"}, + "ct": {"value": 1, "unit": "ct"}, +} + + +def _to_float(value: Any) -> float: + try: + return float(value or 0) + except (TypeError, ValueError): + return 0.0 + + +def _action_code(item: dict[str, Any]) -> str: + action = item.get("recommended_action") or {} + return str(action.get("code") or "") + + +def _action_label(item: dict[str, Any]) -> str: + action = item.get("recommended_action") or {} + return str(action.get("label") or _action_code(item) or "unknown") + + +def _first_present(*values: Any) -> Any: + for value in values: + if value not in (None, ""): + return value + return None + + +def _pchome_product_url(product_id: str) -> str | None: + if not product_id: + return None + return f"https://24h.pchome.com.tw/prod/{product_id}" + + +def _normalize_package_text(value: str) -> str: + normalized = unicodedata.normalize("NFKC", value or "") + normalized = normalized.replace("×", "x").replace("*", "x").replace("*", "x") + return re.sub(r"\s+", " ", normalized).strip().lower() + + +def _canonical_measure_unit(unit: str) -> str: + compact = re.sub(r"\s+", " ", unit or "").strip().lower() + return _MEASURE_UNIT_ALIASES.get(compact, compact) + + +def _round_quantity(value: float) -> int | float: + return int(value) if float(value).is_integer() else round(value, 3) + + +def _risk_signals(normalized_name: str) -> list[str]: + signals = [] + if any(keyword in normalized_name for keyword in _VARIANT_KEYWORDS): + signals.append("variant_selection") + if any(keyword in normalized_name for keyword in _BUNDLE_KEYWORDS): + signals.append("bundle_or_promo") + if any(keyword in normalized_name for keyword in _EXPIRY_KEYWORDS): + signals.append("freshness_or_expiry") + if any(keyword in normalized_name for keyword in _SAMPLE_KEYWORDS): + signals.append("sample_or_travel_size") + return signals + + +def _dedupe_quantity_rows(rows: list[dict[str, Any]]) -> list[dict[str, Any]]: + seen = set() + deduped = [] + for row in rows: + key = (row.get("value"), row.get("unit"), row.get("raw")) + if key in seen: + continue + seen.add(key) + deduped.append(row) + return deduped + + +def _jsonld_nodes(value: Any): + if isinstance(value, dict): + yield value + for child in value.values(): + yield from _jsonld_nodes(child) + elif isinstance(value, list): + for item in value: + yield from _jsonld_nodes(item) + + +def _jsonld_type_includes(node: dict[str, Any], expected_type: str) -> bool: + node_type = node.get("@type") or node.get("type") + if isinstance(node_type, str): + return node_type.lower() == expected_type.lower() + if isinstance(node_type, list): + return any(str(item).lower() == expected_type.lower() for item in node_type) + return False + + +def _first_image_url(image_value: Any) -> str | None: + if isinstance(image_value, str) and image_value.strip(): + return image_value.strip() + if isinstance(image_value, dict): + return _first_present(image_value.get("url"), image_value.get("contentUrl")) + if isinstance(image_value, list): + for item in image_value: + image = _first_image_url(item) + if image: + return image + return None + + +def _normalize_schema_availability(value: Any) -> str | None: + if value in (None, ""): + return None + text = str(value).strip() + lowered = text.lower() + compact = re.sub(r"[\s_-]+", "", lowered) + if "instock" in compact: + return "in_stock" + if "outofstock" in compact or "soldout" in compact: + return "out_of_stock" + if "preorder" in compact: + return "preorder" + if "backorder" in compact: + return "backorder" + if "discontinued" in compact: + return "discontinued" + return "unknown" + + +def parse_pchome_product_page_evidence_html(html: str, product_url: str | None = None) -> dict[str, Any]: + """Parse product-page evidence from HTML fixture text without fetching or writing.""" + soup = BeautifulSoup(html or "", "html.parser") + warnings = [] + image_url = None + availability = None + availability_raw = None + jsonld_product_found = False + jsonld_offer_found = False + fallbacks_used = [] + + for script in soup.find_all("script", attrs={"type": re.compile("ld\\+json", re.IGNORECASE)}): + text = script.string or script.get_text() or "" + if not text.strip(): + continue + try: + data = json.loads(text) + except json.JSONDecodeError: + warnings.append("invalid_jsonld_skipped") + continue + for node in _jsonld_nodes(data): + if _jsonld_type_includes(node, "Product"): + jsonld_product_found = True + image_url = image_url or _first_image_url(node.get("image")) + if _jsonld_type_includes(node, "Offer"): + jsonld_offer_found = True + availability_raw = availability_raw or node.get("availability") + availability = availability or _normalize_schema_availability(availability_raw) + + if not image_url: + og_image = soup.find("meta", property="og:image") + if og_image and og_image.get("content"): + image_url = str(og_image.get("content")).strip() + fallbacks_used.append("og:image") + + if not availability: + product_availability = soup.find("meta", attrs={"property": "product:availability"}) + if product_availability and product_availability.get("content"): + availability_raw = str(product_availability.get("content")).strip() + availability = _normalize_schema_availability(availability_raw) + fallbacks_used.append("product:availability") + + return { + "policy": PRODUCT_PAGE_EVIDENCE_PARSER_POLICY, + "source": "html_fixture", + "product_url": product_url, + "image_url": image_url, + "availability": availability, + "availability_raw": availability_raw, + "jsonld_product_found": jsonld_product_found, + "jsonld_offer_found": jsonld_offer_found, + "fallbacks_used": fallbacks_used, + "parser_warnings": warnings, + "safety": { + "fetches_external_sites": False, + "writes_database": False, + "executes_search": False, + "dispatches_telegram": False, + "llm_calls": False, + }, + } + + +def _is_allowed_pchome_product_url(product_url: str | None) -> bool: + if not product_url: + return False + parsed = urlparse(product_url) + return ( + parsed.scheme in {"http", "https"} + and parsed.netloc == PCHOME_FETCH_ALLOWED_DOMAIN + and parsed.path.startswith("/prod/") + ) + + +def _response_content_bytes(response: Any, max_html_bytes: int) -> bytes: + content = getattr(response, "content", None) + if content is None: + content = str(getattr(response, "text", "") or "").encode("utf-8") + if len(content) > max_html_bytes: + raise ValueError("html_size_cap_exceeded") + return bytes(content) + + +def _fetch_product_page_html( + product_url: str, + *, + timeout_seconds: int, + max_html_bytes: int, + http_get: Any = None, +) -> tuple[str, dict[str, Any]]: + getter = http_get or requests.get + response = getter( + product_url, + timeout=timeout_seconds, + headers={ + "User-Agent": "MOMO-Pro-Evidence-Gate/1.0 (+read-only; no-write)", + "Accept": "text/html,application/xhtml+xml", + }, + ) + status_code = int(getattr(response, "status_code", 0) or 0) + if status_code >= 400: + raise ValueError(f"http_status_{status_code}") + content = _response_content_bytes(response, max_html_bytes=max_html_bytes) + encoding = getattr(response, "encoding", None) or "utf-8" + return content.decode(encoding, errors="replace"), { + "http_status": status_code, + "content_bytes": len(content), + } + + +def parse_unit_package_basis(product_name: str) -> dict[str, Any]: + """Parse unit/package evidence from a product title without fetching or writing.""" + normalized_name = _normalize_package_text(product_name) + quantities = [] + for match in _MEASURE_RE.finditer(normalized_name): + value = float(match.group("value")) + unit = _canonical_measure_unit(match.group("unit")) + quantities.append( + { + "value": _round_quantity(value), + "unit": unit, + "raw": match.group(0).strip(), + } + ) + + counts = [] + for match in _COUNT_RE.finditer(normalized_name): + count = int(match.group("count")) + counts.append({"count": count, "unit": match.group("unit"), "canonical_unit": "ct", "raw": match.group(0)}) + for match in _CHINESE_COUNT_RE.finditer(normalized_name): + count = _CHINESE_DIGITS.get(match.group("count")) + if count: + counts.append({"count": count, "unit": match.group("unit"), "canonical_unit": "ct", "raw": match.group(0)}) + counts = _dedupe_quantity_rows(counts) + + multipliers = [int(match.group("count")) for match in _MULTIPLIER_RE.finditer(normalized_name)] + for row in counts: + if row["count"] > 1 and row["count"] not in multipliers: + multipliers.append(row["count"]) + + risk_signals = _risk_signals(normalized_name) + primary_quantity = quantities[0] if quantities else None + primary_count = counts[0] if counts else None + unit_label = primary_quantity["unit"] if primary_quantity else ("ct" if primary_count else None) + multiplier_product = 1 + for multiplier in multipliers: + multiplier_product *= max(multiplier, 1) + + estimated_total_quantity = None + if primary_quantity: + estimated_total_quantity = float(primary_quantity["value"]) * multiplier_product + elif primary_count: + estimated_total_quantity = float(primary_count["count"]) + + if primary_quantity and risk_signals: + package_basis = "variant_sensitive_quantity_candidate" + elif primary_quantity and multiplier_product > 1: + package_basis = "multi_pack_quantity_candidate" + elif primary_quantity: + package_basis = "single_unit_quantity_candidate" + elif primary_count: + package_basis = "count_package_candidate" + elif risk_signals: + package_basis = "catalog_or_variant_review" + else: + package_basis = "insufficient" + + has_basis = package_basis != "insufficient" + confidence = 0.0 + if primary_quantity and not risk_signals: + confidence = 0.86 if multiplier_product == 1 else 0.78 + elif primary_quantity: + confidence = 0.62 + elif primary_count and not risk_signals: + confidence = 0.68 + elif has_basis: + confidence = 0.36 + + unit_pricing_measure = None + unit_pricing_base_measure = None + if estimated_total_quantity is not None and unit_label: + unit_pricing_measure = { + "value": _round_quantity(estimated_total_quantity), + "unit": unit_label, + } + unit_pricing_base_measure = _UNIT_BASE_MEASURE.get(unit_label) + + ai_exception_required = bool(risk_signals) or not has_basis + + return { + "source": "deterministic_product_title_parser", + "mode": "local_parse_only", + "product_name": product_name or "", + "package_basis": package_basis, + "quantities": quantities, + "counts": counts, + "multipliers": multipliers, + "estimated_total_quantity": _round_quantity(estimated_total_quantity) if estimated_total_quantity is not None else None, + "unit_label": unit_label, + "unit_pricing_measure": unit_pricing_measure, + "unit_pricing_base_measure": unit_pricing_base_measure, + "risk_signals": risk_signals, + "parser_confidence": confidence, + **_ai_exception_compatibility_fields(ai_exception_required), + "writes_database": False, + "fetches_external_sites": False, + "llm_calls": False, + } + + +def _evidence_completeness(item: dict[str, Any], review_candidate: dict[str, Any], external_price: dict[str, Any]) -> dict[str, Any]: + product_id = str(item.get("pchome_product_id") or "").strip() + product_name = str(item.get("product_name") or "").strip() + product_url = _first_present(item.get("product_url"), item.get("pchome_url"), _pchome_product_url(product_id)) + pchome_price = _first_present( + item.get("pchome_price"), + external_price.get("pchome_price"), + review_candidate.get("pchome_price"), + ) + image_url = _first_present(item.get("image_url"), item.get("image"), item.get("product_image_url")) + availability = _first_present(item.get("availability"), item.get("stock_status"), item.get("is_available")) + unit_package_basis = parse_unit_package_basis(product_name) + parsed_unit_basis = ( + unit_package_basis + if unit_package_basis.get("package_basis") != "insufficient" + else None + ) + unit_basis = _first_present( + external_price.get("price_basis"), + item.get("price_basis"), + item.get("unit_label"), + parsed_unit_basis, + ) + unit_review_required = bool(unit_package_basis.get("risk_signals")) + + checks = [ + ("stable_product_id", bool(product_id), "required"), + ("product_name", bool(product_name), "required"), + ("product_url", bool(product_url), "required"), + ("price", pchome_price is not None, "required"), + ("image", bool(image_url), "strongly_recommended"), + ("availability", availability is not None, "strongly_recommended"), + ( + "unit_price_or_package_basis", + bool(unit_basis), + "required_when_bundle_or_unit_sensitive", + ), + ] + present = [field for field, ok, _requirement in checks if ok] + missing = [field for field, ok, _requirement in checks if not ok] + blocking_missing = [ + field + for field, ok, requirement in checks + if not ok and requirement in {"required", "strongly_recommended"} + ] + score = round(len(present) / max(len(checks), 1) * 100, 1) + + ai_exception_required = ( + bool(blocking_missing) + or bool(review_candidate) + or not external_price + or unit_review_required + ) + + return { + "score": score, + "present_fields": present, + "missing_fields": missing, + "blocking_missing_fields": blocking_missing, + "auto_accept_ready": not blocking_missing and bool(external_price) and not unit_review_required, + **_ai_exception_compatibility_fields(ai_exception_required), + "product_url": product_url, + "image_url": image_url, + "availability": availability, + "unit_package_basis": unit_package_basis, + } + + +def compact_mapping_item(item: dict[str, Any]) -> dict[str, Any]: + review_candidate = item.get("review_candidate") or {} + external_price = item.get("external_price") or {} + product_id = str(item.get("pchome_product_id") or "") + product_url = _first_present(item.get("product_url"), item.get("pchome_url"), _pchome_product_url(product_id)) + return { + "pchome_product_id": product_id, + "product_url": product_url, + "product_name": item.get("product_name") or "", + "sales_7d": round(_to_float(item.get("sales_7d")), 2), + "sales_delta_pct": item.get("sales_delta_pct"), + "priority_score": item.get("priority_score"), + "pchome_price": item.get("pchome_price"), + "action_code": _action_code(item), + "action_label": _action_label(item), + "review_candidate": { + "id": review_candidate.get("id"), + "momo_sku": review_candidate.get("momo_sku"), + "momo_name": review_candidate.get("momo_name"), + "quality_score": review_candidate.get("quality_score"), + "gap_pct": review_candidate.get("gap_pct"), + } + if review_candidate + else None, + "external_price": { + "momo_sku": external_price.get("momo_sku"), + "momo_name": external_price.get("momo_name"), + "price_basis": external_price.get("price_basis"), + "gap_pct": external_price.get("gap_pct"), + "data_source_label": external_price.get("data_source_label"), + "updated_at": external_price.get("updated_at"), + } + if external_price + else None, + "evidence_completeness": _evidence_completeness(item, review_candidate, external_price), + "reason_lines": list(item.get("reason_lines") or [])[:3], + } + + +def _build_external_benchmark_alignment() -> dict[str, Any]: + return { + "references": EXTERNAL_BENCHMARK_REFERENCES, + "required_evidence_fields": [ + { + "field": "stable_product_id", + "current_payload": "pchome_product_id", + "status": "present", + "why": "Stable IDs preserve mapping history and make post-run readback comparable.", + }, + { + "field": "product_name", + "current_payload": "product_name", + "status": "present", + "why": "Exact title/name matching is the first identity anchor for operator review.", + }, + { + "field": "product_url", + "current_payload": "derived_from_pchome_product_id", + "status": "present_for_pchome", + "why": "Operators need a direct product page path for visual confirmation.", + }, + { + "field": "price", + "current_payload": "pchome_price/external_price", + "status": "partial", + "why": "Offer price and currency are required before a candidate can become decision-ready.", + }, + { + "field": "image", + "current_payload": None, + "status": "missing_in_current_growth_payload", + "why": "Image evidence should be added before high-volume auto-accept expansion.", + }, + { + "field": "availability", + "current_payload": None, + "status": "missing_in_current_growth_payload", + "why": "Availability prevents matching stale or non-purchasable offers.", + }, + { + "field": "unit_price_or_package_basis", + "current_payload": "external_price.price_basis or deterministic title parser preview", + "status": "parser_preview_available", + "why": "Unit price and package basis protect bundles, variants, and volume-size comparisons.", + }, + ], + "operator_review_principles": [ + "Separate direct mapping, review candidate, and already comparable items.", + "Do not auto-accept variants, colors, bundles, or catalog offers without explicit evidence.", + "Keep search/query support exact-title friendly so copied product names and model terms remain useful.", + ], + } + + +def _build_ai_automation_plan(selected_direct: list[dict[str, Any]], selected_review: list[dict[str, Any]]) -> dict[str, Any]: + return { + "policy": "ollama_first_read_only_ai_assist", + "llm_calls_in_preview": False, + "gemini_allowed": False, + "provider_order": [ + "GCP-A 34.87.90.216:11434", + "GCP-B 34.21.145.224:11434", + "111 192.168.0.111:11434", + ], + "automation_readiness": { + "direct_mapping_targets": len(selected_direct), + "review_candidate_targets": len(selected_review), + "can_generate_operator_summary": bool(selected_direct or selected_review), + "can_execute_write": False, + }, + "steps": [ + { + "name": "identity_anchor_extraction", + "mode": "deterministic_first_ollama_assist_later", + "writes_database": False, + "output": "brand/product_line/spec/package/variant anchors for each selected target", + }, + { + "name": "candidate_search_plan", + "mode": "rule_based_query_pack", + "writes_database": False, + "output": "exact title, brand plus product line, and spec-preserving search terms", + }, + { + "name": "operator_decision_summary", + "mode": "ollama_first_after_write_gate_only", + "writes_database": False, + "output": "plain-language review reason, evidence gaps, and post-write readback checklist", + }, + { + "name": "post_write_readback", + "mode": "deterministic_metrics", + "writes_database": False, + "output": "mapping_rate, direct_mapping_count, review_candidate_count, mapped_count delta", + }, + ], + "ai_exception_required_for": [ + "missing image or availability evidence", + "variant/color/fragrance/shade/package ambiguity", + "unit-price or bundle-sensitive comparisons", + "any candidate not meeting exact identity evidence", + ], + } + + +def _field_enrichment_sources(field: str) -> list[dict[str, Any]]: + source_map = { + "image": [ + { + "source": "PChome product page structured data", + "mode": "future_read_only_fetch", + "writes_database": False, + "expected_output": "primary product image URL", + }, + { + "source": "existing marketplace catalog payload", + "mode": "reuse_if_present", + "writes_database": False, + "expected_output": "cached image_url", + }, + ], + "availability": [ + { + "source": "PChome product page offer availability", + "mode": "future_read_only_fetch", + "writes_database": False, + "expected_output": "in_stock / out_of_stock / unknown", + }, + { + "source": "merchant listing structured data", + "mode": "future_read_only_parse", + "writes_database": False, + "expected_output": "schema.org Offer availability", + }, + ], + "unit_price_or_package_basis": [ + { + "source": "deterministic product title parser", + "mode": "local_parse_preview", + "writes_database": False, + "expected_output": "size, count, unit label, package basis", + }, + { + "source": "external_price.price_basis", + "mode": "reuse_if_present", + "writes_database": False, + "expected_output": "total_price / unit_price", + }, + ], + "price": [ + { + "source": "growth opportunity payload", + "mode": "reuse_if_present", + "writes_database": False, + "expected_output": "PChome listed price", + }, + ], + } + return source_map.get(field, []) + + +def _source_plan_for_field(field: str, missing_count: int) -> dict[str, Any]: + plans = { + "image": { + "payload_keys_checked": ["image_url", "image", "product_image_url"], + "preferred_current_source": "existing marketplace catalog payload", + "future_read_only_fetch_gate": { + "method": "GET", + "allowed_domain": "24h.pchome.com.tw", + "product_url_required": True, + "parse_targets": ["schema.org Product.image", "og:image", "primary product image"], + "check_mode_parser": PRODUCT_PAGE_EVIDENCE_PARSER_POLICY, + "fetches_external_sites_in_preview": False, + "writes_database": False, + }, + }, + "availability": { + "payload_keys_checked": ["availability", "stock_status", "is_available"], + "preferred_current_source": "existing marketplace catalog payload", + "future_read_only_fetch_gate": { + "method": "GET", + "allowed_domain": "24h.pchome.com.tw", + "product_url_required": True, + "parse_targets": ["schema.org Offer.availability", "merchant listing offer availability"], + "check_mode_parser": PRODUCT_PAGE_EVIDENCE_PARSER_POLICY, + "fetches_external_sites_in_preview": False, + "writes_database": False, + }, + }, + "price": { + "payload_keys_checked": ["pchome_price", "external_price.pchome_price", "review_candidate.pchome_price"], + "preferred_current_source": "growth opportunity payload", + "future_read_only_fetch_gate": None, + "payload_mapping_probe": { + "goal": "Confirm whether missing price is a source payload gap or summary field mapping gap.", + "fetches_external_sites_in_preview": False, + "writes_database": False, + }, + }, + } + plan = dict(plans.get(field, {})) + if not plan: + return {} + plan["field"] = field + plan["status"] = "missing_in_current_payload" if missing_count else "covered_by_current_payload" + plan["missing_count"] = missing_count + return plan + + +def _build_fetch_gate_candidates(tasks: list[dict[str, Any]]) -> list[dict[str, Any]]: + candidates = [] + for task in tasks: + missing_fields = set(task.get("missing_fields") or []) + fetch_fields = [field for field in ("image", "availability") if field in missing_fields] + if not fetch_fields: + continue + candidates.append( + { + "pchome_product_id": task.get("pchome_product_id") or "", + "product_name": task.get("product_name") or "", + "product_url": task.get("product_url"), + "fields": fetch_fields, + "method": "GET", + "allowed_domain": "24h.pchome.com.tw", + "executes_fetch_in_preview": False, + "writes_database": False, + } + ) + return candidates[:PCHOME_FETCH_MAX_BATCH_SIZE] + + +def _build_evidence_task(target: dict[str, Any], lane: str) -> dict[str, Any]: + evidence = target.get("evidence_completeness") or {} + missing_fields = list(evidence.get("missing_fields") or []) + blocking_missing_fields = list(evidence.get("blocking_missing_fields") or []) + enrichment_steps = [ + { + "field": field, + "blocking": field in blocking_missing_fields, + "sources": _field_enrichment_sources(field), + } + for field in missing_fields + ] + ai_exception_required = _evidence_requires_ai_exception(evidence) + + return { + "lane": lane, + "pchome_product_id": target.get("pchome_product_id") or "", + "product_name": target.get("product_name") or "", + "product_url": target.get("product_url") or evidence.get("product_url"), + "sales_7d": target.get("sales_7d"), + "priority_score": target.get("priority_score"), + "action_code": target.get("action_code"), + "evidence_score": evidence.get("score"), + "present_fields": list(evidence.get("present_fields") or []), + "missing_fields": missing_fields, + "blocking_missing_fields": blocking_missing_fields, + "auto_accept_ready": bool(evidence.get("auto_accept_ready")), + **_ai_exception_compatibility_fields(ai_exception_required), + "unit_package_basis": evidence.get("unit_package_basis"), + "enrichment_steps": enrichment_steps, + } + + +def summarize_pchome_mapping_backlog(payload: dict[str, Any]) -> dict[str, Any]: + stats = payload.get("stats") or {} + opportunities = [item for item in payload.get("opportunities") or [] if isinstance(item, dict)] + needs_mapping = [item for item in opportunities if not item.get("external_price")] + review_candidates = [item for item in needs_mapping if item.get("review_candidate")] + direct_mapping = [ + item + for item in needs_mapping + if _action_code(item) == "map_external_product" and not item.get("review_candidate") + ] + mapped = [item for item in opportunities if item.get("external_price")] + + action_counts: dict[str, int] = {} + sales_by_action: dict[str, float] = {} + for item in opportunities: + label = _action_label(item) + action_counts[label] = action_counts.get(label, 0) + 1 + sales_by_action[label] = round(sales_by_action.get(label, 0.0) + _to_float(item.get("sales_7d")), 2) + + candidate_count = int(stats.get("candidate_count") or len(opportunities)) + mapped_count = int(stats.get("mapped_count") or len(mapped)) + needs_mapping_count = int(stats.get("needs_mapping_count") or len(needs_mapping)) + mapping_rate = stats.get("mapping_rate") + if mapping_rate is None: + mapping_rate = round(mapped_count / max(candidate_count, 1) * 100, 1) + + top_needs_mapping = sorted( + needs_mapping, + key=lambda item: (_to_float(item.get("sales_7d")), _to_float(item.get("priority_score"))), + reverse=True, + )[:10] + top_review_candidates = sorted( + review_candidates, + key=lambda item: _to_float((item.get("review_candidate") or {}).get("quality_score")), + reverse=True, + )[:10] + + if not payload.get("success", False): + result = "BLOCKED" + elif needs_mapping_count > 0: + result = "NEEDS_MAPPING" + else: + result = "PASS" + + return { + "policy": BACKLOG_POLICY, + "result": result, + "success": bool(payload.get("success")), + "generated_at": payload.get("generated_at"), + "cache_state": payload.get("cache_state"), + "system_name": payload.get("system_name"), + "message": payload.get("message"), + "stats": { + "candidate_count": candidate_count, + "mapped_count": mapped_count, + "mapping_rate": mapping_rate, + "needs_mapping_count": needs_mapping_count, + "review_candidate_count": int(stats.get("review_candidate_count") or len(review_candidates)), + "latest_sales_date": stats.get("latest_sales_date"), + "overall_latest_sales_date": stats.get("overall_latest_sales_date"), + "overall_sales_7d": stats.get("overall_sales_7d"), + "opportunity_sales_7d": stats.get("opportunity_sales_7d"), + "action_counts": dict(stats.get("action_counts") or action_counts), + "action_code_counts": dict(stats.get("action_code_counts") or {}), + "external_data_source_counts": dict(stats.get("external_data_source_counts") or {}), + }, + "backlog": { + "direct_mapping_count": len(direct_mapping), + "review_candidate_count": len(review_candidates), + "mapped_opportunity_count": len(mapped), + "sales_by_action": sales_by_action, + "top_needs_mapping": [compact_mapping_item(item) for item in top_needs_mapping], + "top_review_candidates": [compact_mapping_item(item) for item in top_review_candidates], + }, + "next_actions": [ + "Run the production version truth guard before changing or deploying.", + "Handle direct mapping items first; they have no verified external price yet.", + "Review candidate items next; they already have MOMO candidates but need same-item confirmation.", + "Keep this report read-only until an explicit DB-write operator run is approved.", + ], + } + + +def build_pchome_mapping_operator_preview(payload: dict[str, Any], batch_size: int = 5) -> dict[str, Any]: + """Build a read-only operator run package for the direct mapping backlog.""" + summary = summarize_pchome_mapping_backlog(payload) + backlog = summary.get("backlog") or {} + direct_items = [ + item + for item in backlog.get("top_needs_mapping") or [] + if item.get("action_code") == "map_external_product" + ] + review_items = list(backlog.get("top_review_candidates") or []) + batch_size = max(1, min(int(batch_size or 5), 8)) + selected_direct = direct_items[:batch_size] + selected_review = review_items[:batch_size] + + if selected_direct: + result = "READY_FOR_OPERATOR_PREVIEW" + elif selected_review: + result = "REVIEW_CANDIDATES_ONLY" + else: + result = "NO_DIRECT_MAPPING_TARGETS" + + return { + "policy": OPERATOR_PREVIEW_POLICY, + "result": result, + "success": bool(summary.get("success")), + "generated_at": summary.get("generated_at"), + "stats": summary.get("stats") or {}, + "backlog": { + "direct_mapping_count": int(backlog.get("direct_mapping_count") or 0), + "review_candidate_count": int(backlog.get("review_candidate_count") or 0), + "mapped_opportunity_count": int(backlog.get("mapped_opportunity_count") or 0), + }, + "operator_batch": { + "batch_size": batch_size, + "selected_direct_mapping_count": len(selected_direct), + "selected_review_candidate_count": len(selected_review), + "direct_mapping_targets": selected_direct, + "review_candidate_targets": selected_review, + }, + "command_preview": { + "method": "POST", + "endpoint": "/api/ai/pchome-growth/backfill-momo-candidates", + "payload": {"limit": min(batch_size, 8)}, + "executes_search": True, + "writes_database": True, + "write_gate_required": True, + }, + "external_benchmark_alignment": _build_external_benchmark_alignment(), + "ai_automation_plan": _build_ai_automation_plan(selected_direct, selected_review), + "safety": { + "read_only_preview": True, + "executes_search": False, + "writes_database": False, + "dispatches_telegram": False, + "requires_production_version_truth": True, + "requires_operator_write_approval": True, + }, + "required_before_execute": [ + "Run production version truth guard and keep production /health as latest truth.", + "Confirm the selected direct mapping targets are the intended PChome products.", + "Confirm DB-write authorization for /api/ai/pchome-growth/backfill-momo-candidates.", + "Run post-write mapping backlog readback and compare direct_mapping_count / mapped_count.", + ], + "acceptance_criteria": [ + "direct_mapping_count decreases, or review_candidate_count increases with named MOMO candidates.", + "mapped_count or mapping_rate increases only when a verified external price is written.", + "No Gemini, Telegram dispatch, scheduler mutation, or unrelated DB write is part of this run.", + ], + } + + +def _build_direct_mapping_search_terms(product_name: str, max_terms: int) -> list[str]: + try: + from services.momo_crawler import build_targeted_momo_search_terms + + return build_targeted_momo_search_terms(product_name, max_terms=max_terms) + except Exception: + fallback = re.sub(r"\s+", " ", product_name or "").strip() + return [fallback] if fallback else [] + + +def _build_direct_mapping_search_target(target: dict[str, Any], max_terms: int) -> dict[str, Any]: + evidence = target.get("evidence_completeness") or {} + unit_basis = evidence.get("unit_package_basis") or {} + risk_signals = list(unit_basis.get("risk_signals") or []) + search_terms = _build_direct_mapping_search_terms(target.get("product_name") or "", max_terms) + return { + "pchome_product_id": target.get("pchome_product_id") or "", + "product_name": target.get("product_name") or "", + "product_url": target.get("product_url") or evidence.get("product_url"), + "pchome_price": target.get("pchome_price"), + "sales_7d": target.get("sales_7d"), + "priority_score": target.get("priority_score"), + "search_terms": search_terms, + "search_term_count": len(search_terms), + "identity_anchors": { + "stable_product_id": bool(target.get("pchome_product_id")), + "product_name_present": bool(target.get("product_name")), + "product_url_present": bool(target.get("product_url") or evidence.get("product_url")), + "price_present": target.get("pchome_price") not in (None, ""), + "unit_basis_present": bool(unit_basis), + "unit_package_basis": unit_basis, + "risk_signals": risk_signals, + "variant_sensitive": "variant_selection" in risk_signals, + "bundle_or_promo_sensitive": "bundle_or_promo" in risk_signals, + }, + "candidate_acceptance_gates": [ + "target_pchome_product_id_matches", + "target_match_score_meets_min_score", + "target_hard_veto_is_false", + "auto_compare_type_is_total_price_or_unit_price_for_auto_persistence", + "manual_review_candidates_route_to_machine_verifiable_decision_package", + "no_database_write_from_search_package", + ], + "can_execute_read_only_search": bool(search_terms), + "writes_database": False, + "persists_candidate": False, + } + + +def _search_candidates_by_target(candidates: list[dict[str, Any]]) -> dict[str, list[dict[str, Any]]]: + grouped: dict[str, list[dict[str, Any]]] = {} + for candidate in candidates: + target_id = str(candidate.get("target_pchome_product_id") or "").strip() + if not target_id: + target_id = "unknown" + grouped.setdefault(target_id, []).append(candidate) + return grouped + + +def _is_truthy_flag(value: Any) -> bool: + if isinstance(value, bool): + return value + if value in (None, ""): + return False + if isinstance(value, (int, float)): + return bool(value) + return str(value).strip().lower() in {"1", "true", "yes", "y", "on"} + + +def _build_candidate_decision_id(candidate: dict[str, Any]) -> str: + decision_basis = { + "target_pchome_product_id": candidate.get("target_pchome_product_id"), + "momo_product_id": candidate.get("product_id"), + "momo_name": candidate.get("name"), + "target_match_score": candidate.get("target_match_score"), + "auto_compare_type": candidate.get("auto_compare_type"), + } + digest = hashlib.sha256( + json.dumps(decision_basis, ensure_ascii=False, sort_keys=True, default=str).encode("utf-8") + ).hexdigest() + return f"pchome-direct-mapping-candidate-{digest[:16]}" + + +def _build_candidate_decision_envelope(candidate: dict[str, Any], min_score: float) -> dict[str, Any]: + confidence = _to_float(candidate.get("target_match_score")) + auto_compare_type = str(candidate.get("auto_compare_type") or "").strip() + hard_veto = _is_truthy_flag(candidate.get("target_hard_veto")) + target_id = str(candidate.get("target_pchome_product_id") or "").strip() + momo_product_id = str(candidate.get("product_id") or "").strip() + can_route_to_receipt = ( + bool(target_id) + and bool(momo_product_id) + and confidence >= min_score + and auto_compare_type in {"total_price", "unit_price"} + and not hard_veto + ) + failure_reasons = [] + if not target_id: + failure_reasons.append("missing_target_pchome_product_id") + if not momo_product_id: + failure_reasons.append("missing_momo_product_id") + if confidence < min_score: + failure_reasons.append("target_match_score_below_min_score") + if auto_compare_type not in {"total_price", "unit_price"}: + failure_reasons.append("auto_compare_type_not_receipt_ready") + if hard_veto: + failure_reasons.append("target_hard_veto_true") + + decision = ( + "route_to_no_write_auto_compare_receipt" + if can_route_to_receipt + else "route_to_machine_review_decision" + ) + return { + "decision_id": _build_candidate_decision_id(candidate), + "decision_type": "direct_mapping_candidate_decision", + "stage": "P2_machine_verifiable_candidate_decision", + "subject": { + "target_pchome_product_id": target_id, + "pchome_product_name": candidate.get("target_pchome_name") or candidate.get("target_product_name"), + "momo_product_id": momo_product_id, + "momo_product_name": candidate.get("name"), + "momo_price": candidate.get("price"), + }, + "decision": decision, + "confidence": confidence, + "data_quality": "ready_for_no_write_receipt" if can_route_to_receipt else "needs_machine_review", + "failure_reasons": failure_reasons, + "evidence": [ + {"key": "target_match_score", "value": candidate.get("target_match_score")}, + {"key": "min_score", "value": min_score}, + {"key": "auto_compare_type", "value": auto_compare_type}, + {"key": "target_hard_veto", "value": hard_veto}, + {"key": "target_price_basis", "value": candidate.get("target_price_basis")}, + {"key": "target_gap_pct", "value": candidate.get("target_gap_pct") or candidate.get("gap_pct")}, + {"key": "target_search_term", "value": candidate.get("target_search_term") or candidate.get("search_term")}, + ], + "recommended_action": ( + "build_no_write_auto_compare_receipt" + if can_route_to_receipt + else "build_machine_review_exception_receipt" + ), + "expected_impact": "reduce_direct_mapping_backlog_after_verified_persistence", + "guardrails": { + "machine_actionable": True, + "can_auto_execute": False, + "writes_database": False, + "persists_candidate": False, + "requires_no_write_receipt": True, + "requires_verifier_before_persistence": True, + "manual_review_mode": "exception_only", + }, + } + + +def build_pchome_direct_mapping_auto_search_package( + payload: dict[str, Any], + batch_size: int = 5, + *, + execute_search: bool = False, + limit_per_product: int = 8, + max_terms_per_product: int = 5, + min_score: float = 0.45, + search_func: Any = None, +) -> dict[str, Any]: + """Build a P1 no-write package for direct PChome-to-MOMO candidate search.""" + operator_preview = build_pchome_mapping_operator_preview(payload, batch_size=batch_size) + operator_batch = operator_preview.get("operator_batch") or {} + direct_targets = list(operator_batch.get("direct_mapping_targets") or []) + batch_size = int(operator_batch.get("batch_size") or batch_size or 5) + max_terms_per_product = max(1, min(int(max_terms_per_product or 5), 8)) + limit_per_product = max(1, min(int(limit_per_product or 8), 12)) + try: + min_score = max(0.35, min(float(min_score), 0.95)) + except (TypeError, ValueError): + min_score = 0.45 + + search_targets = [ + _build_direct_mapping_search_target(target, max_terms=max_terms_per_product) + for target in direct_targets + ] + read_only_targets = [ + { + "product_id": target.get("pchome_product_id"), + "name": target.get("product_name"), + "price": target.get("pchome_price"), + "sales_7d": target.get("sales_7d"), + "priority_score": target.get("priority_score"), + } + for target in direct_targets + ] + + search_success = None + search_message = "search_not_executed" + candidates: list[dict[str, Any]] = [] + if execute_search and read_only_targets: + if search_func is None: + from services.momo_crawler import search_momo_products_for_pchome_products + + search_func = search_momo_products_for_pchome_products + search_success, search_message, candidates = search_func( + read_only_targets, + limit_per_product=limit_per_product, + max_products=batch_size, + max_terms_per_product=max_terms_per_product, + min_score=min_score, + ) + candidates = list(candidates or []) + + auto_candidates = [ + candidate + for candidate in candidates + if candidate.get("auto_compare_type") in {"total_price", "unit_price"} + ] + review_candidates = [ + candidate + for candidate in candidates + if candidate.get("auto_compare_type") not in {"total_price", "unit_price"} + ] + grouped_candidates = _search_candidates_by_target(candidates) + for target in search_targets: + target["candidate_count"] = len(grouped_candidates.get(target.get("pchome_product_id") or "", [])) + target["candidate_ids"] = [ + str(candidate.get("product_id") or "") + for candidate in grouped_candidates.get(target.get("pchome_product_id") or "", [])[:5] + ] + + if not direct_targets: + result = "NO_DIRECT_MAPPING_TARGETS" + elif execute_search and candidates: + result = "DIRECT_MAPPING_CANDIDATES_FOUND" + elif execute_search: + result = "DIRECT_MAPPING_SEARCH_NO_CANDIDATES" + else: + result = "DIRECT_MAPPING_SEARCH_PACKAGE_READY" + + return { + "policy": DIRECT_MAPPING_AUTO_SEARCH_PACKAGE_POLICY, + "result": result, + "success": bool(operator_preview.get("success")), + "generated_at": operator_preview.get("generated_at"), + "source_policy": operator_preview.get("policy"), + "stats": operator_preview.get("stats") or {}, + "backlog": operator_preview.get("backlog") or {}, + "summary": { + "direct_mapping_count": int((operator_preview.get("backlog") or {}).get("direct_mapping_count") or 0), + "selected_direct_mapping_count": len(search_targets), + "search_ready_target_count": sum(1 for target in search_targets if target.get("can_execute_read_only_search")), + "planned_search_term_count": sum(int(target.get("search_term_count") or 0) for target in search_targets), + "execute_search_count": 1 if execute_search else 0, + "candidates_found_count": len(candidates), + "auto_compare_candidate_count": len(auto_candidates), + "review_candidate_count": len(review_candidates), + "writes_database_count": 0, + "persists_candidate_count": 0, + }, + "search_package": { + "stage": "P1_direct_mapping_auto_search", + "batch_size": batch_size, + "execute_search": bool(execute_search), + "limit_per_product": limit_per_product, + "max_terms_per_product": max_terms_per_product, + "min_score": min_score, + "targets": search_targets, + }, + "search_execution": { + "executed": bool(execute_search), + "search_success": search_success, + "search_message": search_message, + "candidate_count": len(candidates), + "auto_compare_candidate_count": len(auto_candidates), + "review_candidate_count": len(review_candidates), + "writes_database": False, + "syncs_external_offers": False, + }, + "candidate_acceptance_policy": { + "min_score": min_score, + "auto_compare_types": ["total_price", "unit_price"], + "requires_target_pchome_product_id": True, + "rejects_hard_veto": True, + "routes_manual_review_to_machine_verifiable_decision": True, + "writes_database": False, + }, + "candidate_preview": candidates[:20], + "next_actions": [ + "Execute controlled read-only MOMO search for the selected direct mapping targets.", + "Route auto-compare candidates into no-write receipt and verifier packages before persistence.", + "Route manual-review candidates into machine-verifiable candidate decision packages, not free-form human review.", + ], + "safety": { + "read_only_preview": True, + "executes_search": bool(execute_search), + "writes_database": False, + "persists_candidate": False, + "syncs_external_offers": False, + "dispatches_telegram": False, + "llm_calls_in_preview": False, + "gemini_allowed": False, + "requires_production_version_truth": True, + }, + } + + +def build_pchome_direct_mapping_candidate_decision_package( + payload: dict[str, Any], + batch_size: int = 5, + *, + execute_search: bool = False, + limit_per_product: int = 8, + max_terms_per_product: int = 5, + min_score: float = 0.45, + search_func: Any = None, +) -> dict[str, Any]: + """Build a P2 no-write machine-verifiable decision package for direct mapping candidates.""" + search_package = build_pchome_direct_mapping_auto_search_package( + payload, + batch_size=batch_size, + execute_search=execute_search, + limit_per_product=limit_per_product, + max_terms_per_product=max_terms_per_product, + min_score=min_score, + search_func=search_func, + ) + candidates = list(search_package.get("candidate_preview") or []) + effective_min_score = _to_float((search_package.get("candidate_acceptance_policy") or {}).get("min_score")) + decision_envelopes = [ + _build_candidate_decision_envelope(candidate, min_score=effective_min_score) + for candidate in candidates + ] + auto_compare_decisions = [ + envelope + for envelope in decision_envelopes + if envelope.get("decision") == "route_to_no_write_auto_compare_receipt" + ] + machine_review_decisions = [ + envelope + for envelope in decision_envelopes + if envelope.get("decision") == "route_to_machine_review_decision" + ] + + if not int((search_package.get("summary") or {}).get("selected_direct_mapping_count") or 0): + result = "NO_DIRECT_MAPPING_TARGETS" + elif decision_envelopes: + result = "DIRECT_MAPPING_CANDIDATE_DECISION_PACKAGE_READY" + else: + result = "WAITING_FOR_DIRECT_MAPPING_CANDIDATES" + + return { + "policy": DIRECT_MAPPING_CANDIDATE_DECISION_PACKAGE_POLICY, + "result": result, + "success": bool(search_package.get("success")), + "generated_at": search_package.get("generated_at"), + "source_policy": search_package.get("policy"), + "stats": search_package.get("stats") or {}, + "backlog": search_package.get("backlog") or {}, + "summary": { + "direct_mapping_count": int((search_package.get("summary") or {}).get("direct_mapping_count") or 0), + "selected_direct_mapping_count": int( + (search_package.get("summary") or {}).get("selected_direct_mapping_count") or 0 + ), + "candidates_found_count": len(candidates), + "candidate_decision_count": len(decision_envelopes), + "auto_compare_decision_count": len(auto_compare_decisions), + "machine_review_decision_count": len(machine_review_decisions), + "can_auto_persist_now_count": 0, + "writes_database_count": 0, + "persists_candidate_count": 0, + }, + "decision_package": { + "stage": "P2_machine_verifiable_candidate_decision", + "execute_search": bool(execute_search), + "candidate_decisions": decision_envelopes, + "manual_review_mode": "exception_only", + }, + "decision_acceptance_policy": { + "min_score": effective_min_score, + "auto_compare_types": ["total_price", "unit_price"], + "rejects_hard_veto": True, + "requires_target_pchome_product_id": True, + "requires_momo_product_id": True, + "routes_non_ready_candidates_to_machine_review_decision": True, + "writes_database": False, + }, + "candidate_source_preview": candidates, + "upstream_search_summary": search_package.get("summary") or {}, + "next_actions": [ + "Run controlled read-only search first when candidate_decision_count is zero.", + "Send auto-compare decisions to no-write receipt generation before any persistence.", + "Keep machine-review decisions as exception receipts with named failure reasons.", + ], + "safety": { + "read_only_preview": True, + "executes_search": bool(execute_search), + "writes_database": False, + "persists_candidate": False, + "syncs_external_offers": False, + "dispatches_telegram": False, + "llm_calls_in_preview": False, + "gemini_allowed": False, + "requires_production_version_truth": True, + }, + } + + +def build_pchome_evidence_enrichment_preview(payload: dict[str, Any], batch_size: int = 5) -> dict[str, Any]: + """Build a read-only evidence enrichment package for mapping targets.""" + operator_preview = build_pchome_mapping_operator_preview(payload, batch_size=batch_size) + operator_batch = operator_preview.get("operator_batch") or {} + direct_targets = list(operator_batch.get("direct_mapping_targets") or []) + review_targets = list(operator_batch.get("review_candidate_targets") or []) + evidence_tasks = [ + *[_build_evidence_task(target, "direct_mapping") for target in direct_targets], + *[_build_evidence_task(target, "review_candidate") for target in review_targets], + ] + tasks_with_blockers = [ + task + for task in evidence_tasks + if task.get("blocking_missing_fields") + ] + missing_field_counts: dict[str, int] = {} + for task in evidence_tasks: + for field in task.get("missing_fields") or []: + missing_field_counts[field] = missing_field_counts.get(field, 0) + 1 + + if tasks_with_blockers: + result = "NEEDS_EVIDENCE_ENRICHMENT" + elif evidence_tasks: + result = "EVIDENCE_PREVIEW_READY" + else: + result = "NO_TARGETS" + + return { + "policy": EVIDENCE_ENRICHMENT_PREVIEW_POLICY, + "result": result, + "success": bool(operator_preview.get("success")), + "generated_at": operator_preview.get("generated_at"), + "source_policy": operator_preview.get("policy"), + "stats": operator_preview.get("stats") or {}, + "summary": { + "task_count": len(evidence_tasks), + "tasks_with_blockers": len(tasks_with_blockers), + "missing_field_counts": missing_field_counts, + "auto_accept_ready_count": sum(1 for task in evidence_tasks if task.get("auto_accept_ready")), + LEGACY_HUMAN_REVIEW_REQUIRED_COUNT_KEY: 0, + "primary_human_gate_count": 0, + "ai_exception_required_count": sum(1 for task in evidence_tasks if task.get("ai_exception_required")), + }, + "evidence_tasks": evidence_tasks, + "external_benchmark_alignment": operator_preview.get("external_benchmark_alignment") or {}, + "ai_automation_plan": { + "policy": "ollama_first_read_only_evidence_assist", + "llm_calls_in_preview": False, + "gemini_allowed": False, + "can_execute_write": False, + "recommended_next_ai_task": "Generate deterministic identity anchors and evidence gap summaries after image and availability sources are wired.", + "blocked_until": [ + "image evidence source is wired", + "availability evidence source is wired", + "unit/package parser preview is compared against production title samples", + ], + }, + "safety": { + "read_only_preview": True, + "fetches_external_sites": False, + "writes_database": False, + "executes_search": False, + "dispatches_telegram": False, + "llm_calls_in_preview": False, + "requires_operator_write_approval": True, + }, + "next_actions": [ + "Wire read-only image and availability enrichment before expanding auto-accept.", + "Validate deterministic unit/package basis parsing for bundle-sensitive items.", + "Keep DB writes behind the existing /api/ai/pchome-growth/backfill-momo-candidates write gate.", + ], + } + + +def build_pchome_evidence_source_preview(payload: dict[str, Any], batch_size: int = 5) -> dict[str, Any]: + """Build a read-only source wiring preview for missing product evidence fields.""" + enrichment_preview = build_pchome_evidence_enrichment_preview(payload, batch_size=batch_size) + tasks = list(enrichment_preview.get("evidence_tasks") or []) + fields = ["image", "availability", "price", "unit_price_or_package_basis"] + field_counts = {} + source_plans = {} + for field in fields: + missing_tasks = [task for task in tasks if field in (task.get("missing_fields") or [])] + field_counts[field] = { + "missing_count": len(missing_tasks), + "present_count": max(len(tasks) - len(missing_tasks), 0), + "sample_missing_targets": [ + { + "pchome_product_id": task.get("pchome_product_id") or "", + "product_name": task.get("product_name") or "", + "product_url": task.get("product_url"), + "lane": task.get("lane"), + } + for task in missing_tasks[:3] + ], + } + plan = _source_plan_for_field(field, len(missing_tasks)) + if plan: + source_plans[field] = plan + + fetch_gate_candidates = _build_fetch_gate_candidates(tasks) + if field_counts["image"]["missing_count"] or field_counts["availability"]["missing_count"]: + result = "NEEDS_SOURCE_WIRING" + elif field_counts["price"]["missing_count"]: + result = "NEEDS_PAYLOAD_MAPPING_PROBE" + elif tasks: + result = "SOURCE_PREVIEW_READY" + else: + result = "NO_TARGETS" + + return { + "policy": EVIDENCE_SOURCE_PREVIEW_POLICY, + "result": result, + "success": bool(enrichment_preview.get("success")), + "generated_at": enrichment_preview.get("generated_at"), + "source_policy": enrichment_preview.get("policy"), + "stats": enrichment_preview.get("stats") or {}, + "summary": { + "task_count": len(tasks), + "field_counts": field_counts, + "fetch_gate_candidate_count": len(fetch_gate_candidates), + }, + "source_plans": source_plans, + "fetch_gate_candidates": fetch_gate_candidates, + "external_benchmark_alignment": enrichment_preview.get("external_benchmark_alignment") or {}, + "ai_automation_plan": { + "policy": "ollama_first_read_only_source_wiring_assist", + "llm_calls_in_preview": False, + "gemini_allowed": False, + "can_execute_fetch": False, + "can_execute_write": False, + "recommended_next_ai_task": "Generate schema-aware parsers for image and availability after fetch-gate tests are accepted.", + }, + "safety": { + "read_only_preview": True, + "fetches_external_sites": False, + "writes_database": False, + "executes_search": False, + "dispatches_telegram": False, + "llm_calls_in_preview": False, + "requires_production_version_truth": True, + "requires_fetch_gate_implementation_before_external_get": True, + }, + "next_actions": [ + "Wire image source preview from existing payload keys before adding a controlled product-page fetch gate.", + "Wire availability source preview from existing payload keys before adding a controlled Offer availability parser.", + "Probe missing price rows through current payload mapping before any external fetch or write.", + ], + } + + +def build_pchome_evidence_fetch_gate( + payload: dict[str, Any], + batch_size: int = 3, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + max_html_bytes: int = PCHOME_FETCH_MAX_HTML_BYTES, + http_get: Any = None, +) -> dict[str, Any]: + """Plan or execute a small read-only product-page evidence fetch gate.""" + batch_size = max(1, min(int(batch_size or 3), PCHOME_FETCH_MAX_BATCH_SIZE)) + timeout_seconds = max(1, min(int(timeout_seconds or PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS), 10)) + max_html_bytes = max(16_384, min(int(max_html_bytes or PCHOME_FETCH_MAX_HTML_BYTES), PCHOME_FETCH_MAX_HTML_BYTES)) + source_preview = build_pchome_evidence_source_preview(payload, batch_size=batch_size) + candidates = list(source_preview.get("fetch_gate_candidates") or [])[:batch_size] + receipts = [] + + for candidate in candidates: + product_url = candidate.get("product_url") + allowed = _is_allowed_pchome_product_url(product_url) + receipt = { + "pchome_product_id": candidate.get("pchome_product_id") or "", + "product_name": candidate.get("product_name") or "", + "product_url": product_url, + "fields": list(candidate.get("fields") or []), + "allowed_domain": PCHOME_FETCH_ALLOWED_DOMAIN, + "allowlist_passed": allowed, + "execute_fetch_requested": bool(execute_fetch), + "executed_fetch": False, + "writes_database": False, + "dispatches_telegram": False, + "llm_calls": False, + "parser_policy": PRODUCT_PAGE_EVIDENCE_PARSER_POLICY, + "status": "PLANNED", + } + + if not allowed: + receipt["status"] = "BLOCKED_BY_ALLOWLIST" + receipt["error"] = "product_url must be a PChome 24h /prod/ URL" + receipts.append(receipt) + continue + if not execute_fetch: + receipts.append(receipt) + continue + + try: + html, fetch_meta = _fetch_product_page_html( + product_url, + timeout_seconds=timeout_seconds, + max_html_bytes=max_html_bytes, + http_get=http_get, + ) + parsed = parse_pchome_product_page_evidence_html(html, product_url=product_url) + receipt.update( + { + "status": "FETCHED_WITH_EVIDENCE" + if parsed.get("image_url") or parsed.get("availability") + else "FETCHED_NO_STRUCTURED_EVIDENCE", + "executed_fetch": True, + "fetch_meta": fetch_meta, + "parsed_evidence": { + "image_url": parsed.get("image_url"), + "availability": parsed.get("availability"), + "availability_raw": parsed.get("availability_raw"), + "jsonld_product_found": parsed.get("jsonld_product_found"), + "jsonld_offer_found": parsed.get("jsonld_offer_found"), + "fallbacks_used": parsed.get("fallbacks_used") or [], + "parser_warnings": parsed.get("parser_warnings") or [], + }, + } + ) + except (requests.RequestException, ValueError, UnicodeError) as exc: + receipt["status"] = "FETCH_FAILED" + receipt["error"] = str(exc) + receipts.append(receipt) + + executed_count = sum(1 for receipt in receipts if receipt.get("executed_fetch")) + blocked_count = sum(1 for receipt in receipts if receipt.get("status") == "BLOCKED_BY_ALLOWLIST") + failed_count = sum(1 for receipt in receipts if receipt.get("status") == "FETCH_FAILED") + parsed_image_count = sum(1 for receipt in receipts if (receipt.get("parsed_evidence") or {}).get("image_url")) + parsed_availability_count = sum(1 for receipt in receipts if (receipt.get("parsed_evidence") or {}).get("availability")) + + if not candidates: + result = "NO_FETCH_CANDIDATES" + elif not execute_fetch: + result = "FETCH_GATE_PLANNED" + elif parsed_image_count or parsed_availability_count: + result = "FETCH_GATE_EXECUTED_WITH_EVIDENCE" + elif failed_count or blocked_count: + result = "FETCH_GATE_EXECUTED_WITH_BLOCKERS" + else: + result = "FETCH_GATE_EXECUTED_NO_EVIDENCE" + + return { + "policy": EVIDENCE_FETCH_GATE_POLICY, + "result": result, + "success": bool(source_preview.get("success")), + "generated_at": source_preview.get("generated_at"), + "source_policy": source_preview.get("policy"), + "stats": source_preview.get("stats") or {}, + "summary": { + "candidate_count": len(candidates), + "receipt_count": len(receipts), + "executed_fetch_count": executed_count, + "blocked_count": blocked_count, + "failed_count": failed_count, + "parsed_image_count": parsed_image_count, + "parsed_availability_count": parsed_availability_count, + "max_batch_size": PCHOME_FETCH_MAX_BATCH_SIZE, + }, + "fetch_config": { + "execute_fetch": bool(execute_fetch), + "allowed_domain": PCHOME_FETCH_ALLOWED_DOMAIN, + "timeout_seconds": timeout_seconds, + "max_html_bytes": max_html_bytes, + "method": "GET", + }, + "fetch_receipts": receipts, + "source_preview_summary": source_preview.get("summary") or {}, + "safety": { + "read_only_fetch_gate": True, + "writes_database": False, + "executes_search": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "requires_production_version_truth": True, + "requires_domain_allowlist": True, + "requires_html_size_cap": True, + "requires_operator_review_before_write": True, + }, + "next_actions": [ + "Review fetch receipts for image_url and availability before any write-gated mapping action.", + "Keep parsed evidence as preview evidence until an explicit evidence write path exists.", + "Investigate price payload gaps separately from product-page image and availability evidence.", + ], + } + + +def _merge_task_with_fetch_receipt(task: dict[str, Any], receipt: dict[str, Any] | None) -> dict[str, Any]: + present_fields = set(task.get("present_fields") or []) + missing_fields = set(task.get("missing_fields") or []) + blocking_missing_fields = set(task.get("blocking_missing_fields") or []) + parsed = (receipt or {}).get("parsed_evidence") or {} + evidence_delta = {} + + if parsed.get("image_url"): + present_fields.add("image") + missing_fields.discard("image") + blocking_missing_fields.discard("image") + evidence_delta["image_url"] = parsed.get("image_url") + if parsed.get("availability"): + present_fields.add("availability") + missing_fields.discard("availability") + blocking_missing_fields.discard("availability") + evidence_delta["availability"] = parsed.get("availability") + + if not receipt: + merge_status = "NO_FETCH_RECEIPT" + elif receipt.get("status") == "FETCHED_WITH_EVIDENCE" and evidence_delta: + merge_status = "MERGE_PREVIEW_READY" + elif receipt.get("status") == "PLANNED": + merge_status = "FETCH_GATE_PLANNED" + elif receipt.get("status") == "BLOCKED_BY_ALLOWLIST": + merge_status = "FETCH_GATE_BLOCKED" + elif receipt.get("status") == "FETCH_FAILED": + merge_status = "FETCH_GATE_FAILED" + else: + merge_status = "NO_MERGEABLE_EVIDENCE" + + if merge_status == "MERGE_PREVIEW_READY" and not blocking_missing_fields and evidence_delta: + automation_decision = "AUTO_ACCEPT_EVIDENCE_MERGE" + automation_allowed = True + exception_reason = None + elif merge_status == "FETCH_GATE_PLANNED": + automation_decision = "AUTO_RUN_FETCH_GATE" + automation_allowed = True + exception_reason = None + elif "price" in missing_fields: + automation_decision = "AUTO_PRICE_PAYLOAD_PROBE" + automation_allowed = True + exception_reason = "price_payload_gap" + elif merge_status in {"FETCH_GATE_BLOCKED", "FETCH_GATE_FAILED"}: + automation_decision = "AUTO_RETRY_OR_SOURCE_FALLBACK" + automation_allowed = True + exception_reason = merge_status.lower() + else: + automation_decision = "AUTO_CONTINUE_EVIDENCE_ENRICHMENT" + automation_allowed = True + exception_reason = None + + return { + "pchome_product_id": task.get("pchome_product_id") or "", + "product_name": task.get("product_name") or "", + "product_url": task.get("product_url"), + "lane": task.get("lane"), + "merge_status": merge_status, + "original_missing_fields": list(task.get("missing_fields") or []), + "merged_present_fields": sorted(present_fields), + "remaining_missing_fields": sorted(missing_fields), + "remaining_blocking_missing_fields": sorted(blocking_missing_fields), + "evidence_delta": evidence_delta, + "fetch_receipt_status": (receipt or {}).get("status"), + "automation_decision": automation_decision, + "automation_allowed": automation_allowed, + **_legacy_review_compatibility_fields(bool(exception_reason)), + "automation_exception_reason": exception_reason, + "writes_database": False, + "requires_operator_review": False, + } + + +def build_pchome_evidence_merge_preview( + payload: dict[str, Any], + batch_size: int = 3, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Preview how fetch receipts would change evidence completeness without writing.""" + enrichment_preview = build_pchome_evidence_enrichment_preview(payload, batch_size=batch_size) + fetch_gate = build_pchome_evidence_fetch_gate( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + receipt_by_id = { + receipt.get("pchome_product_id"): receipt + for receipt in fetch_gate.get("fetch_receipts") or [] + if receipt.get("pchome_product_id") + } + merge_items = [ + _merge_task_with_fetch_receipt(task, receipt_by_id.get(task.get("pchome_product_id"))) + for task in enrichment_preview.get("evidence_tasks") or [] + ] + merge_ready_count = sum(1 for item in merge_items if item.get("merge_status") == "MERGE_PREVIEW_READY") + auto_merge_ready_count = sum( + 1 for item in merge_items if item.get("automation_decision") == "AUTO_ACCEPT_EVIDENCE_MERGE" + ) + remaining_blocker_count = sum(1 for item in merge_items if item.get("remaining_blocking_missing_fields")) + automation_decision_counts: dict[str, int] = {} + for item in merge_items: + decision = item.get("automation_decision") or "UNKNOWN" + automation_decision_counts[decision] = automation_decision_counts.get(decision, 0) + 1 + + if merge_ready_count: + result = "MERGE_PREVIEW_READY" + elif fetch_gate.get("result") == "FETCH_GATE_PLANNED": + result = "FETCH_REQUIRED_FOR_MERGE_PREVIEW" + elif merge_items: + result = "NO_MERGEABLE_EVIDENCE" + else: + result = "NO_TARGETS" + + return { + "policy": EVIDENCE_MERGE_PREVIEW_POLICY, + "result": result, + "success": bool(enrichment_preview.get("success")), + "generated_at": enrichment_preview.get("generated_at"), + "source_policy": fetch_gate.get("policy"), + "stats": enrichment_preview.get("stats") or {}, + "summary": { + "task_count": len(merge_items), + "merge_ready_count": merge_ready_count, + "auto_merge_ready_count": auto_merge_ready_count, + "remaining_blocker_count": remaining_blocker_count, + "executed_fetch_count": (fetch_gate.get("summary") or {}).get("executed_fetch_count", 0), + "writes_database_count": 0, + AI_EXCEPTION_REQUIRED_COUNT_KEY: sum(1 for item in merge_items if item.get(AI_EXCEPTION_REQUIRED_KEY)), + LEGACY_REVIEW_REQUIRED_COUNT_KEY: 0, + LEGACY_REVIEW_MODE_KEY: LEGACY_REVIEW_MODE_EXCEPTION_ONLY, + "automation_decision_counts": automation_decision_counts, + }, + "merge_items": merge_items, + "fetch_gate_summary": fetch_gate.get("summary") or {}, + "safety": { + "read_only_merge_preview": True, + "writes_database": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_preview": False, + "requires_operator_review_before_write": False, + LEGACY_REVIEW_MODE_KEY: LEGACY_REVIEW_MODE_EXCEPTION_ONLY, + "requires_auto_policy_receipt_before_write": True, + }, + "next_actions": [ + "Auto-run fetch gate and merge preview for all eligible evidence candidates.", + "Keep price payload gaps in automated payload probes separate from image and availability evidence merge.", + "Persist evidence only through a future auto-policy receipt path, not by manual per-item review.", + ], + } + + +def _auto_policy_receipt_id(item: dict[str, Any]) -> str: + receipt_material = { + "policy": AUTO_POLICY_RECEIPT_GATE_POLICY, + "pchome_product_id": item.get("pchome_product_id") or "", + "automation_decision": item.get("automation_decision") or "", + "evidence_delta": item.get("evidence_delta") or {}, + "remaining_missing_fields": item.get("remaining_missing_fields") or [], + } + digest = hashlib.sha256(json.dumps(receipt_material, sort_keys=True, ensure_ascii=False).encode("utf-8")).hexdigest() + return f"pchome-evidence-{digest[:16]}" + + +def _build_auto_policy_receipt(item: dict[str, Any]) -> dict[str, Any]: + decision = item.get("automation_decision") or "AUTO_CONTINUE_EVIDENCE_ENRICHMENT" + if decision == "AUTO_ACCEPT_EVIDENCE_MERGE": + receipt_status = "READY_FOR_AUTO_PERSISTENCE" + elif decision == "AUTO_RUN_FETCH_GATE": + receipt_status = "READY_FOR_AUTO_FETCH" + else: + receipt_status = "READY_FOR_AUTO_FOLLOWUP" + return { + "receipt_id": _auto_policy_receipt_id(item), + "pchome_product_id": item.get("pchome_product_id") or "", + "product_name": item.get("product_name") or "", + "product_url": item.get("product_url"), + "lane": item.get("lane"), + "receipt_status": receipt_status, + "automation_decision": decision, + "automation_allowed": bool(item.get("automation_allowed")), + **_legacy_review_compatibility_fields(bool(item.get(AI_EXCEPTION_REQUIRED_KEY))), + "evidence_delta": item.get("evidence_delta") or {}, + "remaining_missing_fields": list(item.get("remaining_missing_fields") or []), + "remaining_blocking_missing_fields": list(item.get("remaining_blocking_missing_fields") or []), + "source_merge_status": item.get("merge_status"), + "source_fetch_receipt_status": item.get("fetch_receipt_status"), + "writes_database": False, + "persists_receipt": False, + "updates_mapping": False, + } + + +def build_pchome_auto_policy_receipt_gate( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Build no-write auto-policy receipts from evidence automation decisions.""" + merge_preview = build_pchome_evidence_merge_preview( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + receipts = [_build_auto_policy_receipt(item) for item in merge_preview.get("merge_items") or []] + receipt_status_counts: dict[str, int] = {} + decision_counts: dict[str, int] = {} + for receipt in receipts: + status = receipt.get("receipt_status") or "UNKNOWN" + decision = receipt.get("automation_decision") or "UNKNOWN" + receipt_status_counts[status] = receipt_status_counts.get(status, 0) + 1 + decision_counts[decision] = decision_counts.get(decision, 0) + 1 + + ready_for_auto_persistence_count = receipt_status_counts.get("READY_FOR_AUTO_PERSISTENCE", 0) + ai_exception_required_count = sum(1 for receipt in receipts if receipt.get(AI_EXCEPTION_REQUIRED_KEY)) + if ready_for_auto_persistence_count: + result = "AUTO_POLICY_RECEIPTS_READY" + elif receipts: + result = "AUTO_POLICY_RECEIPTS_PLANNED" + else: + result = "NO_AUTO_POLICY_RECEIPTS" + + return { + "policy": AUTO_POLICY_RECEIPT_GATE_POLICY, + "result": result, + "success": bool(merge_preview.get("success")), + "generated_at": merge_preview.get("generated_at"), + "source_policy": merge_preview.get("policy"), + "stats": merge_preview.get("stats") or {}, + "summary": { + "receipt_count": len(receipts), + "ready_for_auto_persistence_count": ready_for_auto_persistence_count, + "ready_for_auto_fetch_count": receipt_status_counts.get("READY_FOR_AUTO_FETCH", 0), + "ready_for_auto_followup_count": receipt_status_counts.get("READY_FOR_AUTO_FOLLOWUP", 0), + AI_EXCEPTION_REQUIRED_COUNT_KEY: ai_exception_required_count, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: 0, + "writes_database_count": 0, + "persists_receipt_count": 0, + "receipt_status_counts": receipt_status_counts, + "automation_decision_counts": decision_counts, + LEGACY_REVIEW_MODE_KEY: LEGACY_REVIEW_MODE_EXCEPTION_ONLY, + }, + "auto_policy_receipts": receipts, + "merge_preview_summary": merge_preview.get("summary") or {}, + "persistence_gate": { + "mode": "no_write_receipt_preview", + "future_write_target": "evidence_receipt_store", + "requires_auto_policy_receipt": True, + "writes_database": False, + "persists_receipt": False, + }, + "safety": { + "read_only_receipt_gate": True, + "writes_database": False, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use ready auto-policy receipts as the input contract for a future controlled persistence path.", + "Keep no-write receipt UI separate from evidence persistence until a write gate exists.", + "Route only receipt exceptions to human review; do not require per-item review for ready receipts.", + ], + } + + +def _automation_lane( + key: str, + label: str, + status: str, + value: int | float, + detail: str, + next_action: str, +) -> dict[str, Any]: + return { + "key": key, + "label": label, + "status": status, + "value": value, + "detail": detail, + "next_action": next_action, + "writes_database": False, + "ai_exception_mode": "machine_verifiable_auto_resolution", + } + + +def build_pchome_growth_ai_automation_readiness( + payload: dict[str, Any], + batch_size: int = 8, + *, + execute_search: bool = False, + execute_fetch: bool = False, +) -> dict[str, Any]: + """Build a single read-only product-facing AI automation readiness view.""" + mapping_summary = summarize_pchome_mapping_backlog(payload) + search_package = build_pchome_direct_mapping_auto_search_package( + payload, + batch_size=batch_size, + execute_search=execute_search, + ) + decision_package = build_pchome_direct_mapping_candidate_decision_package( + payload, + batch_size=batch_size, + execute_search=execute_search, + ) + receipt_gate = build_pchome_auto_policy_receipt_gate( + payload, + batch_size=12, + execute_fetch=execute_fetch, + ) + backlog = mapping_summary.get("backlog") or {} + search_summary = search_package.get("summary") or {} + decision_summary = decision_package.get("summary") or {} + receipt_summary = receipt_gate.get("summary") or {} + + direct_mapping_count = int(backlog.get("direct_mapping_count") or 0) + selected_search_targets = int(search_summary.get("selected_direct_mapping_count") or 0) + planned_search_terms = int(search_summary.get("planned_search_term_count") or 0) + candidate_decision_count = int(decision_summary.get("candidate_decision_count") or 0) + waiting_candidate_count = selected_search_targets if not candidate_decision_count else 0 + receipt_count = int(receipt_summary.get("receipt_count") or 0) + ready_receipt_count = int(receipt_summary.get("ready_for_auto_persistence_count") or 0) + exception_count = _summary_exception_count(receipt_summary) + int( + decision_summary.get("machine_review_decision_count") or 0 + ) + ai_exception_auto_resolution = { + "mode": AI_EXCEPTION_MODE_MACHINE_VERIFIABLE, + PRIMARY_HUMAN_GATE_COUNT_KEY: 0, + "ai_exception_count": exception_count, + "routes": [ + { + "source": "candidate_decision_package", + "condition": "not_ready_for_no_write_receipt", + "auto_resolution": "build_failure_reasons_and_next_machine_action", + }, + { + "source": "evidence_receipts", + "condition": "not_ready_for_auto_persistence", + "auto_resolution": "route_to_evidence_retry_or_verifier_blocker", + }, + ], + "writes_database": False, + } + + if not direct_mapping_count and ready_receipt_count: + result = "AI_AUTOMATION_READY_FOR_CONTROLLED_APPLY" + elif direct_mapping_count and selected_search_targets: + result = "AI_AUTOMATION_ACTIVE_WAITING_FOR_CANDIDATES" + elif receipt_count: + result = "AI_AUTOMATION_EVIDENCE_RECEIPTS_PLANNED" + else: + result = "AI_AUTOMATION_WAITING_FOR_GROWTH_INPUT" + + automation_lanes = [ + _automation_lane( + "gap_detection", + "缺口偵測", + "active", + direct_mapping_count, + f"mapping_rate={mapping_summary.get('stats', {}).get('mapping_rate')}%", + "優先補 direct mapping 缺口", + ), + _automation_lane( + "same_item_search_package", + "同款搜尋包", + "ready" if selected_search_targets else "waiting", + selected_search_targets, + f"{planned_search_terms} 組搜尋詞", + "執行 controlled read-only 搜尋候選", + ), + _automation_lane( + "candidate_decision_package", + "候選決策包", + "waiting" if waiting_candidate_count else ("ready" if candidate_decision_count else "planned"), + candidate_decision_count, + f"等待 {waiting_candidate_count} 筆候選" if waiting_candidate_count else "可輸出 decision envelope", + "將候選分流到 no-write receipt 或 AI 例外決策", + ), + _automation_lane( + "evidence_receipts", + "證據收據", + "ready" if receipt_count else "planned", + receipt_count, + f"可落地 {ready_receipt_count} · 例外 {exception_count}", + "只把 ready receipt 送入 verifier / dry-run persistence", + ), + _automation_lane( + "controlled_apply", + "受控落地", + "blocked_until_verifier", + 0, + "等待 verifier、rollback、readback", + "P1-P4 穩定後才進 P5/P6", + ), + ] + + return { + "policy": AI_AUTOMATION_READINESS_POLICY, + "result": result, + "success": bool(mapping_summary.get("success")), + "generated_at": mapping_summary.get("generated_at"), + "summary": { + "direct_mapping_count": direct_mapping_count, + "selected_search_target_count": selected_search_targets, + "planned_search_term_count": planned_search_terms, + "candidate_decision_count": candidate_decision_count, + "waiting_candidate_count": waiting_candidate_count, + "auto_compare_decision_count": int(decision_summary.get("auto_compare_decision_count") or 0), + "machine_review_decision_count": int(decision_summary.get("machine_review_decision_count") or 0), + "receipt_count": receipt_count, + "ready_receipt_count": ready_receipt_count, + "exception_count": exception_count, + "ai_exception_count": exception_count, + AI_EXCEPTION_REQUIRED_COUNT_KEY: exception_count, + PRIMARY_HUMAN_GATE_COUNT_KEY: 0, + LEGACY_PRIMARY_FLOW_COUNT_KEY: 0, + "writes_database_count": 0, + "external_network_execute_count": int(bool(execute_search)) + int(bool(execute_fetch)), + }, + "automation_lanes": automation_lanes, + "automation_policy": { + "primary_flow": "ai_controlled", + "human_primary_flow": False, + PRIMARY_HUMAN_GATE_COUNT_KEY: 0, + "exception_resolution": "ai_machine_verifiable", + "machine_verifiable_decision_required": True, + }, + "ai_exception_auto_resolution": ai_exception_auto_resolution, + "manual_policy": { + "deprecated_product_surface": True, + LEGACY_REVIEW_MODE_KEY: LEGACY_REVIEW_MODE_EXCEPTION_ONLY, + "manual_as_primary_flow": False, + "machine_verifiable_decision_required": True, + }, + "visible_product_commitment": [ + "AI 自動化狀態必須顯示在首頁 command center,不只藏在 API。", + "候選先進 decision envelope,再進 no-write receipt,不走自由格式審核。", + "例外也必須由 AI 自動產生 failure reasons、下一個機器動作與 rollback/readback 路徑。", + ], + "safety": { + "read_only_preview": True, + "executes_search": bool(execute_search), + "executes_fetch": bool(execute_fetch), + "writes_database": False, + "persists_receipt": False, + "dispatches_telegram": False, + "llm_calls_in_preview": False, + "gemini_allowed": False, + }, + } + + +def _receipt_payload_hash(receipt: dict[str, Any]) -> str: + payload = { + "receipt_id": receipt.get("receipt_id") or "", + "pchome_product_id": receipt.get("pchome_product_id") or "", + "automation_decision": receipt.get("automation_decision") or "", + "evidence_delta": receipt.get("evidence_delta") or {}, + } + return hashlib.sha256(json.dumps(payload, sort_keys=True, ensure_ascii=False).encode("utf-8")).hexdigest() + + +def _build_persistence_preview_item(receipt: dict[str, Any]) -> dict[str, Any]: + ready = receipt.get("receipt_status") == "READY_FOR_AUTO_PERSISTENCE" + payload_hash = _receipt_payload_hash(receipt) + target_receipt_table = "external_offer_evidence_receipts" + target_evidence_table = "external_offers" + if ready: + persistence_status = "DRY_RUN_READY" + planned_operation = "UPSERT_EVIDENCE_RECEIPT_AND_PATCH_EXTERNAL_OFFER_EVIDENCE" + else: + persistence_status = "WAITING_FOR_READY_RECEIPT" + planned_operation = "NOOP" + + return { + "receipt_id": receipt.get("receipt_id") or "", + "pchome_product_id": receipt.get("pchome_product_id") or "", + "product_url": receipt.get("product_url"), + "persistence_status": persistence_status, + "planned_operation": planned_operation, + "idempotency_key": receipt.get("receipt_id") or "", + "payload_hash": payload_hash, + "dedupe_keys": [ + "receipt_id", + "pchome_product_id", + "payload_hash", + ], + "target_tables": [ + target_receipt_table, + target_evidence_table, + ], + "transaction_preview": { + "begin_transaction": True, + "steps": [ + { + "name": "upsert_auto_policy_receipt", + "target_table": target_receipt_table, + "write_mode": "upsert_by_receipt_id", + "writes_database_in_preview": False, + }, + { + "name": "patch_external_offer_evidence", + "target_table": target_evidence_table, + "write_mode": "patch_image_url_stock_status_raw_payload", + "writes_database_in_preview": False, + }, + { + "name": "post_write_readback", + "target_table": target_receipt_table, + "readback_key": receipt.get("receipt_id") or "", + "writes_database_in_preview": False, + }, + ], + "commit": "future_apply_gate_only", + }, + "parameter_preview": { + "receipt_id": receipt.get("receipt_id") or "", + "pchome_product_id": receipt.get("pchome_product_id") or "", + "automation_decision": receipt.get("automation_decision") or "", + "image_url_present": bool((receipt.get("evidence_delta") or {}).get("image_url")), + "availability": (receipt.get("evidence_delta") or {}).get("availability"), + "payload_hash": payload_hash, + }, + "rollback_plan": [ + "delete auto_policy receipt row by receipt_id if inserted in future apply gate", + "restore prior external_offers image_url / stock_status / raw_payload_json from prewrite snapshot", + "rerun mapping backlog and evidence source readback after rollback", + ], + "post_write_verifier": [ + "receipt_id exists exactly once", + "payload_hash matches receipt evidence_delta", + "external_offers pchome_product_id has image_url and stock_status when receipt delta provided them", + f"mapping backlog readback does not increase {LEGACY_REVIEW_REQUIRED_COUNT_KEY}", + ], + "writes_database": False, + "persists_receipt": False, + "updates_mapping": False, + } + + +def build_pchome_auto_policy_persistence_gate( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Build a no-write persistence transaction contract from ready auto-policy receipts.""" + receipt_gate = build_pchome_auto_policy_receipt_gate( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + persistence_items = [ + _build_persistence_preview_item(receipt) + for receipt in receipt_gate.get("auto_policy_receipts") or [] + ] + dry_run_ready_count = sum(1 for item in persistence_items if item.get("persistence_status") == "DRY_RUN_READY") + waiting_count = sum(1 for item in persistence_items if item.get("persistence_status") == "WAITING_FOR_READY_RECEIPT") + + if dry_run_ready_count: + result = "PERSISTENCE_DRY_RUN_READY" + elif persistence_items: + result = "PERSISTENCE_WAITING_FOR_RECEIPTS" + else: + result = "NO_PERSISTENCE_ITEMS" + + return { + "policy": AUTO_POLICY_PERSISTENCE_GATE_POLICY, + "result": result, + "success": bool(receipt_gate.get("success")), + "generated_at": receipt_gate.get("generated_at"), + "source_policy": receipt_gate.get("policy"), + "stats": receipt_gate.get("stats") or {}, + "summary": { + "persistence_item_count": len(persistence_items), + "dry_run_ready_count": dry_run_ready_count, + "waiting_for_receipt_count": waiting_count, + "writes_database_count": 0, + "persists_receipt_count": 0, + "updates_mapping_count": 0, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: (receipt_gate.get("summary") or {}).get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "persistence_items": persistence_items, + "receipt_gate_summary": receipt_gate.get("summary") or {}, + "schema_contract": { + "target_receipt_table": "external_offer_evidence_receipts", + "requires_schema_migration_before_apply": True, + "required_columns": [ + "receipt_id", + "pchome_product_id", + "automation_decision", + "payload_hash", + "evidence_delta_json", + "created_at", + "applied_at", + "apply_status", + ], + "unique_keys": ["receipt_id"], + }, + "apply_gate": { + "mode": "dry_run_only", + "future_apply_endpoint": "/api/ai/pchome-growth/mapping-backlog/auto-policy-persistence-apply", + "requires_production_version_truth": True, + "requires_prewrite_snapshot": True, + "requires_post_write_readback": True, + "writes_database": False, + }, + "safety": { + "read_only_persistence_gate": True, + "writes_database": False, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Create schema migration preview for external_offer_evidence_receipts before any apply endpoint.", + "Add prewrite snapshot and post-write readback verifier before enabling persistence apply.", + "Keep ready receipts automated; route only verifier failures to exception review.", + ], + } + + +def _schema_migration_preview_id(schema_contract: dict[str, Any]) -> str: + payload = { + "target_receipt_table": schema_contract.get("target_receipt_table") or "", + "required_columns": schema_contract.get("required_columns") or [], + "unique_keys": schema_contract.get("unique_keys") or [], + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-schema-preview-{digest[:16]}" + + +def _build_schema_migration_preview(schema_contract: dict[str, Any]) -> dict[str, Any]: + target_table = schema_contract.get("target_receipt_table") or "external_offer_evidence_receipts" + migration_id = _schema_migration_preview_id(schema_contract) + ddl_preview = [ + f"CREATE TABLE IF NOT EXISTS {target_table} (", + " receipt_id TEXT PRIMARY KEY,", + " pchome_product_id TEXT NOT NULL,", + " automation_decision TEXT NOT NULL,", + " payload_hash TEXT NOT NULL,", + " evidence_delta_json JSONB NOT NULL DEFAULT '{}'::jsonb,", + " created_at TIMESTAMPTZ NOT NULL DEFAULT now(),", + " applied_at TIMESTAMPTZ,", + " apply_status TEXT NOT NULL DEFAULT 'previewed'", + ");", + ( + f"CREATE INDEX IF NOT EXISTS idx_{target_table}_pchome_product_id " + f"ON {target_table} (pchome_product_id);" + ), + ( + f"CREATE INDEX IF NOT EXISTS idx_{target_table}_payload_hash " + f"ON {target_table} (payload_hash);" + ), + ( + f"CREATE INDEX IF NOT EXISTS idx_{target_table}_apply_status " + f"ON {target_table} (apply_status);" + ), + ] + rollback_preview = [ + f"DROP INDEX IF EXISTS idx_{target_table}_apply_status;", + f"DROP INDEX IF EXISTS idx_{target_table}_payload_hash;", + f"DROP INDEX IF EXISTS idx_{target_table}_pchome_product_id;", + f"DROP TABLE IF EXISTS {target_table};", + ] + return { + "migration_id": migration_id, + "target_table": target_table, + "migration_mode": "future_migration_only", + "statement_count": len(ddl_preview), + "ddl_preview": ddl_preview, + "rollback_preview": rollback_preview, + "executes_sql": False, + "writes_database": False, + "requires_backup_before_apply": True, + "requires_migration_smoke": True, + } + + +def _build_prewrite_snapshot_contract(persistence_items: list[dict[str, Any]]) -> dict[str, Any]: + receipt_ids = [item.get("receipt_id") for item in persistence_items if item.get("receipt_id")] + product_ids = [item.get("pchome_product_id") for item in persistence_items if item.get("pchome_product_id")] + return { + "required": True, + "snapshot_mode": "future_apply_gate_only", + "artifact_path_template": "artifacts/pchome_growth/prewrite_snapshot/{run_id}.json", + "dedupe_keys": ["receipt_id", "pchome_product_id", "payload_hash"], + "target_receipt_ids": receipt_ids, + "target_pchome_product_ids": product_ids, + "query_preview": [ + { + "name": "receipt_prewrite_snapshot", + "sql": ( + "SELECT receipt_id, pchome_product_id, payload_hash, apply_status " + "FROM external_offer_evidence_receipts WHERE receipt_id = ANY(:receipt_ids)" + ), + "params": {"receipt_ids": receipt_ids}, + "executes_in_preview": False, + }, + { + "name": "external_offer_prewrite_snapshot", + "sql": ( + "SELECT pchome_product_id, image_url, stock_status, raw_payload_json " + "FROM external_offers WHERE pchome_product_id = ANY(:pchome_product_ids)" + ), + "params": {"pchome_product_ids": product_ids}, + "executes_in_preview": False, + }, + ], + "writes_database": False, + "executes_sql": False, + } + + +def _build_future_apply_verifier(persistence_items: list[dict[str, Any]]) -> dict[str, Any]: + receipt_ids = [item.get("receipt_id") for item in persistence_items if item.get("receipt_id")] + product_ids = [item.get("pchome_product_id") for item in persistence_items if item.get("pchome_product_id")] + return { + "mode": "future_apply_gate_only", + "required": True, + "verifier_count": 5, + "checks": [ + { + "name": "receipt_rows_are_idempotent", + "expected": "one row per receipt_id", + "query_preview": ( + "SELECT receipt_id, count(*) FROM external_offer_evidence_receipts " + "WHERE receipt_id = ANY(:receipt_ids) GROUP BY receipt_id" + ), + "params": {"receipt_ids": receipt_ids}, + "routes_failure_to": "exception_review", + }, + { + "name": "payload_hash_matches", + "expected": "stored payload_hash equals dry-run payload_hash", + "query_preview": ( + "SELECT receipt_id, payload_hash FROM external_offer_evidence_receipts " + "WHERE receipt_id = ANY(:receipt_ids)" + ), + "params": {"receipt_ids": receipt_ids}, + "routes_failure_to": "exception_review", + }, + { + "name": "external_offer_evidence_is_visible", + "expected": "image_url and stock_status are present when receipt delta provided them", + "query_preview": ( + "SELECT pchome_product_id, image_url, stock_status FROM external_offers " + "WHERE pchome_product_id = ANY(:pchome_product_ids)" + ), + "params": {"pchome_product_ids": product_ids}, + "routes_failure_to": "exception_review", + }, + { + "name": "mapping_backlog_does_not_regress", + "expected": f"{LEGACY_REVIEW_REQUIRED_COUNT_KEY} stays zero for ready receipts", + "query_preview": "rerun mapping backlog read-only service after future apply", + "params": {}, + "routes_failure_to": "exception_review", + }, + { + "name": "rollback_can_restore_snapshot", + "expected": "prewrite snapshot contains all touched receipt and product ids", + "query_preview": "validate snapshot artifact before commit in future apply gate", + "params": {}, + "routes_failure_to": "abort_apply_before_commit", + }, + ], + "executes_in_preview": False, + "writes_database": False, + "manual_review_mode": "exception_only", + } + + +def build_pchome_auto_policy_schema_migration_preview( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Build a no-write schema migration and future apply verifier contract.""" + persistence_gate = build_pchome_auto_policy_persistence_gate( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + persistence_items = list(persistence_gate.get("persistence_items") or []) + dry_run_items = [item for item in persistence_items if item.get("persistence_status") == "DRY_RUN_READY"] + waiting_items = [ + item for item in persistence_items + if item.get("persistence_status") == "WAITING_FOR_READY_RECEIPT" + ] + schema_contract = persistence_gate.get("schema_contract") or {} + schema_migration_preview = _build_schema_migration_preview(schema_contract) + prewrite_snapshot_contract = _build_prewrite_snapshot_contract(dry_run_items) + future_apply_verifier = _build_future_apply_verifier(dry_run_items) + + if dry_run_items: + future_apply_status = "APPLY_CONTRACT_READY" + elif persistence_items: + future_apply_status = "WAITING_FOR_DRY_RUN_READY_ITEMS" + else: + future_apply_status = "NO_PERSISTENCE_ITEMS" + + return { + "policy": AUTO_POLICY_SCHEMA_MIGRATION_PREVIEW_POLICY, + "result": "SCHEMA_MIGRATION_PREVIEW_READY", + "success": bool(persistence_gate.get("success")), + "generated_at": persistence_gate.get("generated_at"), + "source_policy": persistence_gate.get("policy"), + "stats": persistence_gate.get("stats") or {}, + "summary": { + "persistence_item_count": len(persistence_items), + "dry_run_ready_count": len(dry_run_items), + "waiting_for_receipt_count": len(waiting_items), + "schema_statement_count": schema_migration_preview["statement_count"], + "future_verifier_count": future_apply_verifier["verifier_count"], + "executes_migration_count": 0, + "writes_database_count": 0, + "persists_receipt_count": 0, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: (persistence_gate.get("summary") or {}).get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "schema_migration_preview": schema_migration_preview, + "prewrite_snapshot_contract": prewrite_snapshot_contract, + "future_apply_verifier": future_apply_verifier, + "future_apply_gate": { + "status": future_apply_status, + "mode": "future_controlled_apply_only", + "future_apply_endpoint": "/api/ai/pchome-growth/mapping-backlog/auto-policy-persistence-apply", + "requires_production_version_truth": True, + "requires_schema_migration_applied": True, + "requires_prewrite_snapshot": True, + "requires_post_write_readback": True, + "current_preview_apply_allowed": False, + "writes_database": False, + "executes_migration": False, + }, + "source_persistence_summary": persistence_gate.get("summary") or {}, + "external_benchmark_references": EXTERNAL_BENCHMARK_REFERENCES, + "safety": { + "read_only_schema_preview": True, + "executes_migration": False, + "executes_sql": False, + "writes_database": False, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Create a migration file only after this preview is accepted by tests and production version truth.", + "Keep prewrite snapshot and post-write verifier mandatory for the future apply endpoint.", + "Route only verifier failures to exception review; ready receipts remain automated.", + ], + } + + +def _build_migration_file_preview(schema_migration_preview: dict[str, Any]) -> dict[str, Any]: + target_table = schema_migration_preview.get("target_table") or "external_offer_evidence_receipts" + migration_number = "045" + migration_filename = f"migrations/{migration_number}_pchome_auto_policy_evidence_receipts.sql" + header = [ + "-- =============================================================================", + "-- Migration 045: PChome auto-policy evidence receipts", + "-- MOMO PRO / PChome revenue growth automation", + "-- 2026-06-28 Taipei", + "-- =============================================================================", + "-- Notes:", + "-- Additive only. This migration creates a receipt ledger for controlled", + "-- PChome evidence persistence. It does not drop, truncate, rewrite, or", + "-- backfill external_offers / competitor_* / product sales tables.", + "-- =============================================================================", + "", + ] + forward_sql_preview = ( + header + + list(schema_migration_preview.get("ddl_preview") or []) + + [ + "", + f"GRANT ALL PRIVILEGES ON {target_table} TO momo;", + "", + "DO $$", + "BEGIN", + " RAISE NOTICE 'Migration 045 complete: PChome auto-policy evidence receipts are ready';", + "END $$;", + ] + ) + forward_text = "\n".join(forward_sql_preview) + "\n" + rollback_sql_preview = [ + "-- Rollback preview only. Run only through an explicit future rollback gate.", + *list(schema_migration_preview.get("rollback_preview") or []), + ] + return { + "migration_number": migration_number, + "migration_filename": migration_filename, + "migration_title": "PChome auto-policy evidence receipts", + "target_table": target_table, + "file_write_mode": "preview_only", + "forward_sql_preview": forward_sql_preview, + "rollback_sql_preview": rollback_sql_preview, + "forward_sql_hash": hashlib.sha256(forward_text.encode("utf-8")).hexdigest(), + "line_count": len(forward_sql_preview), + "content_ends_with_newline": True, + "additive_only": True, + "forbidden_forward_tokens_absent": not any( + token in forward_text.upper() + for token in ["DROP ", "TRUNCATE ", "DELETE ", "ALTER TABLE external_offers"] + ), + "writes_file": False, + "executes_sql": False, + "writes_database": False, + } + + +def _build_future_apply_endpoint_verifier( + *, + schema_preview: dict[str, Any], + migration_file_preview: dict[str, Any], +) -> dict[str, Any]: + future_apply_gate = schema_preview.get("future_apply_gate") or {} + prewrite_snapshot = schema_preview.get("prewrite_snapshot_contract") or {} + future_verifier = schema_preview.get("future_apply_verifier") or {} + receipt_ids = list(prewrite_snapshot.get("target_receipt_ids") or []) + product_ids = list(prewrite_snapshot.get("target_pchome_product_ids") or []) + contract_ready = ( + future_apply_gate.get("status") == "APPLY_CONTRACT_READY" + and bool(receipt_ids) + and bool(product_ids) + and migration_file_preview.get("forbidden_forward_tokens_absent") is True + ) + return { + "endpoint": "/api/ai/pchome-growth/mapping-backlog/auto-policy-persistence-apply", + "method": "POST", + "contract_status": "APPLY_ENDPOINT_CONTRACT_READY" if contract_ready else "WAITING_FOR_APPLY_INPUTS", + "request_contract": { + "required_fields": [ + "run_id", + "production_version_truth", + "migration_filename", + "migration_hash", + "prewrite_snapshot_artifact", + "receipt_ids", + "payload_hashes", + "post_write_readback_required", + "rollback_artifact_required", + ], + "receipt_count": len(receipt_ids), + "product_count": len(product_ids), + "target_receipt_ids": receipt_ids, + "target_pchome_product_ids": product_ids, + "expected_migration_filename": migration_file_preview.get("migration_filename"), + "expected_migration_hash": migration_file_preview.get("forward_sql_hash"), + }, + "preflight_sequence": [ + "verify production /health version truth before opening a DB transaction", + "verify migration file hash equals migration file preview hash", + "create prewrite snapshot artifact for receipt and external offer rows", + "upsert auto-policy receipt rows by receipt_id", + "patch external_offers evidence fields from ready receipt deltas", + "run post-write verifier checks before marking apply_status applied", + ], + "abort_conditions": [ + "production version truth fails", + "migration hash mismatch", + "prewrite snapshot missing any target receipt or product id", + "post-write readback mismatch", + f"{LEGACY_REVIEW_REQUIRED_COUNT_KEY} increases above zero", + ], + "post_write_verifier_contract": future_verifier, + "rollback_contract": { + "required": True, + "mode": "future_rollback_gate_only", + "uses_prewrite_snapshot": True, + "target_receipt_ids": receipt_ids, + "target_pchome_product_ids": product_ids, + "steps": [ + "restore external_offers image_url / stock_status / raw_payload_json from prewrite snapshot", + "mark receipt apply_status as rolled_back by receipt_id", + "rerun mapping backlog read-only summary and verify no regression", + ], + "executes_in_preview": False, + "writes_database": False, + }, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "manual_review_mode": "exception_only", + } + + +def build_pchome_auto_policy_migration_file_preview( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Build a no-write migration file preview and future apply endpoint contract.""" + schema_preview = build_pchome_auto_policy_schema_migration_preview( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + migration_file_preview = _build_migration_file_preview( + schema_preview.get("schema_migration_preview") or {} + ) + future_apply_endpoint_verifier = _build_future_apply_endpoint_verifier( + schema_preview=schema_preview, + migration_file_preview=migration_file_preview, + ) + contract_ready = ( + future_apply_endpoint_verifier.get("contract_status") == "APPLY_ENDPOINT_CONTRACT_READY" + ) + return { + "policy": AUTO_POLICY_MIGRATION_FILE_PREVIEW_POLICY, + "result": "MIGRATION_FILE_PREVIEW_READY", + "success": bool(schema_preview.get("success")), + "generated_at": schema_preview.get("generated_at"), + "source_policy": schema_preview.get("policy"), + "stats": schema_preview.get("stats") or {}, + "summary": { + "persistence_item_count": (schema_preview.get("summary") or {}).get("persistence_item_count", 0), + "dry_run_ready_count": (schema_preview.get("summary") or {}).get("dry_run_ready_count", 0), + "schema_statement_count": (schema_preview.get("summary") or {}).get("schema_statement_count", 0), + "future_verifier_count": (schema_preview.get("summary") or {}).get("future_verifier_count", 0), + "migration_file_line_count": migration_file_preview.get("line_count", 0), + "apply_endpoint_contract_ready_count": 1 if contract_ready else 0, + "writes_file_count": 0, + "executes_migration_count": 0, + "executes_endpoint_count": 0, + "writes_database_count": 0, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: (schema_preview.get("summary") or {}).get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "migration_file_preview": migration_file_preview, + "future_apply_endpoint_verifier": future_apply_endpoint_verifier, + "source_schema_preview_summary": schema_preview.get("summary") or {}, + "future_apply_gate": schema_preview.get("future_apply_gate") or {}, + "safety": { + "read_only_migration_file_preview": True, + "writes_file": False, + "executes_endpoint": False, + "executes_migration": False, + "executes_sql": False, + "writes_database": False, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Promote this preview into a real migration file only after explicit migration-file apply approval.", + "Keep the future apply endpoint blocked until migration hash, snapshot, and readback contracts are present.", + "Route only apply verifier failures to exception review; ready receipts remain automated.", + ], + } + + +def _readiness_check(key: str, passed: bool, evidence: Any, failure_route: str) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _build_apply_readiness_checks(migration_preview: dict[str, Any]) -> list[dict[str, Any]]: + summary = migration_preview.get("summary") or {} + file_preview = migration_preview.get("migration_file_preview") or {} + endpoint = migration_preview.get("future_apply_endpoint_verifier") or {} + request_contract = endpoint.get("request_contract") or {} + rollback_contract = endpoint.get("rollback_contract") or {} + post_write_verifier = endpoint.get("post_write_verifier_contract") or {} + safety = migration_preview.get("safety") or {} + return [ + _readiness_check( + "production_version_truth_required", + True, + "production /health must pass immediately before future file generation and future apply", + "abort_before_file_generation", + ), + _readiness_check( + "ready_receipts_present", + int(summary.get("dry_run_ready_count") or 0) > 0, + {"dry_run_ready_count": summary.get("dry_run_ready_count", 0)}, + "wait_for_ready_receipts", + ), + _readiness_check( + "migration_file_preview_hash_present", + bool(file_preview.get("forward_sql_hash")), + { + "migration_filename": file_preview.get("migration_filename"), + "forward_sql_hash": file_preview.get("forward_sql_hash"), + }, + "regenerate_migration_file_preview", + ), + _readiness_check( + "migration_file_preview_additive_only", + file_preview.get("additive_only") is True + and file_preview.get("forbidden_forward_tokens_absent") is True, + { + "additive_only": file_preview.get("additive_only"), + "forbidden_forward_tokens_absent": file_preview.get("forbidden_forward_tokens_absent"), + }, + "block_until_sql_preview_reviewed", + ), + _readiness_check( + "future_apply_endpoint_contract_ready", + endpoint.get("contract_status") == "APPLY_ENDPOINT_CONTRACT_READY", + {"contract_status": endpoint.get("contract_status")}, + "wait_for_apply_endpoint_contract", + ), + _readiness_check( + "prewrite_snapshot_targets_present", + int(request_contract.get("receipt_count") or 0) > 0 + and int(request_contract.get("product_count") or 0) > 0, + { + "receipt_count": request_contract.get("receipt_count", 0), + "product_count": request_contract.get("product_count", 0), + }, + "wait_for_snapshot_targets", + ), + _readiness_check( + "post_write_verifier_present", + int(post_write_verifier.get("verifier_count") or 0) >= 5, + {"verifier_count": post_write_verifier.get("verifier_count", 0)}, + "block_until_verifier_contract_complete", + ), + _readiness_check( + "rollback_contract_present", + rollback_contract.get("required") is True and rollback_contract.get("uses_prewrite_snapshot") is True, + { + "required": rollback_contract.get("required"), + "uses_prewrite_snapshot": rollback_contract.get("uses_prewrite_snapshot"), + }, + "block_until_rollback_contract_complete", + ), + _readiness_check( + "preview_has_no_side_effects", + safety.get("writes_file") is False + and safety.get("executes_endpoint") is False + and safety.get("writes_database") is False, + { + "writes_file": safety.get("writes_file"), + "executes_endpoint": safety.get("executes_endpoint"), + "writes_database": safety.get("writes_database"), + }, + "block_until_preview_is_read_only", + ), + ] + + +def build_pchome_auto_policy_apply_readiness_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out no-write readiness before any real migration file or apply endpoint exists.""" + migration_preview = build_pchome_auto_policy_migration_file_preview( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + checks = _build_apply_readiness_checks(migration_preview) + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + current_preview_ready = not waiting_checks + if current_preview_ready: + result = "APPLY_READINESS_CLOSEOUT_READY" + closeout_status = "READY_FOR_MIGRATION_FILE_GENERATION_REQUEST" + elif int((migration_preview.get("summary") or {}).get("dry_run_ready_count") or 0) <= 0: + result = "APPLY_READINESS_WAITING_FOR_READY_RECEIPTS" + closeout_status = "WAITING_FOR_READY_RECEIPTS" + else: + result = "APPLY_READINESS_WAITING_FOR_CONTRACTS" + closeout_status = "WAITING_FOR_CONTRACTS" + + future_apply_blockers = [ + { + "key": "migration_file_not_written", + "status": "future_apply_blocker", + "resolution": "write migration file from the approved preview in a future file-generation step", + }, + { + "key": "migration_not_applied", + "status": "future_apply_blocker", + "resolution": "apply migration only through a separate migration apply gate", + }, + { + "key": "prewrite_snapshot_not_created", + "status": "future_apply_blocker", + "resolution": "create snapshot artifact inside the future apply endpoint preflight", + }, + { + "key": "post_write_readback_not_executed", + "status": "future_apply_blocker", + "resolution": "run post-write verifier after the future apply transaction", + }, + ] + + return { + "policy": AUTO_POLICY_APPLY_READINESS_CLOSEOUT_POLICY, + "result": result, + "success": bool(migration_preview.get("success")), + "generated_at": migration_preview.get("generated_at"), + "source_policy": migration_preview.get("policy"), + "stats": migration_preview.get("stats") or {}, + "summary": { + "readiness_check_count": len(checks), + "readiness_pass_count": passed_count, + "readiness_waiting_count": len(waiting_checks), + "current_preview_ready_count": 1 if current_preview_ready else 0, + "future_apply_blocker_count": len(future_apply_blockers), + "dry_run_ready_count": (migration_preview.get("summary") or {}).get("dry_run_ready_count", 0), + "migration_file_line_count": (migration_preview.get("summary") or {}).get("migration_file_line_count", 0), + "apply_endpoint_contract_ready_count": ( + migration_preview.get("summary") or {} + ).get("apply_endpoint_contract_ready_count", 0), + "writes_file_count": 0, + "executes_migration_count": 0, + "executes_endpoint_count": 0, + "writes_database_count": 0, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + migration_preview.get("summary") or {} + ).get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "closeout": { + "status": closeout_status, + "current_preview_ready": current_preview_ready, + "ready_for_migration_file_generation_request": current_preview_ready, + "ready_for_database_apply": False, + "ready_for_endpoint_execution": False, + "waiting_checks": waiting_checks, + "future_apply_blockers": future_apply_blockers, + "manual_review_mode": "exception_only", + }, + "readiness_checks": checks, + "migration_file_preview_summary": { + "migration_filename": (migration_preview.get("migration_file_preview") or {}).get("migration_filename"), + "forward_sql_hash": (migration_preview.get("migration_file_preview") or {}).get("forward_sql_hash"), + "line_count": (migration_preview.get("migration_file_preview") or {}).get("line_count", 0), + "forbidden_forward_tokens_absent": ( + migration_preview.get("migration_file_preview") or {} + ).get("forbidden_forward_tokens_absent"), + }, + "future_apply_endpoint_summary": { + "endpoint": ( + migration_preview.get("future_apply_endpoint_verifier") or {} + ).get("endpoint"), + "contract_status": ( + migration_preview.get("future_apply_endpoint_verifier") or {} + ).get("contract_status"), + "receipt_count": ( + (migration_preview.get("future_apply_endpoint_verifier") or {}).get("request_contract") or {} + ).get("receipt_count", 0), + "product_count": ( + (migration_preview.get("future_apply_endpoint_verifier") or {}).get("request_contract") or {} + ).get("product_count", 0), + }, + "source_migration_preview_summary": migration_preview.get("summary") or {}, + "safety": { + "read_only_apply_readiness_closeout": True, + "writes_file": False, + "executes_endpoint": False, + "executes_migration": False, + "executes_sql": False, + "writes_database": False, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to request a real migration file generation step without changing DB state.", + "Keep database apply blocked until migration file, migration apply gate, snapshot, and readback all exist.", + "Route only readiness failures to exception review; ready receipts remain automated.", + ], + } + + +def _migration_file_generation_request_id(closeout: dict[str, Any]) -> str: + payload = { + "policy": closeout.get("policy") or "", + "result": closeout.get("result") or "", + "migration_file_preview_summary": closeout.get("migration_file_preview_summary") or {}, + "future_apply_endpoint_summary": closeout.get("future_apply_endpoint_summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-migration-file-request-{digest[:16]}" + + +def build_pchome_auto_policy_migration_file_generation_request( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Build a no-write request package for generating the PChome migration file.""" + closeout = build_pchome_auto_policy_apply_readiness_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + migration_summary = closeout.get("migration_file_preview_summary") or {} + endpoint_summary = closeout.get("future_apply_endpoint_summary") or {} + closeout_ready = bool(closeout.get("closeout", {}).get("ready_for_migration_file_generation_request")) + request_id = _migration_file_generation_request_id(closeout) + required_artifacts = [ + { + "key": "migration_file_preview", + "source_endpoint": "/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-file-preview", + "required": True, + }, + { + "key": "apply_readiness_closeout", + "source_endpoint": "/api/ai/pchome-growth/mapping-backlog/auto-policy-apply-readiness-closeout", + "required": True, + }, + { + "key": "production_version_truth", + "source_command": "python scripts/ops/check_production_version_truth.py", + "required": True, + }, + { + "key": "post_generation_diff_check", + "source_command": "git diff --check", + "required": True, + }, + ] + file_generation_steps = [ + { + "name": "create_migration_file_from_preview", + "target_file": migration_summary.get("migration_filename"), + "content_source": "migration_file_preview.forward_sql_preview", + "expected_sha256": migration_summary.get("forward_sql_hash"), + "writes_file_in_preview": False, + }, + { + "name": "verify_generated_file_hash", + "target_file": migration_summary.get("migration_filename"), + "expected_sha256": migration_summary.get("forward_sql_hash"), + "writes_file_in_preview": False, + }, + { + "name": "run_static_safety_checks", + "checks": [ + "forbidden forward tokens remain absent", + "migration is additive only", + "no DROP / TRUNCATE / DELETE / destructive ALTER", + "production version truth still passes", + ], + "writes_file_in_preview": False, + }, + ] + request_status = "FILE_GENERATION_REQUEST_READY" if closeout_ready else "WAITING_FOR_APPLY_READINESS_CLOSEOUT" + return { + "policy": AUTO_POLICY_MIGRATION_FILE_GENERATION_REQUEST_POLICY, + "result": request_status, + "success": bool(closeout.get("success")), + "generated_at": closeout.get("generated_at"), + "source_policy": closeout.get("policy"), + "stats": closeout.get("stats") or {}, + "summary": { + "request_ready_count": 1 if closeout_ready else 0, + "required_artifact_count": len(required_artifacts), + "file_generation_step_count": len(file_generation_steps), + "future_apply_blocker_count": (closeout.get("summary") or {}).get("future_apply_blocker_count", 0), + "dry_run_ready_count": (closeout.get("summary") or {}).get("dry_run_ready_count", 0), + "migration_file_line_count": (closeout.get("summary") or {}).get("migration_file_line_count", 0), + "writes_file_count": 0, + "executes_migration_count": 0, + "executes_endpoint_count": 0, + "writes_database_count": 0, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: (closeout.get("summary") or {}).get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "file_generation_request": { + "request_id": request_id, + "status": request_status, + "ready_to_generate_file": closeout_ready, + "ready_for_database_apply": False, + "target_file": migration_summary.get("migration_filename"), + "expected_sha256": migration_summary.get("forward_sql_hash"), + "expected_line_count": migration_summary.get("line_count", 0), + "source_preview_endpoint": "/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-file-preview", + "source_closeout_endpoint": "/api/ai/pchome-growth/mapping-backlog/auto-policy-apply-readiness-closeout", + "required_artifacts": required_artifacts, + "file_generation_steps": file_generation_steps, + "writes_file_in_preview": False, + }, + "future_apply_endpoint_summary": endpoint_summary, + "future_apply_blockers": (closeout.get("closeout") or {}).get("future_apply_blockers") or [], + "source_closeout_summary": closeout.get("summary") or {}, + "safety": { + "read_only_file_generation_request": True, + "writes_file": False, + "executes_endpoint": False, + "executes_migration": False, + "executes_sql": False, + "writes_database": False, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Generate the migration file from this request only in a separate file-generation step.", + "Verify the generated file hash before any migration apply gate is considered.", + "Keep database apply blocked until the migration file exists and the apply gate has fresh readback.", + ], + } + + +def _read_generated_migration_file(target_file: str | None) -> dict[str, Any]: + repo_root = Path(__file__).resolve().parents[1] + relative_target = target_file or "migrations/045_pchome_auto_policy_evidence_receipts.sql" + migration_path = repo_root / relative_target + if not migration_path.exists(): + return { + "target_file": relative_target, + "exists": False, + "sha256": None, + "line_count": 0, + "ends_with_newline": False, + "forbidden_forward_tokens_absent": False, + "read_error": None, + } + try: + text = migration_path.read_text(encoding="utf-8") + except OSError as exc: + return { + "target_file": relative_target, + "exists": True, + "sha256": None, + "line_count": 0, + "ends_with_newline": False, + "forbidden_forward_tokens_absent": False, + "read_error": str(exc), + } + upper_text = text.upper() + forbidden_absent = not any( + token in upper_text + for token in ["DROP ", "TRUNCATE ", "DELETE ", "ALTER TABLE EXTERNAL_OFFERS"] + ) + return { + "target_file": relative_target, + "exists": True, + "sha256": hashlib.sha256(text.encode("utf-8")).hexdigest(), + "line_count": len(text.splitlines()), + "ends_with_newline": text.endswith("\n"), + "forbidden_forward_tokens_absent": forbidden_absent, + "read_error": None, + } + + +def _apply_gate_check(key: str, passed: bool, evidence: Any, failure_route: str) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def build_pchome_auto_policy_migration_apply_gate_preview( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Build a no-write apply gate preview for the generated PChome migration file.""" + request_package = build_pchome_auto_policy_migration_file_generation_request( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + file_request = request_package.get("file_generation_request") or {} + endpoint_summary = request_package.get("future_apply_endpoint_summary") or {} + generated_file = _read_generated_migration_file(file_request.get("target_file")) + expected_hash = file_request.get("expected_sha256") + hash_matches = bool(expected_hash and generated_file.get("sha256") == expected_hash) + request_ready = request_package.get("result") == "FILE_GENERATION_REQUEST_READY" + checks = [ + _apply_gate_check( + "production_version_truth_required", + True, + "production /health must pass immediately before any future migration apply", + "abort_before_apply_gate", + ), + _apply_gate_check( + "file_generation_request_ready", + request_ready, + {"result": request_package.get("result")}, + "wait_for_file_generation_request", + ), + _apply_gate_check( + "generated_migration_file_exists", + bool(generated_file.get("exists")), + {"target_file": generated_file.get("target_file")}, + "generate_migration_file_first", + ), + _apply_gate_check( + "generated_migration_file_hash_matches_request", + hash_matches, + { + "expected_sha256": expected_hash, + "actual_sha256": generated_file.get("sha256"), + }, + "regenerate_or_review_migration_file", + ), + _apply_gate_check( + "generated_migration_file_additive_only", + generated_file.get("forbidden_forward_tokens_absent") is True, + {"forbidden_forward_tokens_absent": generated_file.get("forbidden_forward_tokens_absent")}, + "block_until_sql_safety_reviewed", + ), + _apply_gate_check( + "future_apply_endpoint_contract_ready", + endpoint_summary.get("contract_status") == "APPLY_ENDPOINT_CONTRACT_READY", + endpoint_summary, + "wait_for_apply_endpoint_contract", + ), + _apply_gate_check( + "prewrite_snapshot_contract_required", + True, + "future apply endpoint must create prewrite snapshot before opening write transaction", + "abort_apply_without_snapshot", + ), + _apply_gate_check( + "post_apply_verifier_required", + True, + "future apply endpoint must run receipt, hash, external_offer, backlog, and rollback verifiers", + "abort_apply_without_verifier", + ), + _apply_gate_check( + "current_preview_has_no_db_side_effects", + True, + { + "executes_sql": False, + "writes_database": False, + "executes_endpoint": False, + }, + "block_until_preview_is_read_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + apply_preview_ready = not waiting_checks + result = "MIGRATION_APPLY_GATE_PREVIEW_READY" if apply_preview_ready else "MIGRATION_APPLY_GATE_WAITING" + apply_blockers = [ + { + "key": "migration_not_applied", + "status": "future_apply_blocker", + "resolution": "apply migration only through a separate explicit DB apply step", + }, + { + "key": "prewrite_snapshot_not_created", + "status": "future_apply_blocker", + "resolution": "create snapshot artifact inside the future apply endpoint preflight", + }, + { + "key": "post_apply_readback_not_executed", + "status": "future_apply_blocker", + "resolution": "run post-apply verifier after future migration transaction", + }, + ] + + return { + "policy": AUTO_POLICY_MIGRATION_APPLY_GATE_PREVIEW_POLICY, + "result": result, + "success": bool(request_package.get("success")), + "generated_at": request_package.get("generated_at"), + "source_policy": request_package.get("policy"), + "stats": request_package.get("stats") or {}, + "summary": { + "apply_gate_check_count": len(checks), + "apply_gate_pass_count": passed_count, + "apply_gate_waiting_count": len(waiting_checks), + "apply_preview_ready_count": 1 if apply_preview_ready else 0, + "generated_file_exists_count": 1 if generated_file.get("exists") else 0, + "generated_file_hash_matches_count": 1 if hash_matches else 0, + "future_apply_blocker_count": len(apply_blockers), + "dry_run_ready_count": (request_package.get("summary") or {}).get("dry_run_ready_count", 0), + "migration_file_line_count": generated_file.get("line_count", 0), + "executes_migration_count": 0, + "executes_endpoint_count": 0, + "writes_database_count": 0, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: (request_package.get("summary") or {}).get( + LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0 + ), + }, + "apply_gate": { + "status": "READY_FOR_EXPLICIT_DB_APPLY_REQUEST" if apply_preview_ready else "WAITING_FOR_APPLY_GATE_INPUTS", + "ready_for_explicit_db_apply_request": apply_preview_ready, + "ready_for_database_apply_now": False, + "target_file": generated_file.get("target_file"), + "expected_sha256": expected_hash, + "actual_sha256": generated_file.get("sha256"), + "hash_matches": hash_matches, + "waiting_checks": waiting_checks, + "future_apply_blockers": apply_blockers, + "manual_review_mode": "exception_only", + }, + "generated_migration_file": generated_file, + "apply_gate_checks": checks, + "future_apply_endpoint_summary": endpoint_summary, + "required_runtime_artifacts": [ + "fresh production version truth", + "prewrite snapshot artifact", + "post-apply readback verifier output", + "rollback artifact if verifier fails", + ], + "source_generation_request_summary": request_package.get("summary") or {}, + "safety": { + "read_only_migration_apply_gate_preview": True, + "writes_file": False, + "executes_endpoint": False, + "executes_migration": False, + "executes_sql": False, + "writes_database": False, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this preview to request an explicit DB apply step only when production truth is fresh.", + "Keep migration apply blocked until prewrite snapshot and post-apply verifier are wired.", + "Route only verifier failures to exception review; ready receipts remain automated.", + ], + } + + +def _db_apply_request_id(apply_preview: dict[str, Any]) -> str: + payload = { + "policy": apply_preview.get("policy") or "", + "result": apply_preview.get("result") or "", + "apply_gate": apply_preview.get("apply_gate") or {}, + "future_apply_endpoint_summary": apply_preview.get("future_apply_endpoint_summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-request-{digest[:16]}" + + +def build_pchome_auto_policy_db_apply_request_gate_preview( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Build a no-write explicit DB apply request gate preview.""" + apply_preview = build_pchome_auto_policy_migration_apply_gate_preview( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + apply_gate = apply_preview.get("apply_gate") or {} + generated_file = apply_preview.get("generated_migration_file") or {} + endpoint_summary = apply_preview.get("future_apply_endpoint_summary") or {} + request_ready = bool(apply_gate.get("ready_for_explicit_db_apply_request")) + request_status = "DB_APPLY_REQUEST_GATE_READY" if request_ready else "WAITING_FOR_MIGRATION_APPLY_GATE_PREVIEW" + request_id = _db_apply_request_id(apply_preview) + required_artifacts = [ + { + "key": "fresh_production_version_truth", + "source_command": "python scripts/ops/check_production_version_truth.py", + "required": True, + }, + { + "key": "generated_migration_file_hash", + "target_file": apply_gate.get("target_file"), + "expected_sha256": apply_gate.get("expected_sha256"), + "required": True, + }, + { + "key": "prewrite_snapshot_contract", + "source_endpoint": "/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-apply-gate-preview", + "required": True, + }, + { + "key": "post_apply_verifier_contract", + "source_endpoint": "/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-apply-gate-preview", + "required": True, + }, + { + "key": "rollback_artifact_contract", + "source_endpoint": "/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-apply-gate-preview", + "required": True, + }, + ] + command_preview = { + "executor": "future_operator_shell_only", + "command": "psql \"$DATABASE_URL\" -v ON_ERROR_STOP=1 -f migrations/045_pchome_auto_policy_evidence_receipts.sql", + "uses_secret_placeholder": True, + "reads_secret_in_preview": False, + "executes_in_preview": False, + "writes_database": False, + } + apply_sequence_preview = [ + { + "name": "refresh_production_version_truth", + "required": True, + "executes_in_preview": False, + }, + { + "name": "verify_migration_file_hash", + "expected_sha256": apply_gate.get("expected_sha256"), + "actual_sha256": apply_gate.get("actual_sha256"), + "required": True, + "executes_in_preview": False, + }, + { + "name": "create_prewrite_snapshot", + "artifact_path_template": "artifacts/pchome_growth/db_apply_prewrite_snapshot/{run_id}.json", + "required": True, + "executes_in_preview": False, + }, + { + "name": "execute_migration", + "command_preview": command_preview["command"], + "required": True, + "executes_in_preview": False, + }, + { + "name": "run_post_apply_verifier", + "checks": [ + "external_offer_evidence_receipts exists", + "receipt primary key is present", + "indexes exist for pchome_product_id / payload_hash / apply_status", + "momo privileges exist", + "mapping backlog read-only summary still works", + ], + "required": True, + "executes_in_preview": False, + }, + ] + abort_conditions = [ + "production version truth fails", + "migration file hash mismatch", + "prewrite snapshot cannot be created", + "post-apply verifier contract missing", + "rollback artifact contract missing", + "database credentials are not supplied by the future operator shell", + ] + + return { + "policy": AUTO_POLICY_DB_APPLY_REQUEST_GATE_PREVIEW_POLICY, + "result": request_status, + "success": bool(apply_preview.get("success")), + "generated_at": apply_preview.get("generated_at"), + "source_policy": apply_preview.get("policy"), + "stats": apply_preview.get("stats") or {}, + "summary": { + "request_ready_count": 1 if request_ready else 0, + "required_artifact_count": len(required_artifacts), + "apply_sequence_step_count": len(apply_sequence_preview), + "abort_condition_count": len(abort_conditions), + "generated_file_exists_count": (apply_preview.get("summary") or {}).get( + "generated_file_exists_count", 0 + ), + "generated_file_hash_matches_count": (apply_preview.get("summary") or {}).get( + "generated_file_hash_matches_count", 0 + ), + "dry_run_ready_count": (apply_preview.get("summary") or {}).get("dry_run_ready_count", 0), + "executes_migration_count": 0, + "executes_endpoint_count": 0, + "writes_database_count": 0, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: (apply_preview.get("summary") or {}).get( + LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0 + ), + }, + "db_apply_request_gate": { + "request_id": request_id, + "status": request_status, + "ready_for_explicit_db_apply_request": request_ready, + "ready_for_database_apply_now": False, + "target_file": apply_gate.get("target_file"), + "expected_sha256": apply_gate.get("expected_sha256"), + "actual_sha256": apply_gate.get("actual_sha256"), + "hash_matches": apply_gate.get("hash_matches"), + "required_artifacts": required_artifacts, + "command_preview": command_preview, + "apply_sequence_preview": apply_sequence_preview, + "abort_conditions": abort_conditions, + "future_apply_endpoint": endpoint_summary.get("endpoint"), + "manual_review_mode": "exception_only", + }, + "generated_migration_file": generated_file, + "source_apply_gate_summary": apply_preview.get("summary") or {}, + "required_runtime_readback": [ + "fresh production /health", + "schema catalog readback for external_offer_evidence_receipts", + "index catalog readback", + "privilege readback", + "mapping backlog read-only smoke", + ], + "rollback_gate_preview": { + "required": True, + "mode": "future_rollback_gate_only", + "uses_prewrite_snapshot": True, + "executes_in_preview": False, + "writes_database": False, + }, + "safety": { + "read_only_db_apply_request_gate_preview": True, + "reads_secret_in_preview": False, + "writes_file": False, + "executes_endpoint": False, + "executes_migration": False, + "executes_sql": False, + "writes_database": False, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this request gate only to prepare an explicit future DB apply operation.", + "Do not execute the psql command until production truth, snapshot, and verifier artifacts are fresh.", + "Route only verifier failures to exception review; ready receipts remain automated.", + ], + } + + +def _db_apply_execution_preflight_id(request_preview: dict[str, Any]) -> str: + payload = { + "policy": request_preview.get("policy") or "", + "result": request_preview.get("result") or "", + "db_apply_request_gate": request_preview.get("db_apply_request_gate") or {}, + "generated_migration_file": request_preview.get("generated_migration_file") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-preflight-{digest[:16]}" + + +def build_pchome_auto_policy_db_apply_execution_preflight( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Build a no-write execution preflight package before any real DB apply.""" + request_preview = build_pchome_auto_policy_db_apply_request_gate_preview( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + request_gate = request_preview.get("db_apply_request_gate") or {} + generated_file = request_preview.get("generated_migration_file") or {} + source_summary = request_preview.get("summary") or {} + request_ready = bool(request_gate.get("ready_for_explicit_db_apply_request")) + hash_matches = bool(request_gate.get("hash_matches")) + preflight_ready = request_ready and hash_matches + preflight_status = ( + "DB_APPLY_EXECUTION_PREFLIGHT_READY" + if preflight_ready + else "WAITING_FOR_DB_APPLY_REQUEST_GATE" + ) + target_file = request_gate.get("target_file") or generated_file.get("target_file") + expected_sha256 = request_gate.get("expected_sha256") or generated_file.get("sha256") + actual_sha256 = request_gate.get("actual_sha256") or generated_file.get("sha256") + preflight_id = _db_apply_execution_preflight_id(request_preview) + + snapshot_steps = [ + { + "key": "fresh_production_version_truth_snapshot", + "source_command": "python scripts/ops/check_production_version_truth.py", + "required": True, + "executes_in_preview": False, + }, + { + "key": "generated_migration_file_hash_snapshot", + "target_file": target_file, + "expected_sha256": expected_sha256, + "actual_sha256": actual_sha256, + "required": True, + "executes_in_preview": False, + }, + { + "key": "schema_catalog_prewrite_snapshot", + "sql_preview": ( + "SELECT to_regclass('public.external_offer_evidence_receipts') " + "AS existing_table;" + ), + "required": True, + "executes_sql_in_preview": False, + "writes_database": False, + }, + { + "key": "table_privilege_prewrite_snapshot", + "sql_preview": ( + "SELECT grantee, privilege_type FROM information_schema.table_privileges " + "WHERE table_name = 'external_offer_evidence_receipts';" + ), + "required": True, + "executes_sql_in_preview": False, + "writes_database": False, + }, + { + "key": "mapping_backlog_read_only_snapshot", + "source_endpoint": "/api/ai/pchome-growth/mapping-backlog", + "required": True, + "executes_endpoint_in_preview": False, + "writes_database": False, + }, + ] + readback_checks = [ + { + "key": "receipt_table_exists", + "sql_preview": "SELECT to_regclass('public.external_offer_evidence_receipts') IS NOT NULL AS ok;", + "required": True, + "executes_sql_in_preview": False, + }, + { + "key": "receipt_primary_key_exists", + "sql_preview": ( + "SELECT COUNT(*) FROM pg_indexes WHERE tablename = " + "'external_offer_evidence_receipts' AND indexname LIKE '%pkey%';" + ), + "required": True, + "executes_sql_in_preview": False, + }, + { + "key": "receipt_indexes_exist", + "sql_preview": ( + "SELECT indexname FROM pg_indexes WHERE tablename = " + "'external_offer_evidence_receipts';" + ), + "expected_index_count": 4, + "required": True, + "executes_sql_in_preview": False, + }, + { + "key": "momo_table_privilege_exists", + "sql_preview": ( + "SELECT privilege_type FROM information_schema.table_privileges " + "WHERE table_name = 'external_offer_evidence_receipts' AND grantee = 'momo';" + ), + "required": True, + "executes_sql_in_preview": False, + }, + { + "key": "mapping_backlog_read_only_smoke", + "source_endpoint": "/api/ai/pchome-growth/mapping-backlog", + "required": True, + "executes_endpoint_in_preview": False, + }, + { + "key": "db_apply_request_gate_regression_smoke", + "source_endpoint": "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-request-gate-preview", + "required": True, + "executes_endpoint_in_preview": False, + }, + ] + rollback_artifacts = [ + { + "key": "schema_migration_rollback_artifact", + "artifact_path_template": "artifacts/pchome_growth/db_apply_rollback/{run_id}.json", + "uses_prewrite_snapshot": True, + "rollback_sql_preview": [ + "DROP TABLE IF EXISTS external_offer_evidence_receipts;" + ], + "required": True, + "executes_sql_in_preview": False, + "writes_database": False, + } + ] + required_artifacts = [ + { + "key": "fresh_production_version_truth", + "source_command": "python scripts/ops/check_production_version_truth.py", + "required": True, + }, + { + "key": "db_apply_request_gate_preview", + "source_endpoint": "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-request-gate-preview", + "required": True, + }, + { + "key": "generated_migration_file_hash", + "target_file": target_file, + "expected_sha256": expected_sha256, + "required": True, + }, + { + "key": "prewrite_snapshot_artifact", + "artifact_path_template": "artifacts/pchome_growth/db_apply_prewrite_snapshot/{run_id}.json", + "required": True, + }, + { + "key": "post_apply_readback_artifact", + "artifact_path_template": "artifacts/pchome_growth/db_apply_readback/{run_id}.json", + "required": True, + }, + { + "key": "rollback_artifact", + "artifact_path_template": "artifacts/pchome_growth/db_apply_rollback/{run_id}.json", + "required": True, + }, + ] + abort_conditions = [ + "production version truth fails", + "DB apply request gate is not ready", + "migration file hash mismatch", + "future shell does not provide DATABASE_URL without exposing it to preview", + "prewrite snapshot artifact cannot be generated", + "post-apply readback artifact cannot be generated", + "rollback artifact cannot be generated", + "any preview step tries to execute SQL or write DB state", + ] + + return { + "policy": AUTO_POLICY_DB_APPLY_EXECUTION_PREFLIGHT_POLICY, + "result": preflight_status, + "success": bool(request_preview.get("success")), + "generated_at": request_preview.get("generated_at"), + "source_policy": request_preview.get("policy"), + "stats": request_preview.get("stats") or {}, + "summary": { + "preflight_ready_count": 1 if preflight_ready else 0, + "request_ready_count": 1 if request_ready else 0, + "required_artifact_count": len(required_artifacts), + "snapshot_plan_count": len(snapshot_steps), + "readback_plan_count": len(readback_checks), + "rollback_artifact_count": len(rollback_artifacts), + "abort_condition_count": len(abort_conditions), + "generated_file_exists_count": source_summary.get("generated_file_exists_count", 0), + "generated_file_hash_matches_count": source_summary.get( + "generated_file_hash_matches_count", 0 + ), + "dry_run_ready_count": source_summary.get("dry_run_ready_count", 0), + "reads_secret_count": 0, + "executes_migration_count": 0, + "executes_endpoint_count": 0, + "executes_sql_count": 0, + "writes_database_count": 0, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: source_summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "execution_preflight": { + "preflight_id": preflight_id, + "source_request_id": request_gate.get("request_id"), + "status": preflight_status, + "ready_for_preflight_artifact_generation": preflight_ready, + "ready_for_database_apply_now": False, + "target_file": target_file, + "expected_sha256": expected_sha256, + "actual_sha256": actual_sha256, + "hash_matches": hash_matches, + "fresh_production_truth_required": True, + "operator_secret_boundary": "future_shell_only", + "reads_secret_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "manual_review_mode": "exception_only", + }, + "required_artifacts": required_artifacts, + "prewrite_snapshot_plan": { + "required": True, + "mode": "future_apply_preflight_only", + "artifact_path_template": "artifacts/pchome_growth/db_apply_prewrite_snapshot/{run_id}.json", + "snapshot_steps": snapshot_steps, + "snapshot_step_count": len(snapshot_steps), + "executes_sql_in_preview": False, + "writes_database": False, + }, + "post_apply_readback_plan": { + "required": True, + "mode": "future_apply_readback_only", + "artifact_path_template": "artifacts/pchome_growth/db_apply_readback/{run_id}.json", + "readback_checks": readback_checks, + "readback_check_count": len(readback_checks), + "executes_sql_in_preview": False, + "writes_database": False, + }, + "rollback_artifact_plan": { + "required": True, + "mode": "future_rollback_gate_only", + "artifacts": rollback_artifacts, + "uses_prewrite_snapshot": True, + "executes_sql_in_preview": False, + "writes_database": False, + }, + "abort_conditions": abort_conditions, + "source_request_gate_summary": source_summary, + "safety": { + "read_only_db_apply_execution_preflight": True, + "reads_secret_in_preview": False, + "writes_file": False, + "writes_artifact_in_preview": False, + "executes_endpoint": False, + "executes_migration": False, + "executes_sql": False, + "writes_database": False, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Generate the prewrite snapshot artifact only in the future explicit DB apply run.", + "Run post-apply readback immediately after the future migration transaction.", + "Use rollback artifact generation for verifier failures; route only failed verifiers to exception review.", + ], + } + + +def _db_apply_authorization_package_id(preflight: dict[str, Any]) -> str: + payload = { + "policy": preflight.get("policy") or "", + "result": preflight.get("result") or "", + "execution_preflight": preflight.get("execution_preflight") or {}, + "summary": preflight.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-authorization-{digest[:16]}" + + +def _authorization_check(key: str, passed: bool, evidence: Any, failure_route: str) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def build_pchome_auto_policy_db_apply_authorization_package( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Build a no-write authorization package for a future explicit DB apply request.""" + preflight = build_pchome_auto_policy_db_apply_execution_preflight( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + summary = preflight.get("summary") or {} + execution = preflight.get("execution_preflight") or {} + snapshot_plan = preflight.get("prewrite_snapshot_plan") or {} + readback_plan = preflight.get("post_apply_readback_plan") or {} + rollback_plan = preflight.get("rollback_artifact_plan") or {} + safety = preflight.get("safety") or {} + + checks = [ + _authorization_check( + "execution_preflight_ready", + preflight.get("result") == "DB_APPLY_EXECUTION_PREFLIGHT_READY" + and bool(execution.get("ready_for_preflight_artifact_generation")), + { + "result": preflight.get("result"), + "ready_for_preflight_artifact_generation": execution.get( + "ready_for_preflight_artifact_generation" + ), + }, + "wait_for_execution_preflight", + ), + _authorization_check( + "db_apply_request_gate_ready", + int(summary.get("request_ready_count") or 0) == 1, + {"request_ready_count": summary.get("request_ready_count", 0)}, + "wait_for_db_apply_request_gate", + ), + _authorization_check( + "generated_migration_file_hash_matches", + bool(execution.get("hash_matches")), + { + "target_file": execution.get("target_file"), + "expected_sha256": execution.get("expected_sha256"), + "actual_sha256": execution.get("actual_sha256"), + }, + "regenerate_or_review_migration_file", + ), + _authorization_check( + "fresh_production_truth_required", + execution.get("fresh_production_truth_required") is True, + {"fresh_production_truth_required": execution.get("fresh_production_truth_required")}, + "abort_without_fresh_production_truth", + ), + _authorization_check( + "prewrite_snapshot_plan_complete", + snapshot_plan.get("required") is True and int(snapshot_plan.get("snapshot_step_count") or 0) >= 5, + {"snapshot_step_count": snapshot_plan.get("snapshot_step_count", 0)}, + "wait_for_prewrite_snapshot_plan", + ), + _authorization_check( + "post_apply_readback_plan_complete", + readback_plan.get("required") is True and int(readback_plan.get("readback_check_count") or 0) >= 6, + {"readback_check_count": readback_plan.get("readback_check_count", 0)}, + "wait_for_post_apply_readback_plan", + ), + _authorization_check( + "rollback_artifact_plan_complete", + rollback_plan.get("required") is True and rollback_plan.get("uses_prewrite_snapshot") is True, + { + "required": rollback_plan.get("required"), + "uses_prewrite_snapshot": rollback_plan.get("uses_prewrite_snapshot"), + }, + "wait_for_rollback_artifact_plan", + ), + _authorization_check( + "preview_reads_no_secret", + execution.get("reads_secret_in_preview") is False + and safety.get("reads_secret_in_preview") is False, + { + "execution_reads_secret_in_preview": execution.get("reads_secret_in_preview"), + "safety_reads_secret_in_preview": safety.get("reads_secret_in_preview"), + }, + "block_until_secret_boundary_is_clean", + ), + _authorization_check( + "preview_executes_no_sql", + execution.get("executes_sql_in_preview") is False + and safety.get("executes_sql") is False + and int(summary.get("executes_sql_count") or 0) == 0, + { + "execution_executes_sql_in_preview": execution.get("executes_sql_in_preview"), + "safety_executes_sql": safety.get("executes_sql"), + "executes_sql_count": summary.get("executes_sql_count", 0), + }, + "block_until_preview_is_no_sql", + ), + _authorization_check( + "preview_writes_no_database", + execution.get("writes_database_in_preview") is False + and safety.get("writes_database") is False + and int(summary.get("writes_database_count") or 0) == 0, + { + "execution_writes_database_in_preview": execution.get("writes_database_in_preview"), + "safety_writes_database": safety.get("writes_database"), + "writes_database_count": summary.get("writes_database_count", 0), + }, + "block_until_preview_is_no_db_write", + ), + _authorization_check( + "manual_review_regression_absent", + int(summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY) or 0) == 0, + {LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0)}, + "route_failed_receipts_to_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + package_ready = not waiting_checks + package_status = ( + "DB_APPLY_AUTHORIZATION_PACKAGE_READY" + if package_ready + else "WAITING_FOR_DB_APPLY_EXECUTION_PREFLIGHT" + ) + freshness_requirements = [ + { + "key": "production_truth_fresh_within_300_seconds", + "source_command": "python scripts/ops/check_production_version_truth.py", + "max_age_seconds": 300, + "required": True, + }, + { + "key": "migration_file_hash_checked_after_fresh_truth", + "target_file": execution.get("target_file"), + "expected_sha256": execution.get("expected_sha256"), + "required": True, + }, + { + "key": "prewrite_snapshot_created_in_same_run", + "artifact_path_template": "artifacts/pchome_growth/db_apply_prewrite_snapshot/{run_id}.json", + "required": True, + }, + { + "key": "post_apply_readback_created_in_same_run", + "artifact_path_template": "artifacts/pchome_growth/db_apply_readback/{run_id}.json", + "required": True, + }, + { + "key": "rollback_artifact_registered_in_same_run", + "artifact_path_template": "artifacts/pchome_growth/db_apply_rollback/{run_id}.json", + "required": True, + }, + ] + manifest_steps = [ + { + "name": "refresh_production_truth", + "source_command": "python scripts/ops/check_production_version_truth.py", + "required": True, + "executes_in_preview": False, + }, + { + "name": "generate_prewrite_snapshot_artifact", + "source_plan": "prewrite_snapshot_plan", + "required": True, + "executes_in_preview": False, + "writes_database": False, + }, + { + "name": "inject_database_url_from_future_shell", + "secret_boundary": "future_shell_only", + "reads_secret_in_preview": False, + "required": True, + "executes_in_preview": False, + }, + { + "name": "execute_migration_in_future_apply_run", + "command_preview": ( + 'psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f ' + "migrations/045_pchome_auto_policy_evidence_receipts.sql" + ), + "required": True, + "executes_in_preview": False, + "writes_database_in_preview": False, + }, + { + "name": "run_post_apply_readback_bundle", + "source_plan": "post_apply_readback_plan", + "required": True, + "executes_in_preview": False, + "writes_database": False, + }, + { + "name": "generate_rollback_artifact_if_verifier_fails", + "source_plan": "rollback_artifact_plan", + "required": True, + "executes_in_preview": False, + "writes_database": False, + }, + ] + verifier_bundle = { + "pre_apply_verifiers": [ + "production_truth_fresh_within_300_seconds", + "generated_migration_file_hash_matches", + "prewrite_snapshot_artifact_created", + ], + "post_apply_verifiers": [ + check.get("key") + for check in readback_plan.get("readback_checks", []) + ], + "failure_routes": [ + "abort_before_sql_if_pre_apply_verifier_fails", + "generate_rollback_artifact_if_post_apply_verifier_fails", + "route_failed_verifier_to_exception_review_only", + ], + "verifier_bundle_count": 3, + "executes_in_preview": False, + "writes_database": False, + } + + return { + "policy": AUTO_POLICY_DB_APPLY_AUTHORIZATION_PACKAGE_POLICY, + "result": package_status, + "success": bool(preflight.get("success")), + "generated_at": preflight.get("generated_at"), + "source_policy": preflight.get("policy"), + "stats": preflight.get("stats") or {}, + "summary": { + "authorization_check_count": len(checks), + "authorization_pass_count": passed_count, + "authorization_waiting_count": len(waiting_checks), + "authorization_package_ready_count": 1 if package_ready else 0, + "freshness_requirement_count": len(freshness_requirements), + "manifest_step_count": len(manifest_steps), + "verifier_bundle_count": verifier_bundle["verifier_bundle_count"], + "required_artifact_count": summary.get("required_artifact_count", 0), + "snapshot_plan_count": summary.get("snapshot_plan_count", 0), + "readback_plan_count": summary.get("readback_plan_count", 0), + "rollback_artifact_count": summary.get("rollback_artifact_count", 0), + "dry_run_ready_count": summary.get("dry_run_ready_count", 0), + "reads_secret_count": 0, + "executes_migration_count": 0, + "executes_endpoint_count": 0, + "executes_sql_count": 0, + "writes_database_count": 0, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "authorization_package": { + "package_id": _db_apply_authorization_package_id(preflight), + "source_preflight_id": execution.get("preflight_id"), + "source_request_id": execution.get("source_request_id"), + "status": package_status, + "ready_for_explicit_apply_authorization_request": package_ready, + "ready_for_database_apply_now": False, + "issue_scope": "future_apply_authorization_request_only", + "target_file": execution.get("target_file"), + "expected_sha256": execution.get("expected_sha256"), + "actual_sha256": execution.get("actual_sha256"), + "hash_matches": execution.get("hash_matches"), + "freshness_window_seconds": 300, + "requires_fresh_production_truth": True, + "operator_secret_boundary": "future_shell_only", + "reads_secret_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "manual_review_mode": "exception_only", + "waiting_checks": waiting_checks, + }, + "freshness_requirements": freshness_requirements, + "machine_apply_manifest": { + "run_id_template": "pchome-db-apply-{utc_timestamp}-{package_digest}", + "source_endpoint": "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-package", + "source_preflight_endpoint": "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-execution-preflight", + "manifest_steps": manifest_steps, + "manifest_step_count": len(manifest_steps), + "executes_in_preview": False, + "writes_database": False, + }, + "verifier_bundle": verifier_bundle, + "authorization_checks": checks, + "source_execution_preflight_summary": summary, + "safety": { + "read_only_db_apply_authorization_package": True, + "reads_secret_in_preview": False, + "writes_file": False, + "writes_artifact_in_preview": False, + "executes_endpoint": False, + "executes_migration": False, + "executes_sql": False, + "writes_database": False, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this package only to request a separate explicit DB apply authorization.", + "Keep future apply blocked unless production truth is refreshed inside the same run.", + "Let machines route failed verifier evidence to exception review; do not re-open manual batch review.", + ], + } + + +def _db_apply_verifier_artifact_preview_id(authorization_package: dict[str, Any]) -> str: + payload = { + "policy": authorization_package.get("policy") or "", + "result": authorization_package.get("result") or "", + "authorization_package": authorization_package.get("authorization_package") or {}, + "summary": authorization_package.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-artifacts-{digest[:16]}" + + +def build_pchome_auto_policy_db_apply_verifier_artifact_preview( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Build no-write artifact schemas for a future DB apply verifier run.""" + authorization_package = build_pchome_auto_policy_db_apply_authorization_package( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + authorization = authorization_package.get("authorization_package") or {} + summary = authorization_package.get("summary") or {} + authorization_ready = bool(authorization.get("ready_for_explicit_apply_authorization_request")) + preview_ready = authorization_ready and authorization_package.get("result") == "DB_APPLY_AUTHORIZATION_PACKAGE_READY" + preview_status = ( + "DB_APPLY_VERIFIER_ARTIFACT_PREVIEW_READY" + if preview_ready + else "WAITING_FOR_DB_APPLY_AUTHORIZATION_PACKAGE" + ) + preview_id = _db_apply_verifier_artifact_preview_id(authorization_package) + artifact_schemas = [ + { + "key": "prewrite_snapshot_artifact", + "artifact_type": "prewrite_snapshot", + "artifact_path_template": "artifacts/pchome_growth/db_apply_prewrite_snapshot/{run_id}.json", + "required_fields": [ + "run_id", + "authorization_package_id", + "production_truth", + "migration_file_hash", + "schema_catalog_before", + "table_privileges_before", + "mapping_backlog_summary_before", + "created_at", + "safety", + ], + "required": True, + "writes_artifact_in_preview": False, + "executes_sql_in_preview": False, + "writes_database": False, + }, + { + "key": "post_apply_readback_artifact", + "artifact_type": "post_apply_readback", + "artifact_path_template": "artifacts/pchome_growth/db_apply_readback/{run_id}.json", + "required_fields": [ + "run_id", + "authorization_package_id", + "receipt_table_exists", + "receipt_primary_key_exists", + "receipt_indexes_exist", + "momo_table_privilege_exists", + "mapping_backlog_read_only_smoke", + "db_apply_request_gate_regression_smoke", + "created_at", + "safety", + ], + "required": True, + "writes_artifact_in_preview": False, + "executes_sql_in_preview": False, + "writes_database": False, + }, + { + "key": "rollback_artifact", + "artifact_type": "rollback", + "artifact_path_template": "artifacts/pchome_growth/db_apply_rollback/{run_id}.json", + "required_fields": [ + "run_id", + "authorization_package_id", + "prewrite_snapshot_artifact", + "post_apply_readback_artifact", + "rollback_sql_preview", + "failure_reason", + "created_at", + "safety", + ], + "rollback_sql_preview": [ + "DROP TABLE IF EXISTS external_offer_evidence_receipts;" + ], + "required": True, + "writes_artifact_in_preview": False, + "executes_sql_in_preview": False, + "writes_database": False, + }, + ] + generation_steps = [ + { + "name": "create_fresh_run_id", + "run_id_template": "pchome-db-apply-{utc_timestamp}-{package_digest}", + "required": True, + "writes_artifact_in_preview": False, + }, + { + "name": "render_prewrite_snapshot_artifact_schema", + "artifact_key": "prewrite_snapshot_artifact", + "required": True, + "writes_artifact_in_preview": False, + }, + { + "name": "render_post_apply_readback_artifact_schema", + "artifact_key": "post_apply_readback_artifact", + "required": True, + "writes_artifact_in_preview": False, + }, + { + "name": "render_rollback_artifact_schema", + "artifact_key": "rollback_artifact", + "required": True, + "writes_artifact_in_preview": False, + }, + { + "name": "link_artifacts_to_authorization_package", + "authorization_package_id": authorization.get("package_id"), + "required": True, + "writes_artifact_in_preview": False, + }, + ] + verifier_manifest = { + "pre_apply_checks": [ + "production_truth_fresh_within_300_seconds", + "migration_file_hash_matches_authorization_package", + "prewrite_snapshot_artifact_schema_valid", + ], + "post_apply_checks": [ + "receipt_table_exists", + "receipt_primary_key_exists", + "receipt_indexes_exist", + "momo_table_privilege_exists", + "mapping_backlog_read_only_smoke", + "db_apply_request_gate_regression_smoke", + ], + "artifact_integrity_checks": [ + "all_artifacts_include_run_id", + "all_artifacts_include_authorization_package_id", + "rollback_artifact_references_prewrite_snapshot", + ], + "failure_handlers": [ + "abort_before_sql_if_pre_apply_check_fails", + "generate_rollback_artifact_if_post_apply_check_fails", + "route_failed_verifier_to_exception_review_only", + ], + "verifier_check_count": 15, + "executes_in_preview": False, + "writes_artifact_in_preview": False, + "writes_database": False, + } + + return { + "policy": AUTO_POLICY_DB_APPLY_VERIFIER_ARTIFACT_PREVIEW_POLICY, + "result": preview_status, + "success": bool(authorization_package.get("success")), + "generated_at": authorization_package.get("generated_at"), + "source_policy": authorization_package.get("policy"), + "stats": authorization_package.get("stats") or {}, + "summary": { + "artifact_preview_ready_count": 1 if preview_ready else 0, + "authorization_package_ready_count": summary.get("authorization_package_ready_count", 0), + "artifact_schema_count": len(artifact_schemas), + "artifact_generation_step_count": len(generation_steps), + "verifier_check_count": verifier_manifest["verifier_check_count"], + "freshness_requirement_count": summary.get("freshness_requirement_count", 0), + "manifest_step_count": summary.get("manifest_step_count", 0), + "required_artifact_count": summary.get("required_artifact_count", 0), + "dry_run_ready_count": summary.get("dry_run_ready_count", 0), + "writes_artifact_count": 0, + "reads_secret_count": 0, + "executes_migration_count": 0, + "executes_endpoint_count": 0, + "executes_sql_count": 0, + "writes_database_count": 0, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "artifact_preview": { + "preview_id": preview_id, + "source_authorization_package_id": authorization.get("package_id"), + "source_preflight_id": authorization.get("source_preflight_id"), + "source_request_id": authorization.get("source_request_id"), + "status": preview_status, + "ready_for_future_artifact_generation": preview_ready, + "ready_to_write_artifacts_now": False, + "ready_for_database_apply_now": False, + "target_file": authorization.get("target_file"), + "expected_sha256": authorization.get("expected_sha256"), + "actual_sha256": authorization.get("actual_sha256"), + "hash_matches": authorization.get("hash_matches"), + "writes_artifact_in_preview": False, + "reads_secret_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "manual_review_mode": "exception_only", + }, + "artifact_schemas": artifact_schemas, + "artifact_generation_plan": { + "mode": "future_apply_run_only", + "generation_steps": generation_steps, + "generation_step_count": len(generation_steps), + "writes_artifact_in_preview": False, + "executes_sql_in_preview": False, + "writes_database": False, + }, + "verifier_manifest": verifier_manifest, + "source_authorization_summary": summary, + "safety": { + "read_only_db_apply_verifier_artifact_preview": True, + "reads_secret_in_preview": False, + "writes_file": False, + "writes_artifact_in_preview": False, + "executes_endpoint": False, + "executes_migration": False, + "executes_sql": False, + "writes_database": False, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this preview to generate artifact writers only inside a separate future apply run.", + "Keep artifact generation no-op until fresh production truth and explicit DB apply authorization are present.", + "Let verifier artifacts drive exception routing instead of reopening manual batch review.", + ], + } + + +def _db_apply_final_handoff_package_id(artifact_preview: dict[str, Any]) -> str: + payload = { + "policy": artifact_preview.get("policy") or "", + "result": artifact_preview.get("result") or "", + "artifact_preview": artifact_preview.get("artifact_preview") or {}, + "summary": artifact_preview.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-final-handoff-{digest[:16]}" + + +def build_pchome_auto_policy_db_apply_final_handoff_package( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Build a no-write final handoff package for a future explicit DB apply.""" + artifact_preview = build_pchome_auto_policy_db_apply_verifier_artifact_preview( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + artifact = artifact_preview.get("artifact_preview") or {} + summary = artifact_preview.get("summary") or {} + artifact_ready = bool(artifact.get("ready_for_future_artifact_generation")) + final_ready = artifact_ready and artifact_preview.get("result") == "DB_APPLY_VERIFIER_ARTIFACT_PREVIEW_READY" + handoff_status = ( + "DB_APPLY_FINAL_HANDOFF_PACKAGE_READY" + if final_ready + else "WAITING_FOR_DB_APPLY_VERIFIER_ARTIFACT_PREVIEW" + ) + handoff_sections = [ + { + "key": "scope_boundary", + "title": "Future explicit DB apply only", + "summary": "This handoff prepares a future apply run; it does not execute SQL or read secrets.", + }, + { + "key": "production_truth", + "title": "Production health is the latest version truth", + "required_command": "python scripts/ops/check_production_version_truth.py", + }, + { + "key": "migration_file", + "title": "Generated migration file and hash", + "target_file": artifact.get("target_file"), + "expected_sha256": artifact.get("expected_sha256"), + "actual_sha256": artifact.get("actual_sha256"), + }, + { + "key": "verifier_artifacts", + "title": "Prewrite snapshot, readback, and rollback artifacts", + "source_endpoint": "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-verifier-artifact-preview", + }, + { + "key": "future_commands", + "title": "Commands are preview-only until a separate explicit apply run", + "secret_boundary": "future_shell_only", + }, + { + "key": "rollback_and_exception_routing", + "title": "Verifier failure goes to rollback artifact and exception review", + "manual_review_mode": "exception_only", + }, + ] + command_previews = [ + { + "name": "refresh_production_truth", + "command": "python scripts/ops/check_production_version_truth.py", + "required": True, + "executes_in_preview": False, + }, + { + "name": "execute_migration_future_shell_only", + "command": ( + 'psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f ' + "migrations/045_pchome_auto_policy_evidence_receipts.sql" + ), + "uses_secret_placeholder": True, + "reads_secret_in_preview": False, + "required": True, + "executes_in_preview": False, + "writes_database_in_preview": False, + }, + { + "name": "run_post_apply_verifier_bundle", + "source_endpoint": "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-verifier-artifact-preview", + "required": True, + "executes_in_preview": False, + "writes_database_in_preview": False, + }, + ] + final_runbook_steps = [ + { + "name": "confirm_fresh_production_truth", + "required": True, + "executes_in_preview": False, + }, + { + "name": "confirm_final_handoff_package_hash_and_ids", + "required": True, + "executes_in_preview": False, + }, + { + "name": "create_prewrite_snapshot_artifact_in_future_run", + "required": True, + "executes_in_preview": False, + "writes_artifact_in_preview": False, + }, + { + "name": "inject_database_url_from_future_shell_without_logging", + "required": True, + "reads_secret_in_preview": False, + "executes_in_preview": False, + }, + { + "name": "execute_migration_once", + "required": True, + "executes_in_preview": False, + "writes_database_in_preview": False, + }, + { + "name": "run_post_apply_readback_verifier", + "required": True, + "executes_in_preview": False, + "writes_database_in_preview": False, + }, + { + "name": "generate_rollback_artifact_on_verifier_failure", + "required": True, + "executes_in_preview": False, + "writes_artifact_in_preview": False, + }, + ] + abort_gates = [ + "production version truth fails or drifts", + "final handoff package is not ready", + "verifier artifact preview is not ready", + "migration file hash mismatch", + "DATABASE_URL would be exposed to preview, logs, or persisted artifacts", + "prewrite snapshot artifact cannot be generated in the future run", + "post-apply verifier artifact cannot be generated in the future run", + "rollback artifact cannot be generated in the future run", + "future psql command is requested outside a separate explicit DB apply authorization", + "any preview step attempts to execute SQL, write artifacts, or write database state", + ] + source_proof_manifest = { + "source_endpoint_chain": [ + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-request-gate-preview", + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-execution-preflight", + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-package", + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-verifier-artifact-preview", + ], + "source_request_id": artifact.get("source_request_id"), + "source_preflight_id": artifact.get("source_preflight_id"), + "source_authorization_package_id": artifact.get("source_authorization_package_id"), + "source_artifact_preview_id": artifact.get("preview_id"), + "expected_sha256": artifact.get("expected_sha256"), + "actual_sha256": artifact.get("actual_sha256"), + "hash_matches": artifact.get("hash_matches"), + "required_to_refresh_before_apply": True, + } + + return { + "policy": AUTO_POLICY_DB_APPLY_FINAL_HANDOFF_PACKAGE_POLICY, + "result": handoff_status, + "success": bool(artifact_preview.get("success")), + "generated_at": artifact_preview.get("generated_at"), + "source_policy": artifact_preview.get("policy"), + "stats": artifact_preview.get("stats") or {}, + "summary": { + "final_handoff_ready_count": 1 if final_ready else 0, + "artifact_preview_ready_count": summary.get("artifact_preview_ready_count", 0), + "handoff_section_count": len(handoff_sections), + "final_runbook_step_count": len(final_runbook_steps), + "command_preview_count": len(command_previews), + "abort_gate_count": len(abort_gates), + "source_endpoint_count": len(source_proof_manifest["source_endpoint_chain"]), + "artifact_schema_count": summary.get("artifact_schema_count", 0), + "artifact_generation_step_count": summary.get("artifact_generation_step_count", 0), + "verifier_check_count": summary.get("verifier_check_count", 0), + "required_artifact_count": summary.get("required_artifact_count", 0), + "dry_run_ready_count": summary.get("dry_run_ready_count", 0), + "writes_artifact_count": 0, + "reads_secret_count": 0, + "executes_migration_count": 0, + "executes_endpoint_count": 0, + "executes_sql_count": 0, + "writes_database_count": 0, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "final_handoff_package": { + "package_id": _db_apply_final_handoff_package_id(artifact_preview), + "source_artifact_preview_id": artifact.get("preview_id"), + "source_authorization_package_id": artifact.get("source_authorization_package_id"), + "source_preflight_id": artifact.get("source_preflight_id"), + "source_request_id": artifact.get("source_request_id"), + "status": handoff_status, + "ready_for_explicit_db_apply_handoff": final_ready, + "ready_for_database_apply_now": False, + "target_file": artifact.get("target_file"), + "expected_sha256": artifact.get("expected_sha256"), + "actual_sha256": artifact.get("actual_sha256"), + "hash_matches": artifact.get("hash_matches"), + "requires_fresh_production_truth": True, + "requires_separate_explicit_db_apply_authorization": True, + "explicit_authorization_boundary": "future_message_and_shell_only", + "operator_secret_boundary": "future_shell_only", + "reads_secret_in_preview": False, + "writes_artifact_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "manual_review_mode": "exception_only", + }, + "handoff_sections": handoff_sections, + "final_runbook_manifest": { + "mode": "future_explicit_apply_only", + "run_id_template": "pchome-db-apply-{utc_timestamp}-{handoff_digest}", + "steps": final_runbook_steps, + "step_count": len(final_runbook_steps), + "executes_in_preview": False, + "writes_artifact_in_preview": False, + "writes_database": False, + }, + "command_previews": command_previews, + "abort_gates": abort_gates, + "source_proof_manifest": source_proof_manifest, + "source_artifact_preview_summary": summary, + "safety": { + "read_only_db_apply_final_handoff_package": True, + "reads_secret_in_preview": False, + "writes_file": False, + "writes_artifact_in_preview": False, + "executes_endpoint": False, + "executes_migration": False, + "executes_sql": False, + "writes_database": False, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this final handoff only after a separate explicit DB apply authorization is given.", + "Refresh production truth and regenerate artifacts inside the same future apply run.", + "Keep failures machine-routed to rollback artifacts and exception review.", + ], + } + + +def _db_apply_controlled_dry_run_shell_preview_id(final_handoff: dict[str, Any]) -> str: + payload = { + "policy": final_handoff.get("policy") or "", + "result": final_handoff.get("result") or "", + "final_handoff_package": final_handoff.get("final_handoff_package") or {}, + "summary": final_handoff.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-dry-run-shell-{digest[:16]}" + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_shell_preview( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Build a no-write shell dry-run preview for a future explicit DB apply.""" + final_handoff = build_pchome_auto_policy_db_apply_final_handoff_package( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + handoff = final_handoff.get("final_handoff_package") or {} + source_summary = final_handoff.get("summary") or {} + handoff_ready = bool(handoff.get("ready_for_explicit_db_apply_handoff")) + dry_run_ready = handoff_ready and final_handoff.get("result") == "DB_APPLY_FINAL_HANDOFF_PACKAGE_READY" + dry_run_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_SHELL_PREVIEW_READY" + if dry_run_ready + else "WAITING_FOR_DB_APPLY_FINAL_HANDOFF_PACKAGE" + ) + shell_preview_id = _db_apply_controlled_dry_run_shell_preview_id(final_handoff) + shell_phases = [ + { + "name": "initialize_dry_run_context", + "required": True, + "executes_in_preview": False, + }, + { + "name": "refresh_production_truth_check_mode", + "command_preview": "python scripts/ops/check_production_version_truth.py", + "required": True, + "executes_in_preview": False, + }, + { + "name": "verify_final_handoff_source_chain", + "source_endpoint": "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-final-handoff-package", + "required": True, + "executes_in_preview": False, + }, + { + "name": "verify_migration_file_hash_check_mode", + "target_file": handoff.get("target_file"), + "expected_sha256": handoff.get("expected_sha256"), + "required": True, + "executes_in_preview": False, + }, + { + "name": "render_artifact_paths_check_mode", + "artifact_path_templates": [ + "artifacts/pchome_growth/db_apply_prewrite_snapshot/{run_id}.json", + "artifacts/pchome_growth/db_apply_readback/{run_id}.json", + "artifacts/pchome_growth/db_apply_rollback/{run_id}.json", + ], + "required": True, + "writes_artifact_in_preview": False, + "executes_in_preview": False, + }, + { + "name": "render_prewrite_snapshot_command_preview", + "required": True, + "writes_artifact_in_preview": False, + "executes_sql_in_preview": False, + "executes_in_preview": False, + }, + { + "name": "render_database_apply_command_preview", + "command_preview": ( + 'psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f ' + "migrations/045_pchome_auto_policy_evidence_receipts.sql" + ), + "uses_secret_placeholder": True, + "reads_secret_in_preview": False, + "required": True, + "executes_in_preview": False, + "writes_database_in_preview": False, + }, + { + "name": "render_post_apply_verifier_command_preview", + "required": True, + "executes_sql_in_preview": False, + "executes_in_preview": False, + "writes_database_in_preview": False, + }, + { + "name": "render_abort_and_rollback_hooks", + "required": True, + "writes_artifact_in_preview": False, + "executes_in_preview": False, + "writes_database_in_preview": False, + }, + ] + shell_script_preview = { + "filename": "scripts/ops/pchome_db_apply_controlled_dry_run.sh", + "mode": "future_script_preview_only", + "lines": [ + "#!/usr/bin/env bash", + "set -euo pipefail", + "DRY_RUN_ONLY=1", + ': "${RUN_ID:=pchome-db-apply-dry-run-preview}"', + "python scripts/ops/check_production_version_truth.py", + "printf '%s\\n' 'DRY RUN: verify final handoff source chain and migration hash'", + "printf '%s\\n' 'DRY RUN: would create prewrite snapshot artifact'", + "printf '%s\\n' 'DRY RUN: would require DATABASE_URL from future shell without logging it'", + "printf '%s\\n' 'DRY RUN: would execute psql \"$DATABASE_URL\" -v ON_ERROR_STOP=1 -f migrations/045_pchome_auto_policy_evidence_receipts.sql'", + "printf '%s\\n' 'DRY RUN: would run post-apply verifier bundle and rollback hooks'", + ], + "line_count": 10, + "writes_file_in_preview": False, + "executes_script_in_preview": False, + "reads_secret_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + } + check_mode_contract = { + "required_checks": [ + "production_truth_passes", + "final_handoff_package_ready", + "migration_file_hash_matches", + "artifact_path_templates_render", + "database_url_not_read_in_preview", + "all_future_write_commands_remain_preview_only", + ], + "required_check_count": 6, + "dry_run_only": True, + "executes_in_preview": False, + "writes_database": False, + } + rollback_hook_preview = { + "hooks": [ + { + "name": "abort_before_sql_on_precheck_failure", + "required": True, + "executes_in_preview": False, + }, + { + "name": "generate_rollback_artifact_on_post_apply_failure", + "required": True, + "writes_artifact_in_preview": False, + "executes_in_preview": False, + }, + { + "name": "route_exception_review_with_artifact_ids", + "required": True, + "executes_in_preview": False, + }, + ], + "hook_count": 3, + "writes_artifact_in_preview": False, + "writes_database": False, + } + + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_SHELL_PREVIEW_POLICY, + "result": dry_run_status, + "success": bool(final_handoff.get("success")), + "generated_at": final_handoff.get("generated_at"), + "source_policy": final_handoff.get("policy"), + "stats": final_handoff.get("stats") or {}, + "summary": { + "dry_run_shell_preview_ready_count": 1 if dry_run_ready else 0, + "final_handoff_ready_count": source_summary.get("final_handoff_ready_count", 0), + "shell_phase_count": len(shell_phases), + "shell_script_line_count": shell_script_preview["line_count"], + "check_mode_required_check_count": check_mode_contract["required_check_count"], + "rollback_hook_count": rollback_hook_preview["hook_count"], + "command_preview_count": source_summary.get("command_preview_count", 0), + "abort_gate_count": source_summary.get("abort_gate_count", 0), + "artifact_schema_count": source_summary.get("artifact_schema_count", 0), + "verifier_check_count": source_summary.get("verifier_check_count", 0), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: source_summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "controlled_dry_run_shell_preview": { + "preview_id": shell_preview_id, + "source_final_handoff_package_id": handoff.get("package_id"), + "source_artifact_preview_id": handoff.get("source_artifact_preview_id"), + "source_authorization_package_id": handoff.get("source_authorization_package_id"), + "source_preflight_id": handoff.get("source_preflight_id"), + "source_request_id": handoff.get("source_request_id"), + "status": dry_run_status, + "ready_for_future_shell_script_generation": dry_run_ready, + "ready_to_write_script_now": False, + "ready_to_execute_shell_now": False, + "ready_for_database_apply_now": False, + "target_file": handoff.get("target_file"), + "expected_sha256": handoff.get("expected_sha256"), + "actual_sha256": handoff.get("actual_sha256"), + "hash_matches": handoff.get("hash_matches"), + "dry_run_only": True, + "operator_secret_boundary": "future_shell_only", + "reads_secret_in_preview": False, + "writes_script_in_preview": False, + "writes_artifact_in_preview": False, + "executes_script_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "manual_review_mode": "exception_only", + }, + "shell_phases": shell_phases, + "shell_script_preview": shell_script_preview, + "check_mode_contract": check_mode_contract, + "rollback_hook_preview": rollback_hook_preview, + "source_final_handoff_summary": source_summary, + "safety": { + "read_only_db_apply_controlled_dry_run_shell_preview": True, + "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, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this preview to generate a future dry-run shell script only after separate apply authorization.", + "Keep DATABASE_URL in the future shell boundary and out of previews, logs, and artifacts.", + "Keep real SQL execution blocked until dry-run shell output is refreshed with production truth.", + ], + } + + +def _db_apply_controlled_dry_run_shell_closeout_id(shell_preview: dict[str, Any]) -> str: + payload = { + "policy": shell_preview.get("policy") or "", + "result": shell_preview.get("result") or "", + "controlled_dry_run_shell_preview": shell_preview.get("controlled_dry_run_shell_preview") or {}, + "summary": shell_preview.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-dry-run-closeout-{digest[:16]}" + + +def _db_apply_authorization_request_intake_id(closeout: dict[str, Any]) -> str: + payload = { + "policy": closeout.get("policy") or "", + "result": closeout.get("result") or "", + "explicit_authorization_boundary": closeout.get("explicit_authorization_boundary") or {}, + "summary": closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-authorization-intake-{digest[:16]}" + + +def _db_apply_authorization_request_closeout_id(intake: dict[str, Any]) -> str: + payload = { + "policy": intake.get("policy") or "", + "result": intake.get("result") or "", + "authorization_request_intake": intake.get("authorization_request_intake") or {}, + "authorization_envelope": intake.get("authorization_envelope") or {}, + "summary": intake.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-authorization-closeout-{digest[:16]}" + + +def _db_apply_authorization_lane_guard_id(closeout: dict[str, Any]) -> str: + payload = { + "policy": closeout.get("policy") or "", + "result": closeout.get("result") or "", + "final_exact_request_package": closeout.get("final_exact_request_package") or {}, + "machine_request_manifest": closeout.get("machine_request_manifest") or {}, + "summary": closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-authorization-lane-{digest[:16]}" + + +def _db_apply_authorization_decision_preflight_id(lane_guard: dict[str, Any]) -> str: + payload = { + "policy": lane_guard.get("policy") or "", + "result": lane_guard.get("result") or "", + "future_authorization_lane_guard": lane_guard.get("future_authorization_lane_guard") or {}, + "lane_transfer_contract": lane_guard.get("lane_transfer_contract") or {}, + "summary": lane_guard.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-authorization-decision-{digest[:16]}" + + +def _db_apply_authorization_decision_closeout_id(preflight: dict[str, Any]) -> str: + payload = { + "policy": preflight.get("policy") or "", + "result": preflight.get("result") or "", + "future_authorization_decision_preflight": ( + preflight.get("future_authorization_decision_preflight") or {} + ), + "decision_preflight_envelope": preflight.get("decision_preflight_envelope") or {}, + "summary": preflight.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-authorization-decision-closeout-{digest[:16]}" + + +def _db_apply_authorization_issuer_gate_id(closeout: dict[str, Any]) -> str: + payload = { + "policy": closeout.get("policy") or "", + "result": closeout.get("result") or "", + "future_authorization_decision_package": ( + closeout.get("future_authorization_decision_package") or {} + ), + "decision_closeout_contract": closeout.get("decision_closeout_contract") or {}, + "summary": closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-authorization-issuer-gate-{digest[:16]}" + + +def _db_apply_authorization_signing_decision_preflight_id(issuer_gate: dict[str, Any]) -> str: + payload = { + "policy": issuer_gate.get("policy") or "", + "result": issuer_gate.get("result") or "", + "future_authorization_issuer_gate": issuer_gate.get("future_authorization_issuer_gate") or {}, + "final_nonsecret_authorization_envelope": ( + issuer_gate.get("final_nonsecret_authorization_envelope") or {} + ), + "issuer_gate_contract": issuer_gate.get("issuer_gate_contract") or {}, + "summary": issuer_gate.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-authorization-signing-preflight-{digest[:16]}" + + +def _db_apply_authorization_signing_decision_closeout_id(preflight: dict[str, Any]) -> str: + payload = { + "policy": preflight.get("policy") or "", + "result": preflight.get("result") or "", + "future_authorization_signing_decision_preflight": ( + preflight.get("future_authorization_signing_decision_preflight") or {} + ), + "signing_decision_preflight_envelope": ( + preflight.get("signing_decision_preflight_envelope") or {} + ), + "summary": preflight.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-authorization-signing-closeout-{digest[:16]}" + + +def _db_apply_authorization_signing_issuer_guard_id(closeout: dict[str, Any]) -> str: + payload = { + "policy": closeout.get("policy") or "", + "result": closeout.get("result") or "", + "unsigned_signing_decision_package": closeout.get("unsigned_signing_decision_package") or {}, + "signing_decision_closeout_contract": ( + closeout.get("signing_decision_closeout_contract") or {} + ), + "summary": closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-authorization-signing-issuer-{digest[:16]}" + + +def _db_apply_authorization_signing_issuer_closeout_id(guard: dict[str, Any]) -> str: + payload = { + "policy": guard.get("policy") or "", + "result": guard.get("result") or "", + "signable_request_boundary": guard.get("signable_request_boundary") or {}, + "signing_issuer_guard_contract": guard.get("signing_issuer_guard_contract") or {}, + "summary": guard.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-authorization-signing-issuer-closeout-{digest[:16]}" + + +def _db_apply_authorization_signing_execution_preflight_id(closeout: dict[str, Any]) -> str: + payload = { + "policy": closeout.get("policy") or "", + "result": closeout.get("result") or "", + "future_authorization_signing_issuer_closeout": ( + closeout.get("future_authorization_signing_issuer_closeout") or {} + ), + "final_signable_request_package": closeout.get("final_signable_request_package") or {}, + "signing_issuer_closeout_contract": ( + closeout.get("signing_issuer_closeout_contract") or {} + ), + "summary": closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-authorization-signing-execution-preflight-{digest[:16]}" + + +def _db_apply_authorization_signing_execution_closeout_id(preflight: dict[str, Any]) -> str: + payload = { + "policy": preflight.get("policy") or "", + "result": preflight.get("result") or "", + "future_authorization_signing_execution_preflight": ( + preflight.get("future_authorization_signing_execution_preflight") or {} + ), + "signing_execution_preflight_package": ( + preflight.get("signing_execution_preflight_package") or {} + ), + "operator_held_secret_boundary_contract": ( + preflight.get("operator_held_secret_boundary_contract") or {} + ), + "summary": preflight.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-authorization-signing-execution-closeout-{digest[:16]}" + + +def _db_apply_authorization_signed_receipt_preflight_id(closeout: dict[str, Any]) -> str: + payload = { + "policy": closeout.get("policy") or "", + "result": closeout.get("result") or "", + "future_authorization_signing_execution_closeout": ( + closeout.get("future_authorization_signing_execution_closeout") or {} + ), + "unsigned_signed_authorization_receipt_boundary": ( + closeout.get("unsigned_signed_authorization_receipt_boundary") or {} + ), + "signing_execution_closeout_contract": ( + closeout.get("signing_execution_closeout_contract") or {} + ), + "summary": closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-authorization-signed-receipt-preflight-{digest[:16]}" + + +def _db_apply_authorization_signed_receipt_closeout_id(preflight: dict[str, Any]) -> str: + payload = { + "policy": preflight.get("policy") or "", + "result": preflight.get("result") or "", + "future_authorization_signed_receipt_preflight": ( + preflight.get("future_authorization_signed_receipt_preflight") or {} + ), + "external_signing_receipt_evidence_boundary": ( + preflight.get("external_signing_receipt_evidence_boundary") or {} + ), + "signed_receipt_preflight_contract": ( + preflight.get("signed_receipt_preflight_contract") or {} + ), + "summary": preflight.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-authorization-signed-receipt-closeout-{digest[:16]}" + + +def _db_apply_authorization_signed_receipt_evidence_intake_id( + closeout: dict[str, Any], +) -> str: + payload = { + "policy": closeout.get("policy") or "", + "result": closeout.get("result") or "", + "future_authorization_signed_receipt_closeout": ( + closeout.get("future_authorization_signed_receipt_closeout") or {} + ), + "detached_receipt_verification_boundary": ( + closeout.get("detached_receipt_verification_boundary") or {} + ), + "signed_receipt_closeout_contract": ( + closeout.get("signed_receipt_closeout_contract") or {} + ), + "summary": closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-authorization-signed-receipt-evidence-intake-{digest[:16]}" + + +def _db_apply_authorization_detached_verification_evidence_validation_id( + intake: dict[str, Any], +) -> str: + payload = { + "policy": intake.get("policy") or "", + "result": intake.get("result") or "", + "future_signed_authorization_receipt_evidence_intake": ( + intake.get("future_signed_authorization_receipt_evidence_intake") or {} + ), + "detached_verification_evidence_schema": ( + intake.get("detached_verification_evidence_schema") or {} + ), + "signed_receipt_evidence_intake_contract": ( + intake.get("signed_receipt_evidence_intake_contract") or {} + ), + "summary": intake.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-authorization-detached-verification-evidence-validation-{digest[:16]}" + + +def _db_apply_authorization_verifier_receipt_closeout_id( + validation: dict[str, Any], +) -> str: + payload = { + "policy": validation.get("policy") or "", + "result": validation.get("result") or "", + "future_detached_verification_evidence_validation": ( + validation.get("future_detached_verification_evidence_validation") or {} + ), + "verifier_receipt_closeout_boundary": ( + validation.get("verifier_receipt_closeout_boundary") or {} + ), + "detached_verification_evidence_validation_contract": ( + validation.get("detached_verification_evidence_validation_contract") or {} + ), + "summary": validation.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-authorization-verifier-receipt-closeout-{digest[:16]}" + + +def _db_apply_authorization_evidence_execution_preflight_id( + closeout: dict[str, Any], +) -> str: + payload = { + "policy": closeout.get("policy") or "", + "result": closeout.get("result") or "", + "future_verifier_receipt_closeout": ( + closeout.get("future_verifier_receipt_closeout") or {} + ), + "verifier_receipt_evidence_handoff": ( + closeout.get("verifier_receipt_evidence_handoff") or {} + ), + "verifier_receipt_closeout_contract": ( + closeout.get("verifier_receipt_closeout_contract") or {} + ), + "summary": closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-authorization-evidence-execution-preflight-{digest[:16]}" + + +def _db_apply_authorization_evidence_execution_closeout_id( + preflight: dict[str, Any], +) -> str: + payload = { + "policy": preflight.get("policy") or "", + "result": preflight.get("result") or "", + "future_database_apply_authorization_verifier_handoff": ( + preflight.get("future_database_apply_authorization_verifier_handoff") or {} + ), + "authorization_evidence_execution_preflight": ( + preflight.get("authorization_evidence_execution_preflight") or {} + ), + "authorization_evidence_execution_preflight_contract": ( + preflight.get("authorization_evidence_execution_preflight_contract") or {} + ), + "summary": preflight.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-authorization-evidence-execution-closeout-{digest[:16]}" + + +def _db_apply_controlled_apply_final_preflight_id( + closeout: dict[str, Any], +) -> str: + payload = { + "policy": closeout.get("policy") or "", + "result": closeout.get("result") or "", + "future_database_apply_authorization_final_verifier_gate": ( + closeout.get("future_database_apply_authorization_final_verifier_gate") or {} + ), + "authorization_evidence_execution_closeout": ( + closeout.get("authorization_evidence_execution_closeout") or {} + ), + "authorization_evidence_execution_closeout_contract": ( + closeout.get("authorization_evidence_execution_closeout_contract") or {} + ), + "summary": closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-apply-final-preflight-{digest[:16]}" + + +def _db_apply_controlled_dry_run_package_id( + final_preflight: dict[str, Any], +) -> str: + payload = { + "policy": final_preflight.get("policy") or "", + "result": final_preflight.get("result") or "", + "future_database_apply_controlled_apply_final_preflight": ( + final_preflight.get("future_database_apply_controlled_apply_final_preflight") + or {} + ), + "controlled_apply_final_preflight": ( + final_preflight.get("controlled_apply_final_preflight") or {} + ), + "controlled_apply_final_preflight_contract": ( + final_preflight.get("controlled_apply_final_preflight_contract") or {} + ), + "summary": final_preflight.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-package-{digest[:16]}" + + +def _db_apply_controlled_dry_run_receipt_closeout_id( + dry_run_package: dict[str, Any], +) -> str: + payload = { + "policy": dry_run_package.get("policy") or "", + "result": dry_run_package.get("result") or "", + "future_database_apply_controlled_dry_run_execution_receipt": ( + dry_run_package.get( + "future_database_apply_controlled_dry_run_execution_receipt" + ) + or {} + ), + "controlled_dry_run_package": ( + dry_run_package.get("controlled_dry_run_package") or {} + ), + "controlled_dry_run_package_contract": ( + dry_run_package.get("controlled_dry_run_package_contract") or {} + ), + "summary": dry_run_package.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-receipt-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_runner_readiness_id( + receipt_closeout: dict[str, Any], +) -> str: + payload = { + "policy": receipt_closeout.get("policy") or "", + "result": receipt_closeout.get("result") or "", + "future_database_apply_controlled_dry_run_result_parser_verification": ( + receipt_closeout.get( + "future_database_apply_controlled_dry_run_result_parser_verification" + ) + or {} + ), + "controlled_dry_run_receipt_closeout": ( + receipt_closeout.get("controlled_dry_run_receipt_closeout") or {} + ), + "controlled_dry_run_receipt_closeout_contract": ( + receipt_closeout.get("controlled_dry_run_receipt_closeout_contract") or {} + ), + "summary": receipt_closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-runner-readiness-{digest[:16]}" + + +def _db_apply_controlled_dry_run_execution_plan_closeout_id( + runner_readiness: dict[str, Any], +) -> str: + payload = { + "policy": runner_readiness.get("policy") or "", + "result": runner_readiness.get("result") or "", + "future_database_apply_controlled_dry_run_execution_plan_binding": ( + runner_readiness.get( + "future_database_apply_controlled_dry_run_execution_plan_binding" + ) + or {} + ), + "controlled_dry_run_runner_readiness": ( + runner_readiness.get("controlled_dry_run_runner_readiness") or {} + ), + "controlled_dry_run_runner_readiness_contract": ( + runner_readiness.get("controlled_dry_run_runner_readiness_contract") + or {} + ), + "summary": runner_readiness.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-execution-plan-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_command_artifact_closeout_id( + execution_plan_closeout: dict[str, Any], +) -> str: + payload = { + "policy": execution_plan_closeout.get("policy") or "", + "result": execution_plan_closeout.get("result") or "", + "future_database_apply_controlled_dry_run_command_artifact_verification": ( + execution_plan_closeout.get( + "future_database_apply_controlled_dry_run_command_artifact_verification" + ) + or {} + ), + "controlled_dry_run_execution_plan_closeout": ( + execution_plan_closeout.get("controlled_dry_run_execution_plan_closeout") + or {} + ), + "controlled_dry_run_execution_plan_closeout_contract": ( + execution_plan_closeout.get( + "controlled_dry_run_execution_plan_closeout_contract" + ) + or {} + ), + "summary": execution_plan_closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-command-artifact-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_runner_execution_receipt_closeout_id( + command_artifact_closeout: dict[str, Any], +) -> str: + payload = { + "policy": command_artifact_closeout.get("policy") or "", + "result": command_artifact_closeout.get("result") or "", + "future_database_apply_controlled_dry_run_runner_execution_receipt_preflight": ( + command_artifact_closeout.get( + "future_database_apply_controlled_dry_run_runner_execution_receipt_preflight" + ) + or {} + ), + "controlled_dry_run_command_artifact_closeout": ( + command_artifact_closeout.get( + "controlled_dry_run_command_artifact_closeout" + ) + or {} + ), + "controlled_dry_run_command_artifact_closeout_contract": ( + command_artifact_closeout.get( + "controlled_dry_run_command_artifact_closeout_contract" + ) + or {} + ), + "summary": command_artifact_closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-runner-execution-receipt-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_post_receipt_parser_closeout_id( + runner_execution_receipt_closeout: dict[str, Any], +) -> str: + payload = { + "policy": runner_execution_receipt_closeout.get("policy") or "", + "result": runner_execution_receipt_closeout.get("result") or "", + "future_database_apply_controlled_dry_run_post_receipt_parser_verification": ( + runner_execution_receipt_closeout.get( + "future_database_apply_controlled_dry_run_post_receipt_parser_verification" + ) + or {} + ), + "controlled_dry_run_runner_execution_receipt_closeout": ( + runner_execution_receipt_closeout.get( + "controlled_dry_run_runner_execution_receipt_closeout" + ) + or {} + ), + "controlled_dry_run_runner_execution_receipt_closeout_contract": ( + runner_execution_receipt_closeout.get( + "controlled_dry_run_runner_execution_receipt_closeout_contract" + ) + or {} + ), + "summary": runner_execution_receipt_closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-post-receipt-parser-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_no_apply_enforcement_closeout_id( + post_receipt_parser_closeout: dict[str, Any], +) -> str: + payload = { + "policy": post_receipt_parser_closeout.get("policy") or "", + "result": post_receipt_parser_closeout.get("result") or "", + "future_database_apply_controlled_dry_run_no_apply_enforcement_verification": ( + post_receipt_parser_closeout.get( + "future_database_apply_controlled_dry_run_no_apply_enforcement_verification" + ) + or {} + ), + "controlled_dry_run_post_receipt_parser_closeout": ( + post_receipt_parser_closeout.get( + "controlled_dry_run_post_receipt_parser_closeout" + ) + or {} + ), + "controlled_dry_run_post_receipt_parser_closeout_contract": ( + post_receipt_parser_closeout.get( + "controlled_dry_run_post_receipt_parser_closeout_contract" + ) + or {} + ), + "summary": post_receipt_parser_closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-no-apply-enforcement-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_final_executor_guard_closeout_id( + no_apply_enforcement_closeout: dict[str, Any], +) -> str: + payload = { + "policy": no_apply_enforcement_closeout.get("policy") or "", + "result": no_apply_enforcement_closeout.get("result") or "", + "future_database_apply_controlled_dry_run_final_dry_run_executor_guard": ( + no_apply_enforcement_closeout.get( + "future_database_apply_controlled_dry_run_final_dry_run_executor_guard" + ) + or {} + ), + "controlled_dry_run_no_apply_enforcement_closeout": ( + no_apply_enforcement_closeout.get( + "controlled_dry_run_no_apply_enforcement_closeout" + ) + or {} + ), + "controlled_dry_run_no_apply_enforcement_closeout_contract": ( + no_apply_enforcement_closeout.get( + "controlled_dry_run_no_apply_enforcement_closeout_contract" + ) + or {} + ), + "summary": no_apply_enforcement_closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-final-executor-guard-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_pre_apply_replay_closeout_id( + final_executor_guard_closeout: dict[str, Any], +) -> str: + payload = { + "policy": final_executor_guard_closeout.get("policy") or "", + "result": final_executor_guard_closeout.get("result") or "", + "future_database_apply_controlled_dry_run_pre_apply_replay_verifier": ( + final_executor_guard_closeout.get( + "future_database_apply_controlled_dry_run_pre_apply_replay_verifier" + ) + or {} + ), + "controlled_dry_run_final_executor_guard_closeout": ( + final_executor_guard_closeout.get( + "controlled_dry_run_final_executor_guard_closeout" + ) + or {} + ), + "controlled_dry_run_final_executor_guard_closeout_contract": ( + final_executor_guard_closeout.get( + "controlled_dry_run_final_executor_guard_closeout_contract" + ) + or {} + ), + "summary": final_executor_guard_closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-pre-apply-replay-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_apply_executor_readiness_closeout_id( + pre_apply_replay_closeout: dict[str, Any], +) -> str: + payload = { + "policy": pre_apply_replay_closeout.get("policy") or "", + "result": pre_apply_replay_closeout.get("result") or "", + "future_database_apply_controlled_dry_run_apply_executor_readiness_contract": ( + pre_apply_replay_closeout.get( + "future_database_apply_controlled_dry_run_apply_executor_readiness_contract" + ) + or {} + ), + "controlled_dry_run_pre_apply_replay_closeout": ( + pre_apply_replay_closeout.get( + "controlled_dry_run_pre_apply_replay_closeout" + ) + or {} + ), + "controlled_dry_run_pre_apply_replay_closeout_contract": ( + pre_apply_replay_closeout.get( + "controlled_dry_run_pre_apply_replay_closeout_contract" + ) + or {} + ), + "summary": pre_apply_replay_closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-apply-executor-readiness-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_invocation_receipt_closeout_id( + apply_executor_readiness_closeout: dict[str, Any], +) -> str: + payload = { + "policy": apply_executor_readiness_closeout.get("policy") or "", + "result": apply_executor_readiness_closeout.get("result") or "", + "future_database_apply_controlled_dry_run_invocation_readiness_receipt": ( + apply_executor_readiness_closeout.get( + "future_database_apply_controlled_dry_run_invocation_readiness_receipt" + ) + or {} + ), + "controlled_dry_run_apply_executor_readiness_closeout": ( + apply_executor_readiness_closeout.get( + "controlled_dry_run_apply_executor_readiness_closeout" + ) + or {} + ), + "controlled_dry_run_apply_executor_readiness_closeout_contract": ( + apply_executor_readiness_closeout.get( + "controlled_dry_run_apply_executor_readiness_closeout_contract" + ) + or {} + ), + "summary": apply_executor_readiness_closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-invocation-receipt-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_no_write_invocation_package_closeout_id( + invocation_receipt_closeout: dict[str, Any], +) -> str: + payload = { + "policy": invocation_receipt_closeout.get("policy") or "", + "result": invocation_receipt_closeout.get("result") or "", + "future_database_apply_controlled_dry_run_no_write_invocation_package": ( + invocation_receipt_closeout.get( + "future_database_apply_controlled_dry_run_no_write_invocation_package" + ) + or {} + ), + "controlled_dry_run_invocation_receipt_closeout": ( + invocation_receipt_closeout.get( + "controlled_dry_run_invocation_receipt_closeout" + ) + or {} + ), + "controlled_dry_run_invocation_receipt_closeout_contract": ( + invocation_receipt_closeout.get( + "controlled_dry_run_invocation_receipt_closeout_contract" + ) + or {} + ), + "summary": invocation_receipt_closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-no-write-invocation-package-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_execution_preflight_guard_closeout_id( + no_write_invocation_package_closeout: dict[str, Any], +) -> str: + payload = { + "policy": no_write_invocation_package_closeout.get("policy") or "", + "result": no_write_invocation_package_closeout.get("result") or "", + "future_database_apply_controlled_dry_run_execution_preflight_guard": ( + no_write_invocation_package_closeout.get( + "future_database_apply_controlled_dry_run_execution_preflight_guard" + ) + or {} + ), + "controlled_dry_run_no_write_invocation_package_closeout": ( + no_write_invocation_package_closeout.get( + "controlled_dry_run_no_write_invocation_package_closeout" + ) + or {} + ), + "controlled_dry_run_no_write_invocation_package_closeout_contract": ( + no_write_invocation_package_closeout.get( + "controlled_dry_run_no_write_invocation_package_closeout_contract" + ) + or {} + ), + "summary": no_write_invocation_package_closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-execution-preflight-guard-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_runner_invocation_boundary_closeout_id( + execution_preflight_guard_closeout: dict[str, Any], +) -> str: + payload = { + "policy": execution_preflight_guard_closeout.get("policy") or "", + "result": execution_preflight_guard_closeout.get("result") or "", + "future_database_apply_controlled_dry_run_runner_invocation_boundary": ( + execution_preflight_guard_closeout.get( + "future_database_apply_controlled_dry_run_runner_invocation_boundary" + ) + or {} + ), + "controlled_dry_run_execution_preflight_guard_closeout": ( + execution_preflight_guard_closeout.get( + "controlled_dry_run_execution_preflight_guard_closeout" + ) + or {} + ), + "controlled_dry_run_execution_preflight_guard_closeout_contract": ( + execution_preflight_guard_closeout.get( + "controlled_dry_run_execution_preflight_guard_closeout_contract" + ) + or {} + ), + "summary": execution_preflight_guard_closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-runner-invocation-boundary-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_no_execution_receipt_handoff_closeout_id( + runner_invocation_boundary_closeout: dict[str, Any], +) -> str: + payload = { + "policy": runner_invocation_boundary_closeout.get("policy") or "", + "result": runner_invocation_boundary_closeout.get("result") or "", + "future_database_apply_controlled_dry_run_no_execution_receipt_handoff": ( + runner_invocation_boundary_closeout.get( + "future_database_apply_controlled_dry_run_no_execution_receipt_handoff" + ) + or {} + ), + "controlled_dry_run_runner_invocation_boundary_closeout": ( + runner_invocation_boundary_closeout.get( + "controlled_dry_run_runner_invocation_boundary_closeout" + ) + or {} + ), + "controlled_dry_run_runner_invocation_boundary_closeout_contract": ( + runner_invocation_boundary_closeout.get( + "controlled_dry_run_runner_invocation_boundary_closeout_contract" + ) + or {} + ), + "summary": runner_invocation_boundary_closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-no-execution-receipt-handoff-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_final_no_runner_execution_proof_closeout_id( + no_execution_receipt_handoff_closeout: dict[str, Any], +) -> str: + payload = { + "policy": no_execution_receipt_handoff_closeout.get("policy") or "", + "result": no_execution_receipt_handoff_closeout.get("result") or "", + "future_database_apply_controlled_dry_run_final_no_runner_execution_proof": ( + no_execution_receipt_handoff_closeout.get( + "future_database_apply_controlled_dry_run_final_no_runner_execution_proof" + ) + or {} + ), + "controlled_dry_run_no_execution_receipt_handoff_closeout": ( + no_execution_receipt_handoff_closeout.get( + "controlled_dry_run_no_execution_receipt_handoff_closeout" + ) + or {} + ), + "controlled_dry_run_no_execution_receipt_handoff_closeout_contract": ( + no_execution_receipt_handoff_closeout.get( + "controlled_dry_run_no_execution_receipt_handoff_closeout_contract" + ) + or {} + ), + "summary": no_execution_receipt_handoff_closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-final-no-runner-execution-proof-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_controlled_executor_quarantine_proof_closeout_id( + final_no_runner_execution_proof_closeout: dict[str, Any], +) -> str: + payload = { + "policy": final_no_runner_execution_proof_closeout.get("policy") or "", + "result": final_no_runner_execution_proof_closeout.get("result") or "", + "future_database_apply_controlled_dry_run_controlled_executor_quarantine_proof": ( + final_no_runner_execution_proof_closeout.get( + "future_database_apply_controlled_dry_run_controlled_executor_quarantine_proof" + ) + or {} + ), + "controlled_dry_run_final_no_runner_execution_proof_closeout": ( + final_no_runner_execution_proof_closeout.get( + "controlled_dry_run_final_no_runner_execution_proof_closeout" + ) + or {} + ), + "controlled_dry_run_final_no_runner_execution_proof_closeout_contract": ( + final_no_runner_execution_proof_closeout.get( + "controlled_dry_run_final_no_runner_execution_proof_closeout_contract" + ) + or {} + ), + "summary": final_no_runner_execution_proof_closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-controlled-executor-quarantine-proof-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_execution_envelope_freeze_proof_closeout_id( + controlled_executor_quarantine_proof_closeout: dict[str, Any], +) -> str: + payload = { + "policy": controlled_executor_quarantine_proof_closeout.get("policy") or "", + "result": controlled_executor_quarantine_proof_closeout.get("result") or "", + "future_database_apply_controlled_dry_run_execution_envelope_freeze_proof": ( + controlled_executor_quarantine_proof_closeout.get( + "future_database_apply_controlled_dry_run_execution_envelope_freeze_proof" + ) + or {} + ), + "controlled_dry_run_controlled_executor_quarantine_proof_closeout": ( + controlled_executor_quarantine_proof_closeout.get( + "controlled_dry_run_controlled_executor_quarantine_proof_closeout" + ) + or {} + ), + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_contract": ( + controlled_executor_quarantine_proof_closeout.get( + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_contract" + ) + or {} + ), + "summary": controlled_executor_quarantine_proof_closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-execution-envelope-freeze-proof-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_frozen_envelope_verifier_handoff_closeout_id( + execution_envelope_freeze_proof_closeout: dict[str, Any], +) -> str: + payload = { + "policy": execution_envelope_freeze_proof_closeout.get("policy") or "", + "result": execution_envelope_freeze_proof_closeout.get("result") or "", + "future_database_apply_controlled_dry_run_frozen_envelope_verifier_handoff": ( + execution_envelope_freeze_proof_closeout.get( + "future_database_apply_controlled_dry_run_frozen_envelope_verifier_handoff" + ) + or {} + ), + "controlled_dry_run_execution_envelope_freeze_proof_closeout": ( + execution_envelope_freeze_proof_closeout.get( + "controlled_dry_run_execution_envelope_freeze_proof_closeout" + ) + or {} + ), + "controlled_dry_run_execution_envelope_freeze_proof_closeout_contract": ( + execution_envelope_freeze_proof_closeout.get( + "controlled_dry_run_execution_envelope_freeze_proof_closeout_contract" + ) + or {} + ), + "summary": execution_envelope_freeze_proof_closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-frozen-envelope-verifier-handoff-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_verifier_invocation_lock_proof_closeout_id( + frozen_envelope_verifier_handoff_closeout: dict[str, Any], +) -> str: + payload = { + "policy": frozen_envelope_verifier_handoff_closeout.get("policy") or "", + "result": frozen_envelope_verifier_handoff_closeout.get("result") or "", + "future_database_apply_controlled_dry_run_verifier_invocation_lock_proof": ( + frozen_envelope_verifier_handoff_closeout.get( + "future_database_apply_controlled_dry_run_verifier_invocation_lock_proof" + ) + or {} + ), + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout": ( + frozen_envelope_verifier_handoff_closeout.get( + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout" + ) + or {} + ), + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_contract": ( + frozen_envelope_verifier_handoff_closeout.get( + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_contract" + ) + or {} + ), + "summary": frozen_envelope_verifier_handoff_closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-verifier-invocation-lock-proof-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_verifier_no_execution_receipt_proof_closeout_id( + verifier_invocation_lock_proof_closeout: dict[str, Any], +) -> str: + payload = { + "policy": verifier_invocation_lock_proof_closeout.get("policy") or "", + "result": verifier_invocation_lock_proof_closeout.get("result") or "", + "future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof": ( + verifier_invocation_lock_proof_closeout.get( + "future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof" + ) + or {} + ), + "controlled_dry_run_verifier_invocation_lock_proof_closeout": ( + verifier_invocation_lock_proof_closeout.get( + "controlled_dry_run_verifier_invocation_lock_proof_closeout" + ) + or {} + ), + "controlled_dry_run_verifier_invocation_lock_proof_closeout_contract": ( + verifier_invocation_lock_proof_closeout.get( + "controlled_dry_run_verifier_invocation_lock_proof_closeout_contract" + ) + or {} + ), + "summary": verifier_invocation_lock_proof_closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-verifier-no-execution-receipt-proof-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_id( + verifier_no_execution_receipt_proof_closeout: dict[str, Any], +) -> str: + payload = { + "policy": verifier_no_execution_receipt_proof_closeout.get("policy") or "", + "result": verifier_no_execution_receipt_proof_closeout.get("result") or "", + "future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof": ( + verifier_no_execution_receipt_proof_closeout.get( + "future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof" + ) + or {} + ), + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout": ( + verifier_no_execution_receipt_proof_closeout.get( + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout" + ) + or {} + ), + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout_contract": ( + verifier_no_execution_receipt_proof_closeout.get( + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout_contract" + ) + or {} + ), + "summary": verifier_no_execution_receipt_proof_closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-verifier-receipt-persistence-guard-proof-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_id( + verifier_receipt_persistence_guard_proof_closeout: dict[str, Any], +) -> str: + payload = { + "policy": verifier_receipt_persistence_guard_proof_closeout.get("policy") + or "", + "result": verifier_receipt_persistence_guard_proof_closeout.get("result") + or "", + "future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof": ( + verifier_receipt_persistence_guard_proof_closeout.get( + "future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof" + ) + or {} + ), + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout": ( + verifier_receipt_persistence_guard_proof_closeout.get( + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout" + ) + or {} + ), + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_contract": ( + verifier_receipt_persistence_guard_proof_closeout.get( + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_contract" + ) + or {} + ), + "summary": verifier_receipt_persistence_guard_proof_closeout.get( + "summary" + ) + or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-receipt-persistence-storage-boundary-proof-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_id( + receipt_persistence_storage_boundary_proof_closeout: dict[str, Any], +) -> str: + payload = { + "policy": receipt_persistence_storage_boundary_proof_closeout.get("policy") + or "", + "result": receipt_persistence_storage_boundary_proof_closeout.get("result") + or "", + "future_database_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof": ( + receipt_persistence_storage_boundary_proof_closeout.get( + "future_database_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof" + ) + or {} + ), + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout": ( + receipt_persistence_storage_boundary_proof_closeout.get( + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout" + ) + or {} + ), + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_contract": ( + receipt_persistence_storage_boundary_proof_closeout.get( + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_contract" + ) + or {} + ), + "summary": receipt_persistence_storage_boundary_proof_closeout.get( + "summary" + ) + or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-storage-boundary-no-write-ledger-proof-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_no_write_ledger_retention_proof_closeout_id( + storage_boundary_no_write_ledger_proof_closeout: dict[str, Any], +) -> str: + payload = { + "policy": storage_boundary_no_write_ledger_proof_closeout.get("policy") + or "", + "result": storage_boundary_no_write_ledger_proof_closeout.get("result") + or "", + "future_database_apply_controlled_dry_run_no_write_ledger_retention_proof": ( + storage_boundary_no_write_ledger_proof_closeout.get( + "future_database_apply_controlled_dry_run_no_write_ledger_retention_proof" + ) + or {} + ), + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout": ( + storage_boundary_no_write_ledger_proof_closeout.get( + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout" + ) + or {} + ), + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_contract": ( + storage_boundary_no_write_ledger_proof_closeout.get( + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_contract" + ) + or {} + ), + "summary": storage_boundary_no_write_ledger_proof_closeout.get( + "summary" + ) + or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-no-write-ledger-retention-proof-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_id( + no_write_ledger_retention_proof_closeout: dict[str, Any], +) -> str: + payload = { + "policy": no_write_ledger_retention_proof_closeout.get("policy") + or "", + "result": no_write_ledger_retention_proof_closeout.get("result") + or "", + "future_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof": ( + no_write_ledger_retention_proof_closeout.get( + "future_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof" + ) + or {} + ), + "controlled_dry_run_no_write_ledger_retention_proof_closeout": ( + no_write_ledger_retention_proof_closeout.get( + "controlled_dry_run_no_write_ledger_retention_proof_closeout" + ) + or {} + ), + "controlled_dry_run_no_write_ledger_retention_proof_closeout_contract": ( + no_write_ledger_retention_proof_closeout.get( + "controlled_dry_run_no_write_ledger_retention_proof_closeout_contract" + ) + or {} + ), + "summary": no_write_ledger_retention_proof_closeout.get("summary") or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-retention-boundary-no-write-archive-proof-closeout-{digest[:16]}" + + +def _db_apply_controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_id( + retention_boundary_no_write_archive_proof_closeout: dict[str, Any], +) -> str: + payload = { + "policy": retention_boundary_no_write_archive_proof_closeout.get("policy") + or "", + "result": retention_boundary_no_write_archive_proof_closeout.get("result") + or "", + "future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof": ( + retention_boundary_no_write_archive_proof_closeout.get( + "future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof" + ) + or {} + ), + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout": ( + retention_boundary_no_write_archive_proof_closeout.get( + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout" + ) + or {} + ), + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_contract": ( + retention_boundary_no_write_archive_proof_closeout.get( + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_contract" + ) + or {} + ), + "summary": retention_boundary_no_write_archive_proof_closeout.get( + "summary" + ) + or {}, + } + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() + return f"pchome-db-apply-controlled-dry-run-archive-retention-sealed-handoff-proof-closeout-{digest[:16]}" + + +def _dry_run_closeout_check(key: str, passed: bool, evidence: Any, failure_route: str) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _authorization_request_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _authorization_lane_guard_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _authorization_decision_preflight_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _authorization_decision_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _authorization_issuer_gate_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _authorization_signing_decision_preflight_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _authorization_signing_decision_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _authorization_signing_issuer_guard_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _authorization_signing_issuer_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _authorization_signing_execution_preflight_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _authorization_signing_execution_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _authorization_signed_receipt_preflight_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _authorization_signed_receipt_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _authorization_signed_receipt_evidence_intake_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _authorization_detached_verification_evidence_validation_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _authorization_verifier_receipt_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _authorization_evidence_execution_preflight_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _authorization_evidence_execution_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_apply_final_preflight_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_package_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_receipt_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_runner_readiness_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_execution_plan_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_command_artifact_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_runner_execution_receipt_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_post_receipt_parser_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_no_apply_enforcement_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_final_executor_guard_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_pre_apply_replay_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_apply_executor_readiness_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_invocation_receipt_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_no_write_invocation_package_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_execution_preflight_guard_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_runner_invocation_boundary_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_no_execution_receipt_handoff_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_final_no_runner_execution_proof_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_controlled_executor_quarantine_proof_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_execution_envelope_freeze_proof_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_frozen_envelope_verifier_handoff_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_verifier_invocation_lock_proof_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_verifier_no_execution_receipt_proof_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_no_write_ledger_retention_proof_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def _controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_check( + key: str, + passed: bool, + evidence: Any, + failure_route: str, +) -> dict[str, Any]: + return { + "key": key, + "status": "pass" if passed else "waiting", + "passed": bool(passed), + "evidence": evidence, + "failure_route": failure_route, + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_shell_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out the controlled dry-run shell preview without executing it.""" + shell_preview = build_pchome_auto_policy_db_apply_controlled_dry_run_shell_preview( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + shell = shell_preview.get("controlled_dry_run_shell_preview") or {} + summary = shell_preview.get("summary") or {} + shell_script = shell_preview.get("shell_script_preview") or {} + check_mode = shell_preview.get("check_mode_contract") or {} + rollback_hooks = shell_preview.get("rollback_hook_preview") or {} + safety = shell_preview.get("safety") or {} + checks = [ + _dry_run_closeout_check( + "production_truth_required", + True, + "production /health must be refreshed immediately before any future explicit DB apply", + "abort_before_authorization_boundary", + ), + _dry_run_closeout_check( + "controlled_dry_run_shell_preview_ready", + shell_preview.get("result") == "DB_APPLY_CONTROLLED_DRY_RUN_SHELL_PREVIEW_READY" + and bool(shell.get("ready_for_future_shell_script_generation")), + { + "result": shell_preview.get("result"), + "ready_for_future_shell_script_generation": shell.get( + "ready_for_future_shell_script_generation" + ), + }, + "wait_for_controlled_dry_run_shell_preview", + ), + _dry_run_closeout_check( + "final_handoff_ready", + int(summary.get("final_handoff_ready_count") or 0) == 1, + {"final_handoff_ready_count": summary.get("final_handoff_ready_count", 0)}, + "wait_for_final_handoff_package", + ), + _dry_run_closeout_check( + "shell_phase_contract_complete", + int(summary.get("shell_phase_count") or 0) >= 9, + {"shell_phase_count": summary.get("shell_phase_count", 0)}, + "wait_for_shell_phase_contract", + ), + _dry_run_closeout_check( + "shell_script_preview_complete", + int(summary.get("shell_script_line_count") or 0) >= 10 + and shell_script.get("writes_file_in_preview") is False + and shell_script.get("executes_script_in_preview") is False, + { + "shell_script_line_count": summary.get("shell_script_line_count", 0), + "writes_file_in_preview": shell_script.get("writes_file_in_preview"), + "executes_script_in_preview": shell_script.get("executes_script_in_preview"), + }, + "wait_for_shell_script_preview", + ), + _dry_run_closeout_check( + "check_mode_contract_complete", + int(check_mode.get("required_check_count") or 0) >= 6 + and check_mode.get("dry_run_only") is True, + { + "required_check_count": check_mode.get("required_check_count", 0), + "dry_run_only": check_mode.get("dry_run_only"), + }, + "wait_for_check_mode_contract", + ), + _dry_run_closeout_check( + "rollback_hooks_complete", + int(rollback_hooks.get("hook_count") or 0) >= 3, + {"rollback_hook_count": rollback_hooks.get("hook_count", 0)}, + "wait_for_rollback_hooks", + ), + _dry_run_closeout_check( + "preview_writes_no_script", + int(summary.get("writes_script_count") or 0) == 0 + and shell.get("ready_to_write_script_now") is False + and safety.get("writes_script_in_preview") is False, + { + "writes_script_count": summary.get("writes_script_count", 0), + "ready_to_write_script_now": shell.get("ready_to_write_script_now"), + "safety_writes_script_in_preview": safety.get("writes_script_in_preview"), + }, + "block_until_no_script_write_preview", + ), + _dry_run_closeout_check( + "preview_executes_no_shell", + int(summary.get("executes_script_count") or 0) == 0 + and shell.get("ready_to_execute_shell_now") is False + and safety.get("executes_script") is False, + { + "executes_script_count": summary.get("executes_script_count", 0), + "ready_to_execute_shell_now": shell.get("ready_to_execute_shell_now"), + "safety_executes_script": safety.get("executes_script"), + }, + "block_until_no_shell_execution_preview", + ), + _dry_run_closeout_check( + "preview_reads_no_secret", + int(summary.get("reads_secret_count") or 0) == 0 + and shell.get("reads_secret_in_preview") is False + and safety.get("reads_secret_in_preview") is False, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "shell_reads_secret_in_preview": shell.get("reads_secret_in_preview"), + "safety_reads_secret_in_preview": safety.get("reads_secret_in_preview"), + }, + "block_until_secret_boundary_clean", + ), + _dry_run_closeout_check( + "preview_executes_no_sql", + int(summary.get("executes_sql_count") or 0) == 0 + and shell.get("executes_sql_in_preview") is False + and safety.get("executes_sql") is False, + { + "executes_sql_count": summary.get("executes_sql_count", 0), + "shell_executes_sql_in_preview": shell.get("executes_sql_in_preview"), + "safety_executes_sql": safety.get("executes_sql"), + }, + "block_until_no_sql_preview", + ), + _dry_run_closeout_check( + "preview_writes_no_database", + int(summary.get("writes_database_count") or 0) == 0 + and shell.get("writes_database_in_preview") is False + and safety.get("writes_database") is False, + { + "writes_database_count": summary.get("writes_database_count", 0), + "shell_writes_database_in_preview": shell.get("writes_database_in_preview"), + "safety_writes_database": safety.get("writes_database"), + }, + "block_until_no_db_write_preview", + ), + _dry_run_closeout_check( + "manual_review_regression_absent", + int(summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY) or 0) == 0, + {LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0)}, + "route_only_failed_verifiers_to_exception_review", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_SHELL_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_CONTROLLED_DRY_RUN_SHELL_PREVIEW" + ) + explicit_authorization_boundary = { + "boundary_id": _db_apply_controlled_dry_run_shell_closeout_id(shell_preview), + "source_dry_run_shell_preview_id": shell.get("preview_id"), + "source_final_handoff_package_id": shell.get("source_final_handoff_package_id"), + "source_artifact_preview_id": shell.get("source_artifact_preview_id"), + "source_authorization_package_id": shell.get("source_authorization_package_id"), + "source_preflight_id": shell.get("source_preflight_id"), + "source_request_id": shell.get("source_request_id"), + "status": closeout_status, + "ready_for_explicit_apply_authorization_boundary": closeout_ready, + "ready_for_database_apply_now": False, + "target_file": shell.get("target_file"), + "expected_sha256": shell.get("expected_sha256"), + "actual_sha256": shell.get("actual_sha256"), + "hash_matches": shell.get("hash_matches"), + "requires_new_explicit_db_apply_authorization": True, + "requires_fresh_production_truth_in_future_run": True, + "requires_future_shell_secret_injection": True, + "operator_secret_boundary": "future_shell_only", + "reads_secret_in_preview": False, + "writes_script_in_preview": False, + "executes_shell_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "manual_review_mode": "exception_only", + } + future_apply_boundaries = [ + { + "key": "fresh_production_truth_same_run", + "required": True, + "source_command": "python scripts/ops/check_production_version_truth.py", + }, + { + "key": "fresh_final_handoff_and_dry_run_closeout", + "required": True, + "source_endpoint": "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-shell-closeout", + }, + { + "key": "database_url_from_future_shell_only", + "required": True, + "reads_secret_in_preview": False, + }, + { + "key": "prewrite_snapshot_before_sql", + "required": True, + "writes_artifact_in_preview": False, + }, + { + "key": "post_apply_verifier_after_sql", + "required": True, + "executes_sql_in_preview": False, + }, + { + "key": "rollback_artifact_on_verifier_failure", + "required": True, + "writes_artifact_in_preview": False, + }, + ] + + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_SHELL_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(shell_preview.get("success")), + "generated_at": shell_preview.get("generated_at"), + "source_policy": shell_preview.get("policy"), + "stats": shell_preview.get("stats") or {}, + "summary": { + "closeout_ready_count": 1 if closeout_ready else 0, + "closeout_check_count": len(checks), + "closeout_pass_count": passed_count, + "closeout_waiting_count": len(waiting_checks), + "dry_run_shell_preview_ready_count": summary.get("dry_run_shell_preview_ready_count", 0), + "future_apply_boundary_count": len(future_apply_boundaries), + "shell_phase_count": summary.get("shell_phase_count", 0), + "shell_script_line_count": summary.get("shell_script_line_count", 0), + "check_mode_required_check_count": summary.get("check_mode_required_check_count", 0), + "rollback_hook_count": summary.get("rollback_hook_count", 0), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "controlled_dry_run_shell_closeout": { + "status": closeout_status, + "ready_for_explicit_apply_authorization_boundary": closeout_ready, + "ready_for_database_apply_now": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + }, + "explicit_authorization_boundary": explicit_authorization_boundary, + "future_apply_boundaries": future_apply_boundaries, + "closeout_checks": checks, + "source_dry_run_shell_summary": summary, + "safety": { + "read_only_db_apply_controlled_dry_run_shell_closeout": True, + "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, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Treat this closeout as the final no-write boundary before any separate explicit DB apply authorization.", + "Do not execute shell, SQL, or DB writes from this preview.", + "Refresh production truth, dry-run closeout, and artifacts inside the future apply run.", + ], + } + + +def build_pchome_auto_policy_db_apply_authorization_decision_preflight( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Prepare no-write inputs for a future DB apply authorization decision.""" + lane_guard = build_pchome_auto_policy_db_apply_authorization_lane_guard( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + lane = lane_guard.get("future_authorization_lane_guard") or {} + contract = lane_guard.get("lane_transfer_contract") or {} + summary = lane_guard.get("summary") or {} + source_package = lane_guard.get("source_final_exact_request_package") or {} + source_manifest = lane_guard.get("source_machine_request_manifest") or {} + source_manifest_steps = source_manifest.get("manifest_steps") or [] + lane_requirements = lane_guard.get("lane_entry_requirements") or [] + requirement_keys = {item.get("key") for item in lane_requirements} + side_effect_free = ( + int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and lane.get("reads_secret_in_preview") is False + and lane.get("executes_shell_in_preview") is False + and lane.get("executes_sql_in_preview") is False + and lane.get("writes_database_in_preview") is False + ) + decision_input_requirements = [ + { + "key": "fresh_production_truth_same_run", + "required": True, + "source_command": "python scripts/ops/check_production_version_truth.py", + }, + { + "key": "authorization_lane_guard_id", + "required": True, + "source_id": lane.get("guard_id"), + }, + { + "key": "final_exact_request_package_id", + "required": True, + "source_id": lane.get("source_closeout_package_id"), + }, + { + "key": "exact_request_payload", + "required": True, + "field_count": summary.get("exact_request_payload_field_count", 0), + }, + { + "key": "migration_target_hash", + "required": True, + "target_file": lane.get("target_file"), + "expected_sha256": lane.get("expected_sha256"), + "hash_matches": lane.get("hash_matches"), + }, + { + "key": "secret_boundary_rejection_proof", + "required": True, + "operator_secret_boundary": lane.get("operator_secret_boundary"), + "reads_secret_in_preview": False, + }, + { + "key": "rollback_boundary_acknowledgement", + "required": True, + }, + { + "key": "post_apply_verifier_reference", + "required": True, + "source_endpoint": "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-verifier-artifact-preview", + }, + ] + decision_rejection_policy = [ + "production_truth_missing_or_stale", + "authorization_lane_guard_not_ready", + "exact_request_payload_missing_or_mutated", + "migration_hash_missing_or_mismatch", + "secret_material_present_in_request", + "rollback_boundary_missing", + "post_apply_verifier_missing", + "direct_database_apply_requested_from_decision_preflight", + "preview_attempted_shell_sql_or_database_side_effect", + "manual_review_batch_regression_detected", + ] + checks = [ + _authorization_decision_preflight_check( + "authorization_lane_guard_ready", + lane_guard.get("result") == "DB_APPLY_AUTHORIZATION_LANE_GUARD_READY" + and lane.get("ready_for_future_authorization_lane_entry") is True, + { + "result": lane_guard.get("result"), + "ready_for_future_authorization_lane_entry": lane.get( + "ready_for_future_authorization_lane_entry" + ), + }, + "wait_for_authorization_lane_guard", + ), + _authorization_decision_preflight_check( + "lane_contract_is_no_authorization", + contract.get("issues_database_apply_authorization") is False + and contract.get("ready_for_database_apply_now") is False + and contract.get("writes_database") is False, + { + "issues_database_apply_authorization": contract.get( + "issues_database_apply_authorization" + ), + "ready_for_database_apply_now": contract.get("ready_for_database_apply_now"), + "writes_database": contract.get("writes_database"), + }, + "block_if_lane_contract_authorizes_apply", + ), + _authorization_decision_preflight_check( + "same_run_production_truth_required", + "production_truth_refreshed_in_same_run" in requirement_keys + and lane.get("requires_fresh_production_truth_in_same_run") is True, + { + "requirement_keys": sorted(key for key in requirement_keys if key), + "requires_fresh_production_truth_in_same_run": lane.get( + "requires_fresh_production_truth_in_same_run" + ), + }, + "require_same_run_production_truth", + ), + _authorization_decision_preflight_check( + "decision_inputs_complete", + len(decision_input_requirements) == 8 + and all(item.get("required") is True for item in decision_input_requirements), + {"decision_input_requirement_count": len(decision_input_requirements)}, + "wait_for_decision_input_requirements", + ), + _authorization_decision_preflight_check( + "exact_request_payload_complete", + int(summary.get("exact_request_payload_field_count") or 0) == 10 + and source_package.get("payload_template_field_count") == 10, + { + "exact_request_payload_field_count": summary.get( + "exact_request_payload_field_count", 0 + ), + "payload_template_field_count": source_package.get("payload_template_field_count"), + }, + "wait_for_exact_request_payload", + ), + _authorization_decision_preflight_check( + "migration_target_hash_locked", + bool(lane.get("target_file")) + and bool(lane.get("expected_sha256")) + and lane.get("hash_matches") is True, + {"target_file": lane.get("target_file"), "hash_matches": lane.get("hash_matches")}, + "abort_on_migration_hash_gap", + ), + _authorization_decision_preflight_check( + "secret_boundary_rejects_secret_material", + "secret_material_absent_from_request" in requirement_keys + and lane.get("operator_secret_boundary") == "future_shell_only" + and lane.get("reads_secret_in_preview") is False, + { + "operator_secret_boundary": lane.get("operator_secret_boundary"), + "reads_secret_in_preview": lane.get("reads_secret_in_preview"), + }, + "abort_on_secret_boundary_violation", + ), + _authorization_decision_preflight_check( + "rollback_boundary_required", + "rollback_boundary_acknowledged" in requirement_keys, + {"requirement_keys": sorted(key for key in requirement_keys if key)}, + "block_until_rollback_boundary_is_present", + ), + _authorization_decision_preflight_check( + "source_manifest_complete", + source_manifest.get("manifest_step_count") == 6 + and len(source_manifest_steps) == 6, + { + "manifest_step_count": source_manifest.get("manifest_step_count"), + "manifest_step_len": len(source_manifest_steps), + }, + "wait_for_machine_request_manifest", + ), + _authorization_decision_preflight_check( + "rejection_policy_complete", + len(decision_rejection_policy) == 10 + and "direct_database_apply_requested_from_decision_preflight" in decision_rejection_policy, + {"decision_rejection_reason_count": len(decision_rejection_policy)}, + "wait_for_decision_rejection_policy", + ), + _authorization_decision_preflight_check( + "preview_has_no_side_effects", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + }, + "abort_on_preview_side_effect", + ), + _authorization_decision_preflight_check( + "manual_review_regression_absent", + int(summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY) or 0) == 0, + {LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0)}, + "route_failed_verifier_to_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + preflight_ready = not waiting_checks + preflight_status = ( + "DB_APPLY_AUTHORIZATION_DECISION_PREFLIGHT_READY" + if preflight_ready + else "WAITING_FOR_DB_APPLY_AUTHORIZATION_LANE_GUARD" + ) + future_authorization_decision_preflight = { + "preflight_id": _db_apply_authorization_decision_preflight_id(lane_guard), + "source_lane_guard_id": lane.get("guard_id"), + "source_closeout_package_id": lane.get("source_closeout_package_id"), + "source_intake_id": lane.get("source_intake_id"), + "source_closeout_boundary_id": lane.get("source_closeout_boundary_id"), + "status": preflight_status, + "ready_for_future_authorization_decision": preflight_ready, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "can_enter_authorization_decision_lane": preflight_ready, + "request_scope": "future_explicit_db_apply_authorization_only", + "target_file": lane.get("target_file"), + "expected_sha256": lane.get("expected_sha256"), + "actual_sha256": lane.get("actual_sha256"), + "hash_matches": lane.get("hash_matches"), + "requires_fresh_production_truth_in_same_run": True, + "operator_secret_boundary": "future_shell_only", + "reads_secret_in_preview": False, + "executes_shell_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "manual_review_mode": "exception_only", + } + decision_preflight_envelope = { + "mode": "authorization_decision_preflight_only", + "allows_authorization_decision_in_future_lane": preflight_ready, + "issues_database_apply_authorization": False, + "ready_for_database_apply_now": False, + "rejects_direct_database_apply": True, + "requires_post_apply_verifier": True, + "manual_review_mode": "exception_only", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_AUTHORIZATION_DECISION_PREFLIGHT_POLICY, + "result": preflight_status, + "success": bool(lane_guard.get("success")), + "generated_at": lane_guard.get("generated_at"), + "source_policy": lane_guard.get("policy"), + "stats": lane_guard.get("stats") or {}, + "summary": { + "authorization_decision_preflight_ready_count": 1 if preflight_ready else 0, + "decision_preflight_check_count": len(checks), + "decision_preflight_pass_count": passed_count, + "decision_preflight_waiting_count": len(waiting_checks), + "authorization_lane_guard_ready_count": summary.get( + "authorization_lane_guard_ready_count", 0 + ), + "decision_input_requirement_count": len(decision_input_requirements), + "decision_rejection_reason_count": len(decision_rejection_policy), + "lane_entry_requirement_count": summary.get("lane_entry_requirement_count", 0), + "exact_request_payload_field_count": summary.get( + "exact_request_payload_field_count", 0 + ), + "machine_request_manifest_step_count": summary.get( + "machine_request_manifest_step_count", 0 + ), + "required_request_evidence_count": summary.get("required_request_evidence_count", 0), + "authorization_acceptance_gate_count": summary.get( + "authorization_acceptance_gate_count", 0 + ), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "future_authorization_decision_preflight": future_authorization_decision_preflight, + "decision_preflight_envelope": decision_preflight_envelope, + "decision_input_requirements": decision_input_requirements, + "decision_rejection_policy": decision_rejection_policy, + "decision_preflight_checks": checks, + "source_lane_guard_summary": summary, + "source_lane_transfer_contract": contract, + "source_final_exact_request_package": source_package, + "safety": { + "read_only_db_apply_authorization_decision_preflight": True, + "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, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this preflight to verify inputs before a future authorization decision lane.", + "Keep actual DB apply authorization out of this preflight.", + "Require same-run production truth, secret rejection, rollback boundary, and post-apply verifier before any future decision.", + ], + } + + +def build_pchome_auto_policy_db_apply_authorization_decision_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out the no-write package for a future authorization decision lane.""" + preflight = build_pchome_auto_policy_db_apply_authorization_decision_preflight( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + decision = preflight.get("future_authorization_decision_preflight") or {} + envelope = preflight.get("decision_preflight_envelope") or {} + summary = preflight.get("summary") or {} + decision_input_requirements = preflight.get("decision_input_requirements") or [] + decision_rejection_policy = preflight.get("decision_rejection_policy") or [] + input_keys = {item.get("key") for item in decision_input_requirements} + side_effect_free = ( + int(summary.get("writes_script_count") or 0) == 0 + and int(summary.get("writes_artifact_count") or 0) == 0 + and int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_migration_count") or 0) == 0 + and int(summary.get("executes_endpoint_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and decision.get("reads_secret_in_preview") is False + and decision.get("executes_shell_in_preview") is False + and decision.get("executes_sql_in_preview") is False + and decision.get("writes_database_in_preview") is False + ) + checks = [ + _authorization_decision_closeout_check( + "authorization_decision_preflight_ready", + preflight.get("result") == "DB_APPLY_AUTHORIZATION_DECISION_PREFLIGHT_READY" + and decision.get("ready_for_future_authorization_decision") is True, + { + "result": preflight.get("result"), + "ready_for_future_authorization_decision": decision.get( + "ready_for_future_authorization_decision" + ), + }, + "wait_for_authorization_decision_preflight", + ), + _authorization_decision_closeout_check( + "decision_envelope_allows_future_lane_only", + envelope.get("allows_authorization_decision_in_future_lane") is True + and envelope.get("issues_database_apply_authorization") is False + and envelope.get("ready_for_database_apply_now") is False, + { + "allows_authorization_decision_in_future_lane": envelope.get( + "allows_authorization_decision_in_future_lane" + ), + "issues_database_apply_authorization": envelope.get( + "issues_database_apply_authorization" + ), + "ready_for_database_apply_now": envelope.get("ready_for_database_apply_now"), + }, + "block_if_decision_envelope_authorizes_apply", + ), + _authorization_decision_closeout_check( + "decision_inputs_complete", + len(decision_input_requirements) == 8 + and all(item.get("required") is True for item in decision_input_requirements), + { + "decision_input_requirement_count": len(decision_input_requirements), + "input_keys": sorted(key for key in input_keys if key), + }, + "wait_for_decision_input_requirements", + ), + _authorization_decision_closeout_check( + "decision_rejection_policy_complete", + len(decision_rejection_policy) == 10 + and "direct_database_apply_requested_from_decision_preflight" + in decision_rejection_policy, + {"decision_rejection_reason_count": len(decision_rejection_policy)}, + "wait_for_decision_rejection_policy", + ), + _authorization_decision_closeout_check( + "same_run_production_truth_required", + "fresh_production_truth_same_run" in input_keys + and decision.get("requires_fresh_production_truth_in_same_run") is True, + { + "input_keys": sorted(key for key in input_keys if key), + "requires_fresh_production_truth_in_same_run": decision.get( + "requires_fresh_production_truth_in_same_run" + ), + }, + "require_same_run_production_truth", + ), + _authorization_decision_closeout_check( + "post_apply_verifier_required", + "post_apply_verifier_reference" in input_keys + and envelope.get("requires_post_apply_verifier") is True, + { + "input_keys": sorted(key for key in input_keys if key), + "requires_post_apply_verifier": envelope.get("requires_post_apply_verifier"), + }, + "require_post_apply_verifier_artifact", + ), + _authorization_decision_closeout_check( + "migration_target_hash_locked", + bool(decision.get("target_file")) + and bool(decision.get("expected_sha256")) + and decision.get("hash_matches") is True, + { + "target_file": decision.get("target_file"), + "hash_matches": decision.get("hash_matches"), + }, + "abort_on_migration_hash_gap", + ), + _authorization_decision_closeout_check( + "secret_boundary_clean", + "secret_boundary_rejection_proof" in input_keys + and decision.get("operator_secret_boundary") == "future_shell_only" + and decision.get("reads_secret_in_preview") is False, + { + "operator_secret_boundary": decision.get("operator_secret_boundary"), + "reads_secret_in_preview": decision.get("reads_secret_in_preview"), + }, + "abort_on_secret_boundary_violation", + ), + _authorization_decision_closeout_check( + "source_lane_guard_and_package_ids_present", + bool(decision.get("preflight_id")) + and bool(decision.get("source_lane_guard_id")) + and bool(decision.get("source_closeout_package_id")) + and bool(decision.get("source_intake_id")) + and bool(decision.get("source_closeout_boundary_id")), + { + "preflight_id": decision.get("preflight_id"), + "source_lane_guard_id": decision.get("source_lane_guard_id"), + "source_closeout_package_id": decision.get("source_closeout_package_id"), + "source_intake_id": decision.get("source_intake_id"), + "source_closeout_boundary_id": decision.get("source_closeout_boundary_id"), + }, + "wait_for_source_authorization_chain_ids", + ), + _authorization_decision_closeout_check( + "preview_has_no_side_effects", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + }, + "abort_on_preview_side_effect", + ), + _authorization_decision_closeout_check( + "direct_apply_still_rejected", + envelope.get("rejects_direct_database_apply") is True + and decision.get("issues_database_apply_authorization") is False + and decision.get("ready_for_database_apply_now") is False, + { + "rejects_direct_database_apply": envelope.get("rejects_direct_database_apply"), + "issues_database_apply_authorization": decision.get( + "issues_database_apply_authorization" + ), + "ready_for_database_apply_now": decision.get("ready_for_database_apply_now"), + }, + "reject_direct_database_apply_from_closeout", + ), + _authorization_decision_closeout_check( + "manual_review_regression_absent", + int(summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY) or 0) == 0 + and decision.get("manual_review_mode") == "exception_only", + { + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + "manual_review_mode": decision.get("manual_review_mode"), + }, + "route_failed_verifier_to_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_AUTHORIZATION_DECISION_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_AUTHORIZATION_DECISION_PREFLIGHT" + ) + future_authorization_decision_closeout = { + "closeout_id": _db_apply_authorization_decision_closeout_id(preflight), + "source_preflight_id": decision.get("preflight_id"), + "source_lane_guard_id": decision.get("source_lane_guard_id"), + "source_closeout_package_id": decision.get("source_closeout_package_id"), + "source_intake_id": decision.get("source_intake_id"), + "source_closeout_boundary_id": decision.get("source_closeout_boundary_id"), + "status": closeout_status, + "ready_for_future_authorization_decision_closeout": closeout_ready, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + future_authorization_decision_package = { + "package_id": future_authorization_decision_closeout["closeout_id"], + "source_preflight_id": decision.get("preflight_id"), + "source_lane_guard_id": decision.get("source_lane_guard_id"), + "source_closeout_package_id": decision.get("source_closeout_package_id"), + "source_intake_id": decision.get("source_intake_id"), + "source_closeout_boundary_id": decision.get("source_closeout_boundary_id"), + "status": closeout_status, + "ready_for_future_authorization_decision_package": closeout_ready, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "decision_scope": "future_explicit_db_apply_authorization_decision_only", + "target_file": decision.get("target_file"), + "expected_sha256": decision.get("expected_sha256"), + "actual_sha256": decision.get("actual_sha256"), + "hash_matches": decision.get("hash_matches"), + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "post_apply_verifier_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-verifier-artifact-preview" + ), + "operator_secret_boundary": "future_shell_only", + "reads_secret_in_preview": False, + "executes_shell_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + } + decision_closeout_contract = { + "mode": "future_authorization_decision_closeout_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-decision-closeout" + ), + "source_preflight_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-decision-preflight" + ), + "machine_verifiable": True, + "permits_future_authorization_decision_lane": closeout_ready, + "issues_database_apply_authorization": False, + "ready_for_database_apply_now": False, + "writes_database": False, + "executes_in_preview": False, + "manual_review_mode": "exception_only", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_AUTHORIZATION_DECISION_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(preflight.get("success")), + "generated_at": preflight.get("generated_at"), + "source_policy": preflight.get("policy"), + "stats": preflight.get("stats") or {}, + "summary": { + "authorization_decision_closeout_ready_count": 1 if closeout_ready else 0, + "decision_closeout_check_count": len(checks), + "decision_closeout_pass_count": passed_count, + "decision_closeout_waiting_count": len(waiting_checks), + "authorization_decision_preflight_ready_count": summary.get( + "authorization_decision_preflight_ready_count", 0 + ), + "decision_input_requirement_count": len(decision_input_requirements), + "decision_rejection_reason_count": len(decision_rejection_policy), + "post_apply_verifier_required_count": ( + 1 if envelope.get("requires_post_apply_verifier") is True else 0 + ), + "same_run_truth_required_count": ( + 1 if decision.get("requires_fresh_production_truth_in_same_run") is True else 0 + ), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "future_authorization_decision_closeout": future_authorization_decision_closeout, + "future_authorization_decision_package": future_authorization_decision_package, + "decision_closeout_contract": decision_closeout_contract, + "decision_closeout_checks": checks, + "source_decision_preflight_summary": summary, + "source_decision_input_requirements": decision_input_requirements, + "source_decision_rejection_policy": decision_rejection_policy, + "safety": { + "read_only_db_apply_authorization_decision_closeout": True, + "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, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout as the machine-readable package for a future explicit authorization decision lane.", + "Keep database apply authorization and SQL execution out of this closeout.", + "Require fresh production truth, secret rejection, rollback boundary, and post-apply verifier inside the future decision run.", + ], + } + + +def build_pchome_auto_policy_db_apply_authorization_issuer_gate( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Build a no-secret issuer gate envelope without signing DB apply authorization.""" + closeout = build_pchome_auto_policy_db_apply_authorization_decision_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + decision = closeout.get("future_authorization_decision_closeout") or {} + package = closeout.get("future_authorization_decision_package") or {} + contract = closeout.get("decision_closeout_contract") or {} + summary = closeout.get("summary") or {} + rejection_policy = closeout.get("source_decision_rejection_policy") or [] + source_inputs = closeout.get("source_decision_input_requirements") or [] + source_input_keys = {item.get("key") for item in source_inputs} + side_effect_free = ( + int(summary.get("writes_script_count") or 0) == 0 + and int(summary.get("writes_artifact_count") or 0) == 0 + and int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_migration_count") or 0) == 0 + and int(summary.get("executes_endpoint_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and package.get("reads_secret_in_preview") is False + and package.get("executes_shell_in_preview") is False + and package.get("executes_sql_in_preview") is False + and package.get("writes_database_in_preview") is False + ) + required_issuer_evidence = [ + { + "key": "fresh_production_truth_same_run", + "required": True, + "source_command": "python scripts/ops/check_production_version_truth.py", + }, + { + "key": "decision_closeout_id", + "required": True, + "source_id": decision.get("closeout_id"), + }, + { + "key": "decision_preflight_id", + "required": True, + "source_id": package.get("source_preflight_id"), + }, + { + "key": "authorization_lane_guard_id", + "required": True, + "source_id": package.get("source_lane_guard_id"), + }, + { + "key": "final_request_package_id", + "required": True, + "source_id": package.get("source_closeout_package_id"), + }, + { + "key": "migration_target_hash", + "required": True, + "target_file": package.get("target_file"), + "expected_sha256": package.get("expected_sha256"), + "hash_matches": package.get("hash_matches"), + }, + { + "key": "secret_boundary_rejection", + "required": True, + "operator_secret_boundary": package.get("operator_secret_boundary"), + "secret_material_included": False, + }, + { + "key": "post_apply_verifier_reference", + "required": True, + "source_endpoint": package.get("post_apply_verifier_endpoint"), + }, + { + "key": "direct_apply_rejection_policy", + "required": True, + "source_rejection": "direct_database_apply_requested_from_decision_preflight", + }, + ] + nonsecret_authorization_claims = [ + { + "key": "source_decision_closeout_ready", + "claim": decision.get("ready_for_future_authorization_decision_closeout") is True, + }, + { + "key": "no_database_apply_authorization_issued", + "claim": package.get("issues_database_apply_authorization") is False, + }, + { + "key": "no_secret_material_included", + "claim": package.get("reads_secret_in_preview") is False, + }, + { + "key": "no_shell_sql_or_database_execution", + "claim": side_effect_free, + }, + { + "key": "target_migration_hash_locked", + "claim": package.get("hash_matches") is True, + }, + { + "key": "same_run_production_truth_required", + "claim": package.get("requires_fresh_production_truth_in_same_run") is True, + }, + { + "key": "post_apply_verifier_required", + "claim": package.get("requires_post_apply_verifier") is True, + }, + { + "key": "direct_apply_rejected_until_issuer_lane", + "claim": "direct_database_apply_requested_from_decision_preflight" + in rejection_policy, + }, + ] + checks = [ + _authorization_issuer_gate_check( + "decision_closeout_ready", + closeout.get("result") == "DB_APPLY_AUTHORIZATION_DECISION_CLOSEOUT_READY" + and decision.get("ready_for_future_authorization_decision_closeout") is True + and package.get("ready_for_future_authorization_decision_package") is True, + { + "result": closeout.get("result"), + "ready_for_future_authorization_decision_closeout": decision.get( + "ready_for_future_authorization_decision_closeout" + ), + "ready_for_future_authorization_decision_package": package.get( + "ready_for_future_authorization_decision_package" + ), + }, + "wait_for_decision_closeout", + ), + _authorization_issuer_gate_check( + "nonsecret_envelope_only", + package.get("operator_secret_boundary") == "future_shell_only" + and package.get("reads_secret_in_preview") is False, + { + "operator_secret_boundary": package.get("operator_secret_boundary"), + "reads_secret_in_preview": package.get("reads_secret_in_preview"), + }, + "abort_on_secret_material_in_envelope", + ), + _authorization_issuer_gate_check( + "source_chain_ids_present", + bool(decision.get("closeout_id")) + and bool(package.get("source_preflight_id")) + and bool(package.get("source_lane_guard_id")) + and bool(package.get("source_closeout_package_id")) + and bool(package.get("source_intake_id")) + and bool(package.get("source_closeout_boundary_id")), + { + "decision_closeout_id": decision.get("closeout_id"), + "source_preflight_id": package.get("source_preflight_id"), + "source_lane_guard_id": package.get("source_lane_guard_id"), + "source_closeout_package_id": package.get("source_closeout_package_id"), + "source_intake_id": package.get("source_intake_id"), + "source_closeout_boundary_id": package.get("source_closeout_boundary_id"), + }, + "wait_for_source_authorization_chain_ids", + ), + _authorization_issuer_gate_check( + "same_run_production_truth_required", + "fresh_production_truth_same_run" in source_input_keys + and package.get("requires_fresh_production_truth_in_same_run") is True + and int(summary.get("same_run_truth_required_count") or 0) == 1, + { + "input_keys": sorted(key for key in source_input_keys if key), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + }, + "require_same_run_production_truth", + ), + _authorization_issuer_gate_check( + "post_apply_verifier_required", + "post_apply_verifier_reference" in source_input_keys + and package.get("requires_post_apply_verifier") is True + and int(summary.get("post_apply_verifier_required_count") or 0) == 1, + { + "input_keys": sorted(key for key in source_input_keys if key), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + }, + "require_post_apply_verifier_artifact", + ), + _authorization_issuer_gate_check( + "migration_target_hash_locked", + bool(package.get("target_file")) + and bool(package.get("expected_sha256")) + and package.get("hash_matches") is True, + { + "target_file": package.get("target_file"), + "hash_matches": package.get("hash_matches"), + }, + "abort_on_migration_hash_gap", + ), + _authorization_issuer_gate_check( + "decision_package_is_no_authorization", + package.get("issues_database_apply_authorization") is False + and package.get("ready_for_database_apply_now") is False, + { + "issues_database_apply_authorization": package.get( + "issues_database_apply_authorization" + ), + "ready_for_database_apply_now": package.get("ready_for_database_apply_now"), + }, + "block_if_package_authorizes_apply", + ), + _authorization_issuer_gate_check( + "closeout_contract_is_no_write", + contract.get("issues_database_apply_authorization") is False + and contract.get("ready_for_database_apply_now") is False + and contract.get("writes_database") is False + and contract.get("executes_in_preview") is False, + { + "issues_database_apply_authorization": contract.get( + "issues_database_apply_authorization" + ), + "ready_for_database_apply_now": contract.get("ready_for_database_apply_now"), + "writes_database": contract.get("writes_database"), + "executes_in_preview": contract.get("executes_in_preview"), + }, + "block_if_contract_authorizes_write", + ), + _authorization_issuer_gate_check( + "preview_has_no_side_effects", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + }, + "abort_on_preview_side_effect", + ), + _authorization_issuer_gate_check( + "direct_apply_still_rejected", + "direct_database_apply_requested_from_decision_preflight" in rejection_policy + and package.get("issues_database_apply_authorization") is False + and package.get("ready_for_database_apply_now") is False, + { + "rejection_reason_present": ( + "direct_database_apply_requested_from_decision_preflight" in rejection_policy + ), + "issues_database_apply_authorization": package.get( + "issues_database_apply_authorization" + ), + "ready_for_database_apply_now": package.get("ready_for_database_apply_now"), + }, + "reject_direct_database_apply_from_issuer_gate", + ), + _authorization_issuer_gate_check( + "issuer_policy_requires_future_explicit_authorization", + len(required_issuer_evidence) == 9 + and len(nonsecret_authorization_claims) == 8 + and all(item.get("required") is True for item in required_issuer_evidence), + { + "required_issuer_evidence_count": len(required_issuer_evidence), + "nonsecret_authorization_claim_count": len(nonsecret_authorization_claims), + }, + "wait_for_complete_nonsecret_issuer_envelope", + ), + _authorization_issuer_gate_check( + "manual_review_regression_absent", + int(summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY) or 0) == 0 + and decision.get("manual_review_mode") == "exception_only", + { + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + "manual_review_mode": decision.get("manual_review_mode"), + }, + "route_failed_verifier_to_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + issuer_ready = not waiting_checks + issuer_status = ( + "DB_APPLY_AUTHORIZATION_ISSUER_GATE_READY" + if issuer_ready + else "WAITING_FOR_DB_APPLY_AUTHORIZATION_DECISION_CLOSEOUT" + ) + issuer_gate_id = _db_apply_authorization_issuer_gate_id(closeout) + future_authorization_issuer_gate = { + "gate_id": issuer_gate_id, + "source_decision_closeout_id": decision.get("closeout_id"), + "source_decision_preflight_id": package.get("source_preflight_id"), + "source_lane_guard_id": package.get("source_lane_guard_id"), + "source_closeout_package_id": package.get("source_closeout_package_id"), + "source_intake_id": package.get("source_intake_id"), + "source_closeout_boundary_id": package.get("source_closeout_boundary_id"), + "status": issuer_status, + "ready_for_future_authorization_issuer_lane": issuer_ready, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + final_nonsecret_authorization_envelope = { + "envelope_id": issuer_gate_id, + "source_decision_closeout_id": decision.get("closeout_id"), + "source_decision_package_id": package.get("package_id"), + "source_decision_preflight_id": package.get("source_preflight_id"), + "source_lane_guard_id": package.get("source_lane_guard_id"), + "source_closeout_package_id": package.get("source_closeout_package_id"), + "source_intake_id": package.get("source_intake_id"), + "source_closeout_boundary_id": package.get("source_closeout_boundary_id"), + "status": issuer_status, + "authorization_material_type": "nonsecret_request_envelope", + "decision_scope": "future_explicit_db_apply_authorization_issuer_lane_only", + "ready_for_future_authorization_issuer_lane": issuer_ready, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "target_file": package.get("target_file"), + "expected_sha256": package.get("expected_sha256"), + "actual_sha256": package.get("actual_sha256"), + "hash_matches": package.get("hash_matches"), + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "post_apply_verifier_endpoint": package.get("post_apply_verifier_endpoint"), + "operator_secret_boundary": "future_shell_only", + "secret_material_included": False, + "reads_secret_in_preview": False, + "executes_shell_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "nonsecret_authorization_claims": nonsecret_authorization_claims, + "required_issuer_evidence": required_issuer_evidence, + } + issuer_gate_contract = { + "mode": "future_authorization_issuer_gate_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-issuer-gate" + ), + "source_decision_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-decision-closeout" + ), + "machine_verifiable": True, + "permits_future_authorization_issuer_lane": issuer_ready, + "issues_database_apply_authorization": False, + "ready_for_database_apply_now": False, + "signs_database_apply_authorization": False, + "writes_database": False, + "executes_in_preview": False, + "manual_review_mode": "exception_only", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_AUTHORIZATION_ISSUER_GATE_POLICY, + "result": issuer_status, + "success": bool(closeout.get("success")), + "generated_at": closeout.get("generated_at"), + "source_policy": closeout.get("policy"), + "stats": closeout.get("stats") or {}, + "summary": { + "authorization_issuer_gate_ready_count": 1 if issuer_ready else 0, + "issuer_gate_check_count": len(checks), + "issuer_gate_pass_count": passed_count, + "issuer_gate_waiting_count": len(waiting_checks), + "authorization_decision_closeout_ready_count": summary.get( + "authorization_decision_closeout_ready_count", 0 + ), + "decision_closeout_check_count": summary.get("decision_closeout_check_count", 0), + "required_issuer_evidence_count": len(required_issuer_evidence), + "nonsecret_authorization_claim_count": len(nonsecret_authorization_claims), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + "decision_rejection_reason_count": summary.get("decision_rejection_reason_count", 0), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "future_authorization_issuer_gate": future_authorization_issuer_gate, + "final_nonsecret_authorization_envelope": final_nonsecret_authorization_envelope, + "issuer_gate_contract": issuer_gate_contract, + "issuer_gate_checks": checks, + "source_decision_closeout_summary": summary, + "source_decision_closeout_contract": contract, + "source_decision_rejection_policy": rejection_policy, + "safety": { + "read_only_db_apply_authorization_issuer_gate": True, + "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, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this issuer gate to hand a nonsecret envelope to a future explicit authorization signing lane.", + "Keep database apply authorization signing, secret reads, shell execution, SQL, and DB writes out of this gate.", + "Require fresh production truth, secret rejection, rollback boundary, and post-apply verifier inside the future issuer run.", + ], + } + + +def build_pchome_auto_policy_db_apply_authorization_signing_decision_preflight( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Prepare a no-secret preflight before any future authorization signing decision.""" + issuer_gate = build_pchome_auto_policy_db_apply_authorization_issuer_gate( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + gate = issuer_gate.get("future_authorization_issuer_gate") or {} + envelope = issuer_gate.get("final_nonsecret_authorization_envelope") or {} + contract = issuer_gate.get("issuer_gate_contract") or {} + summary = issuer_gate.get("summary") or {} + required_evidence = envelope.get("required_issuer_evidence") or [] + nonsecret_claims = envelope.get("nonsecret_authorization_claims") or [] + rejection_policy = issuer_gate.get("source_decision_rejection_policy") or [] + evidence_keys = {item.get("key") for item in required_evidence} + claim_keys = {item.get("key") for item in nonsecret_claims} + side_effect_free = ( + int(summary.get("writes_script_count") or 0) == 0 + and int(summary.get("writes_artifact_count") or 0) == 0 + and int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_migration_count") or 0) == 0 + and int(summary.get("executes_endpoint_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and envelope.get("reads_secret_in_preview") is False + and envelope.get("executes_shell_in_preview") is False + and envelope.get("executes_sql_in_preview") is False + and envelope.get("writes_database_in_preview") is False + and gate.get("signs_database_apply_authorization") is False + and envelope.get("signs_database_apply_authorization") is False + ) + signing_decision_input_requirements = [ + { + "key": "fresh_production_truth_same_run", + "required": True, + "source_command": "python scripts/ops/check_production_version_truth.py", + }, + { + "key": "issuer_gate_id", + "required": True, + "source_id": gate.get("gate_id"), + }, + { + "key": "nonsecret_authorization_envelope_id", + "required": True, + "source_id": envelope.get("envelope_id"), + }, + { + "key": "decision_closeout_id", + "required": True, + "source_id": envelope.get("source_decision_closeout_id"), + }, + { + "key": "decision_preflight_id", + "required": True, + "source_id": envelope.get("source_decision_preflight_id"), + }, + { + "key": "authorization_lane_guard_id", + "required": True, + "source_id": envelope.get("source_lane_guard_id"), + }, + { + "key": "migration_target_hash", + "required": True, + "target_file": envelope.get("target_file"), + "expected_sha256": envelope.get("expected_sha256"), + "hash_matches": envelope.get("hash_matches"), + }, + { + "key": "secret_boundary_rejection", + "required": True, + "operator_secret_boundary": envelope.get("operator_secret_boundary"), + "secret_material_included": envelope.get("secret_material_included"), + }, + { + "key": "post_apply_verifier_reference", + "required": True, + "source_endpoint": envelope.get("post_apply_verifier_endpoint"), + }, + { + "key": "no_signing_without_future_explicit_authorization", + "required": True, + "signs_database_apply_authorization": False, + }, + ] + signing_decision_rejection_policy = [ + "production_truth_missing_or_stale", + "issuer_gate_not_ready", + "nonsecret_authorization_envelope_missing_or_mutated", + "issuer_evidence_missing", + "nonsecret_claim_failed", + "migration_hash_missing_or_mismatch", + "secret_material_present_in_signing_preflight", + "post_apply_verifier_missing", + "direct_database_apply_requested_from_signing_preflight", + "authorization_signing_requested_from_preflight", + "preview_attempted_shell_sql_or_database_side_effect", + ] + checks = [ + _authorization_signing_decision_preflight_check( + "issuer_gate_ready", + issuer_gate.get("result") == "DB_APPLY_AUTHORIZATION_ISSUER_GATE_READY" + and gate.get("ready_for_future_authorization_issuer_lane") is True + and envelope.get("ready_for_future_authorization_issuer_lane") is True, + { + "result": issuer_gate.get("result"), + "ready_for_future_authorization_issuer_lane": gate.get( + "ready_for_future_authorization_issuer_lane" + ), + "envelope_ready_for_future_authorization_issuer_lane": envelope.get( + "ready_for_future_authorization_issuer_lane" + ), + }, + "wait_for_authorization_issuer_gate", + ), + _authorization_signing_decision_preflight_check( + "nonsecret_envelope_complete", + envelope.get("authorization_material_type") == "nonsecret_request_envelope" + and bool(envelope.get("envelope_id")) + and envelope.get("secret_material_included") is False, + { + "authorization_material_type": envelope.get("authorization_material_type"), + "envelope_id": envelope.get("envelope_id"), + "secret_material_included": envelope.get("secret_material_included"), + }, + "wait_for_nonsecret_authorization_envelope", + ), + _authorization_signing_decision_preflight_check( + "required_issuer_evidence_complete", + len(required_evidence) == 9 + and all(item.get("required") is True for item in required_evidence), + { + "required_issuer_evidence_count": len(required_evidence), + "evidence_keys": sorted(key for key in evidence_keys if key), + }, + "wait_for_required_issuer_evidence", + ), + _authorization_signing_decision_preflight_check( + "nonsecret_claims_complete", + len(nonsecret_claims) == 8 + and all(item.get("claim") is True for item in nonsecret_claims), + { + "nonsecret_authorization_claim_count": len(nonsecret_claims), + "claim_keys": sorted(key for key in claim_keys if key), + }, + "wait_for_nonsecret_authorization_claims", + ), + _authorization_signing_decision_preflight_check( + "same_run_production_truth_required", + "fresh_production_truth_same_run" in evidence_keys + and envelope.get("requires_fresh_production_truth_in_same_run") is True + and int(summary.get("same_run_truth_required_count") or 0) == 1, + { + "evidence_keys": sorted(key for key in evidence_keys if key), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + }, + "require_same_run_production_truth", + ), + _authorization_signing_decision_preflight_check( + "post_apply_verifier_required", + "post_apply_verifier_reference" in evidence_keys + and envelope.get("requires_post_apply_verifier") is True + and int(summary.get("post_apply_verifier_required_count") or 0) == 1, + { + "evidence_keys": sorted(key for key in evidence_keys if key), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + }, + "require_post_apply_verifier_artifact", + ), + _authorization_signing_decision_preflight_check( + "migration_target_hash_locked", + bool(envelope.get("target_file")) + and bool(envelope.get("expected_sha256")) + and envelope.get("hash_matches") is True, + { + "target_file": envelope.get("target_file"), + "hash_matches": envelope.get("hash_matches"), + }, + "abort_on_migration_hash_gap", + ), + _authorization_signing_decision_preflight_check( + "secret_boundary_clean", + "secret_boundary_rejection" in evidence_keys + and envelope.get("operator_secret_boundary") == "future_shell_only" + and envelope.get("secret_material_included") is False + and envelope.get("reads_secret_in_preview") is False, + { + "operator_secret_boundary": envelope.get("operator_secret_boundary"), + "secret_material_included": envelope.get("secret_material_included"), + "reads_secret_in_preview": envelope.get("reads_secret_in_preview"), + }, + "abort_on_secret_boundary_violation", + ), + _authorization_signing_decision_preflight_check( + "source_chain_ids_present", + bool(gate.get("gate_id")) + and bool(envelope.get("source_decision_closeout_id")) + and bool(envelope.get("source_decision_preflight_id")) + and bool(envelope.get("source_lane_guard_id")) + and bool(envelope.get("source_closeout_package_id")) + and bool(envelope.get("source_intake_id")) + and bool(envelope.get("source_closeout_boundary_id")), + { + "gate_id": gate.get("gate_id"), + "source_decision_closeout_id": envelope.get("source_decision_closeout_id"), + "source_decision_preflight_id": envelope.get("source_decision_preflight_id"), + "source_lane_guard_id": envelope.get("source_lane_guard_id"), + "source_closeout_package_id": envelope.get("source_closeout_package_id"), + "source_intake_id": envelope.get("source_intake_id"), + "source_closeout_boundary_id": envelope.get("source_closeout_boundary_id"), + }, + "wait_for_source_authorization_chain_ids", + ), + _authorization_signing_decision_preflight_check( + "preview_has_no_side_effects", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization": envelope.get( + "signs_database_apply_authorization" + ), + }, + "abort_on_preview_side_effect", + ), + _authorization_signing_decision_preflight_check( + "signing_and_direct_apply_still_rejected", + contract.get("signs_database_apply_authorization") is False + and contract.get("issues_database_apply_authorization") is False + and contract.get("ready_for_database_apply_now") is False + and contract.get("writes_database") is False, + { + "signs_database_apply_authorization": contract.get( + "signs_database_apply_authorization" + ), + "issues_database_apply_authorization": contract.get( + "issues_database_apply_authorization" + ), + "ready_for_database_apply_now": contract.get("ready_for_database_apply_now"), + "writes_database": contract.get("writes_database"), + }, + "reject_signing_or_direct_database_apply_from_preflight", + ), + _authorization_signing_decision_preflight_check( + "manual_review_regression_absent", + int(summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY) or 0) == 0 + and gate.get("manual_review_mode") == "exception_only", + { + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + "manual_review_mode": gate.get("manual_review_mode"), + }, + "route_failed_verifier_to_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + preflight_ready = not waiting_checks + preflight_status = ( + "DB_APPLY_AUTHORIZATION_SIGNING_DECISION_PREFLIGHT_READY" + if preflight_ready + else "WAITING_FOR_DB_APPLY_AUTHORIZATION_ISSUER_GATE" + ) + preflight_id = _db_apply_authorization_signing_decision_preflight_id(issuer_gate) + future_authorization_signing_decision_preflight = { + "preflight_id": preflight_id, + "source_issuer_gate_id": gate.get("gate_id"), + "source_decision_closeout_id": envelope.get("source_decision_closeout_id"), + "source_decision_package_id": envelope.get("source_decision_package_id"), + "source_decision_preflight_id": envelope.get("source_decision_preflight_id"), + "source_lane_guard_id": envelope.get("source_lane_guard_id"), + "source_closeout_package_id": envelope.get("source_closeout_package_id"), + "source_intake_id": envelope.get("source_intake_id"), + "source_closeout_boundary_id": envelope.get("source_closeout_boundary_id"), + "status": preflight_status, + "ready_for_future_signing_decision_preflight": preflight_ready, + "can_enter_authorization_signing_decision_lane": preflight_ready, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + signing_decision_preflight_envelope = { + "mode": "authorization_signing_decision_preflight_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-decision-preflight" + ), + "source_issuer_gate_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-issuer-gate" + ), + "machine_verifiable": True, + "allows_future_authorization_signing_decision_lane": preflight_ready, + "issues_database_apply_authorization": False, + "ready_for_database_apply_now": False, + "signs_database_apply_authorization": False, + "rejects_direct_database_apply": True, + "requires_post_apply_verifier": True, + "secret_material_required_in_preview": False, + "manual_review_mode": "exception_only", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_AUTHORIZATION_SIGNING_DECISION_PREFLIGHT_POLICY, + "result": preflight_status, + "success": bool(issuer_gate.get("success")), + "generated_at": issuer_gate.get("generated_at"), + "source_policy": issuer_gate.get("policy"), + "stats": issuer_gate.get("stats") or {}, + "summary": { + "authorization_signing_decision_preflight_ready_count": 1 if preflight_ready else 0, + "signing_decision_preflight_check_count": len(checks), + "signing_decision_preflight_pass_count": passed_count, + "signing_decision_preflight_waiting_count": len(waiting_checks), + "authorization_issuer_gate_ready_count": summary.get( + "authorization_issuer_gate_ready_count", 0 + ), + "issuer_gate_check_count": summary.get("issuer_gate_check_count", 0), + "required_issuer_evidence_count": len(required_evidence), + "nonsecret_authorization_claim_count": len(nonsecret_claims), + "signing_decision_input_requirement_count": len(signing_decision_input_requirements), + "signing_decision_rejection_reason_count": len(signing_decision_rejection_policy), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "future_authorization_signing_decision_preflight": ( + future_authorization_signing_decision_preflight + ), + "signing_decision_preflight_envelope": signing_decision_preflight_envelope, + "signing_decision_input_requirements": signing_decision_input_requirements, + "signing_decision_rejection_policy": signing_decision_rejection_policy, + "signing_decision_preflight_checks": checks, + "source_issuer_gate_summary": summary, + "source_issuer_gate_contract": contract, + "source_nonsecret_authorization_envelope": envelope, + "safety": { + "read_only_db_apply_authorization_signing_decision_preflight": True, + "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, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this preflight to verify the nonsecret envelope before a future explicit signing decision lane.", + "Keep authorization signing, secret reads, shell execution, SQL, and DB writes out of this preflight.", + "Require fresh production truth, secret rejection, rollback boundary, and post-apply verifier inside the future signing decision run.", + ], + } + + +def build_pchome_auto_policy_db_apply_authorization_signing_decision_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out signing-decision preflight as an unsigned package.""" + preflight = build_pchome_auto_policy_db_apply_authorization_signing_decision_preflight( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + decision = preflight.get("future_authorization_signing_decision_preflight") or {} + envelope = preflight.get("signing_decision_preflight_envelope") or {} + source_envelope = preflight.get("source_nonsecret_authorization_envelope") or {} + summary = preflight.get("summary") or {} + input_requirements = preflight.get("signing_decision_input_requirements") or [] + rejection_policy = preflight.get("signing_decision_rejection_policy") or [] + input_keys = {item.get("key") for item in input_requirements} + side_effect_free = ( + int(summary.get("writes_script_count") or 0) == 0 + and int(summary.get("writes_artifact_count") or 0) == 0 + and int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_migration_count") or 0) == 0 + and int(summary.get("executes_endpoint_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and int(summary.get("signs_database_apply_authorization_count") or 0) == 0 + and decision.get("signs_database_apply_authorization") is False + and envelope.get("signs_database_apply_authorization") is False + and envelope.get("secret_material_required_in_preview") is False + and source_envelope.get("secret_material_included") is False + and source_envelope.get("reads_secret_in_preview") is False + and source_envelope.get("executes_shell_in_preview") is False + and source_envelope.get("executes_sql_in_preview") is False + and source_envelope.get("writes_database_in_preview") is False + ) + checks = [ + _authorization_signing_decision_closeout_check( + "signing_decision_preflight_ready", + preflight.get("result") + == "DB_APPLY_AUTHORIZATION_SIGNING_DECISION_PREFLIGHT_READY" + and decision.get("ready_for_future_signing_decision_preflight") is True, + { + "result": preflight.get("result"), + "ready_for_future_signing_decision_preflight": decision.get( + "ready_for_future_signing_decision_preflight" + ), + }, + "wait_for_signing_decision_preflight", + ), + _authorization_signing_decision_closeout_check( + "preflight_envelope_allows_future_lane_only", + envelope.get("allows_future_authorization_signing_decision_lane") is True + and envelope.get("issues_database_apply_authorization") is False + and envelope.get("ready_for_database_apply_now") is False + and envelope.get("signs_database_apply_authorization") is False, + { + "allows_future_authorization_signing_decision_lane": envelope.get( + "allows_future_authorization_signing_decision_lane" + ), + "issues_database_apply_authorization": envelope.get( + "issues_database_apply_authorization" + ), + "ready_for_database_apply_now": envelope.get("ready_for_database_apply_now"), + "signs_database_apply_authorization": envelope.get( + "signs_database_apply_authorization" + ), + }, + "block_if_preflight_envelope_signs_or_authorizes", + ), + _authorization_signing_decision_closeout_check( + "signing_decision_inputs_complete", + len(input_requirements) == 10 + and all(item.get("required") is True for item in input_requirements), + { + "signing_decision_input_requirement_count": len(input_requirements), + "input_keys": sorted(key for key in input_keys if key), + }, + "wait_for_signing_decision_input_requirements", + ), + _authorization_signing_decision_closeout_check( + "signing_decision_rejection_policy_complete", + len(rejection_policy) == 11 + and "authorization_signing_requested_from_preflight" in rejection_policy + and "direct_database_apply_requested_from_signing_preflight" in rejection_policy, + {"signing_decision_rejection_reason_count": len(rejection_policy)}, + "wait_for_signing_decision_rejection_policy", + ), + _authorization_signing_decision_closeout_check( + "unsigned_package_source_envelope_complete", + source_envelope.get("authorization_material_type") == "nonsecret_request_envelope" + and bool(source_envelope.get("envelope_id")) + and source_envelope.get("secret_material_included") is False, + { + "authorization_material_type": source_envelope.get("authorization_material_type"), + "envelope_id": source_envelope.get("envelope_id"), + "secret_material_included": source_envelope.get("secret_material_included"), + }, + "wait_for_nonsecret_source_envelope", + ), + _authorization_signing_decision_closeout_check( + "source_chain_ids_present", + bool(decision.get("preflight_id")) + and bool(decision.get("source_issuer_gate_id")) + and bool(decision.get("source_decision_closeout_id")) + and bool(decision.get("source_decision_preflight_id")) + and bool(decision.get("source_lane_guard_id")) + and bool(decision.get("source_closeout_package_id")) + and bool(decision.get("source_intake_id")) + and bool(decision.get("source_closeout_boundary_id")), + { + "preflight_id": decision.get("preflight_id"), + "source_issuer_gate_id": decision.get("source_issuer_gate_id"), + "source_decision_closeout_id": decision.get("source_decision_closeout_id"), + "source_decision_preflight_id": decision.get("source_decision_preflight_id"), + "source_lane_guard_id": decision.get("source_lane_guard_id"), + "source_closeout_package_id": decision.get("source_closeout_package_id"), + "source_intake_id": decision.get("source_intake_id"), + "source_closeout_boundary_id": decision.get("source_closeout_boundary_id"), + }, + "wait_for_source_authorization_chain_ids", + ), + _authorization_signing_decision_closeout_check( + "same_run_production_truth_required", + "fresh_production_truth_same_run" in input_keys + and source_envelope.get("requires_fresh_production_truth_in_same_run") is True + and int(summary.get("same_run_truth_required_count") or 0) == 1, + { + "input_keys": sorted(key for key in input_keys if key), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + }, + "require_same_run_production_truth", + ), + _authorization_signing_decision_closeout_check( + "post_apply_verifier_required", + "post_apply_verifier_reference" in input_keys + and source_envelope.get("requires_post_apply_verifier") is True + and int(summary.get("post_apply_verifier_required_count") or 0) == 1, + { + "input_keys": sorted(key for key in input_keys if key), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + }, + "require_post_apply_verifier_artifact", + ), + _authorization_signing_decision_closeout_check( + "migration_target_hash_locked", + bool(source_envelope.get("target_file")) + and bool(source_envelope.get("expected_sha256")) + and source_envelope.get("hash_matches") is True, + { + "target_file": source_envelope.get("target_file"), + "hash_matches": source_envelope.get("hash_matches"), + }, + "abort_on_migration_hash_gap", + ), + _authorization_signing_decision_closeout_check( + "secret_boundary_clean", + "secret_boundary_rejection" in input_keys + and source_envelope.get("operator_secret_boundary") == "future_shell_only" + and source_envelope.get("secret_material_included") is False + and source_envelope.get("reads_secret_in_preview") is False, + { + "operator_secret_boundary": source_envelope.get("operator_secret_boundary"), + "secret_material_included": source_envelope.get("secret_material_included"), + "reads_secret_in_preview": source_envelope.get("reads_secret_in_preview"), + }, + "abort_on_secret_boundary_violation", + ), + _authorization_signing_decision_closeout_check( + "preview_has_no_side_effects_and_no_signing", + side_effect_free + and envelope.get("rejects_direct_database_apply") is True + and decision.get("issues_database_apply_authorization") is False + and decision.get("ready_for_database_apply_now") is False, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + "rejects_direct_database_apply": envelope.get("rejects_direct_database_apply"), + }, + "abort_on_preview_side_effect_or_signing", + ), + _authorization_signing_decision_closeout_check( + "manual_review_regression_absent", + int(summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY) or 0) == 0 + and decision.get("manual_review_mode") == "exception_only", + { + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + "manual_review_mode": decision.get("manual_review_mode"), + }, + "route_failed_verifier_to_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_AUTHORIZATION_SIGNING_DECISION_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_AUTHORIZATION_SIGNING_DECISION_PREFLIGHT" + ) + closeout_id = _db_apply_authorization_signing_decision_closeout_id(preflight) + future_authorization_signing_decision_closeout = { + "closeout_id": closeout_id, + "source_signing_decision_preflight_id": decision.get("preflight_id"), + "source_issuer_gate_id": decision.get("source_issuer_gate_id"), + "source_decision_closeout_id": decision.get("source_decision_closeout_id"), + "source_decision_package_id": decision.get("source_decision_package_id"), + "source_decision_preflight_id": decision.get("source_decision_preflight_id"), + "source_lane_guard_id": decision.get("source_lane_guard_id"), + "source_closeout_package_id": decision.get("source_closeout_package_id"), + "source_intake_id": decision.get("source_intake_id"), + "source_closeout_boundary_id": decision.get("source_closeout_boundary_id"), + "status": closeout_status, + "ready_for_future_signing_decision_closeout": closeout_ready, + "can_enter_unsigned_signing_decision_package_lane": closeout_ready, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + unsigned_signing_decision_package = { + "package_id": closeout_id, + "source_signing_decision_preflight_id": decision.get("preflight_id"), + "source_issuer_gate_id": decision.get("source_issuer_gate_id"), + "source_nonsecret_envelope_id": source_envelope.get("envelope_id"), + "source_decision_closeout_id": decision.get("source_decision_closeout_id"), + "source_decision_package_id": decision.get("source_decision_package_id"), + "source_decision_preflight_id": decision.get("source_decision_preflight_id"), + "source_lane_guard_id": decision.get("source_lane_guard_id"), + "source_closeout_package_id": decision.get("source_closeout_package_id"), + "source_intake_id": decision.get("source_intake_id"), + "source_closeout_boundary_id": decision.get("source_closeout_boundary_id"), + "status": closeout_status, + "ready_for_future_unsigned_signing_decision_package": closeout_ready, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "decision_scope": "future_explicit_db_apply_authorization_signing_decision_only", + "authorization_material_type": "unsigned_signing_decision_package", + "target_file": source_envelope.get("target_file"), + "expected_sha256": source_envelope.get("expected_sha256"), + "actual_sha256": source_envelope.get("actual_sha256"), + "hash_matches": source_envelope.get("hash_matches"), + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "post_apply_verifier_endpoint": source_envelope.get("post_apply_verifier_endpoint"), + "operator_secret_boundary": "future_shell_only", + "secret_material_included": False, + "secret_material_required_in_preview": False, + "reads_secret_in_preview": False, + "executes_shell_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "signing_decision_input_requirements": input_requirements, + "signing_decision_rejection_policy": rejection_policy, + } + signing_decision_closeout_contract = { + "mode": "unsigned_signing_decision_closeout_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-decision-closeout" + ), + "source_signing_decision_preflight_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-decision-preflight" + ), + "machine_verifiable": True, + "permits_future_unsigned_signing_decision_package_lane": closeout_ready, + "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", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_AUTHORIZATION_SIGNING_DECISION_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(preflight.get("success")), + "generated_at": preflight.get("generated_at"), + "source_policy": preflight.get("policy"), + "stats": preflight.get("stats") or {}, + "summary": { + "authorization_signing_decision_closeout_ready_count": 1 if closeout_ready else 0, + "signing_decision_closeout_check_count": len(checks), + "signing_decision_closeout_pass_count": passed_count, + "signing_decision_closeout_waiting_count": len(waiting_checks), + "authorization_signing_decision_preflight_ready_count": summary.get( + "authorization_signing_decision_preflight_ready_count", 0 + ), + "signing_decision_preflight_check_count": summary.get( + "signing_decision_preflight_check_count", 0 + ), + "signing_decision_input_requirement_count": len(input_requirements), + "signing_decision_rejection_reason_count": len(rejection_policy), + "required_issuer_evidence_count": summary.get("required_issuer_evidence_count", 0), + "nonsecret_authorization_claim_count": summary.get( + "nonsecret_authorization_claim_count", 0 + ), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "future_authorization_signing_decision_closeout": ( + future_authorization_signing_decision_closeout + ), + "unsigned_signing_decision_package": unsigned_signing_decision_package, + "signing_decision_closeout_contract": signing_decision_closeout_contract, + "signing_decision_closeout_checks": checks, + "source_signing_decision_preflight_summary": summary, + "source_signing_decision_preflight_envelope": envelope, + "source_nonsecret_authorization_envelope": source_envelope, + "safety": { + "read_only_db_apply_authorization_signing_decision_closeout": True, + "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, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout as the unsigned package for a future explicit authorization signing decision lane.", + "Keep authorization signing, secret reads, shell execution, SQL, and DB writes out of this closeout.", + "Require fresh production truth, secret rejection, rollback boundary, and post-apply verifier inside the future signing decision run.", + ], + } + + +def build_pchome_auto_policy_db_apply_authorization_signing_issuer_guard( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Guard the future signing issuer lane without issuing authorization.""" + closeout = build_pchome_auto_policy_db_apply_authorization_signing_decision_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + signing_closeout = closeout.get("future_authorization_signing_decision_closeout") or {} + package = closeout.get("unsigned_signing_decision_package") or {} + contract = closeout.get("signing_decision_closeout_contract") or {} + summary = closeout.get("summary") or {} + input_requirements = package.get("signing_decision_input_requirements") or [] + rejection_policy = package.get("signing_decision_rejection_policy") or [] + input_keys = {item.get("key") for item in input_requirements} + side_effect_free = ( + int(summary.get("writes_script_count") or 0) == 0 + and int(summary.get("writes_artifact_count") or 0) == 0 + and int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_migration_count") or 0) == 0 + and int(summary.get("executes_endpoint_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and int(summary.get("signs_database_apply_authorization_count") or 0) == 0 + and signing_closeout.get("signs_database_apply_authorization") is False + and package.get("signs_database_apply_authorization") is False + and package.get("secret_material_included") is False + and package.get("secret_material_required_in_preview") is False + and package.get("reads_secret_in_preview") is False + and package.get("executes_shell_in_preview") is False + and package.get("executes_sql_in_preview") is False + and package.get("writes_database_in_preview") is False + ) + checks = [ + _authorization_signing_issuer_guard_check( + "signing_decision_closeout_ready", + closeout.get("result") == "DB_APPLY_AUTHORIZATION_SIGNING_DECISION_CLOSEOUT_READY" + and signing_closeout.get("ready_for_future_signing_decision_closeout") is True + and package.get("ready_for_future_unsigned_signing_decision_package") is True, + { + "result": closeout.get("result"), + "ready_for_future_signing_decision_closeout": signing_closeout.get( + "ready_for_future_signing_decision_closeout" + ), + "ready_for_future_unsigned_signing_decision_package": package.get( + "ready_for_future_unsigned_signing_decision_package" + ), + }, + "wait_for_signing_decision_closeout", + ), + _authorization_signing_issuer_guard_check( + "unsigned_signing_decision_package_complete", + package.get("authorization_material_type") == "unsigned_signing_decision_package" + and bool(package.get("package_id")) + and package.get("ready_for_future_unsigned_signing_decision_package") is True, + { + "authorization_material_type": package.get("authorization_material_type"), + "package_id": package.get("package_id"), + "ready_for_future_unsigned_signing_decision_package": package.get( + "ready_for_future_unsigned_signing_decision_package" + ), + }, + "wait_for_unsigned_signing_decision_package", + ), + _authorization_signing_issuer_guard_check( + "unsigned_package_is_no_authorization_or_signing", + package.get("issues_database_apply_authorization") is False + and package.get("ready_for_database_apply_now") is False + and package.get("signs_database_apply_authorization") is False, + { + "issues_database_apply_authorization": package.get( + "issues_database_apply_authorization" + ), + "ready_for_database_apply_now": package.get("ready_for_database_apply_now"), + "signs_database_apply_authorization": package.get( + "signs_database_apply_authorization" + ), + }, + "block_if_unsigned_package_authorizes_or_signs", + ), + _authorization_signing_issuer_guard_check( + "source_chain_ids_present", + bool(signing_closeout.get("closeout_id")) + and bool(package.get("source_signing_decision_preflight_id")) + and bool(package.get("source_issuer_gate_id")) + and bool(package.get("source_nonsecret_envelope_id")) + and bool(package.get("source_decision_closeout_id")) + and bool(package.get("source_decision_preflight_id")) + and bool(package.get("source_lane_guard_id")) + and bool(package.get("source_closeout_package_id")) + and bool(package.get("source_intake_id")) + and bool(package.get("source_closeout_boundary_id")), + { + "closeout_id": signing_closeout.get("closeout_id"), + "source_signing_decision_preflight_id": package.get( + "source_signing_decision_preflight_id" + ), + "source_issuer_gate_id": package.get("source_issuer_gate_id"), + "source_nonsecret_envelope_id": package.get("source_nonsecret_envelope_id"), + "source_decision_closeout_id": package.get("source_decision_closeout_id"), + "source_decision_preflight_id": package.get("source_decision_preflight_id"), + "source_lane_guard_id": package.get("source_lane_guard_id"), + "source_closeout_package_id": package.get("source_closeout_package_id"), + "source_intake_id": package.get("source_intake_id"), + "source_closeout_boundary_id": package.get("source_closeout_boundary_id"), + }, + "wait_for_source_authorization_chain_ids", + ), + _authorization_signing_issuer_guard_check( + "same_run_production_truth_required", + "fresh_production_truth_same_run" in input_keys + and package.get("requires_fresh_production_truth_in_same_run") is True + and int(summary.get("same_run_truth_required_count") or 0) == 1, + { + "input_keys": sorted(key for key in input_keys if key), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + }, + "require_same_run_production_truth", + ), + _authorization_signing_issuer_guard_check( + "post_apply_verifier_required", + "post_apply_verifier_reference" in input_keys + and package.get("requires_post_apply_verifier") is True + and int(summary.get("post_apply_verifier_required_count") or 0) == 1, + { + "input_keys": sorted(key for key in input_keys if key), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + }, + "require_post_apply_verifier_artifact", + ), + _authorization_signing_issuer_guard_check( + "migration_target_hash_locked", + bool(package.get("target_file")) + and bool(package.get("expected_sha256")) + and package.get("hash_matches") is True, + { + "target_file": package.get("target_file"), + "hash_matches": package.get("hash_matches"), + }, + "abort_on_migration_hash_gap", + ), + _authorization_signing_issuer_guard_check( + "secret_boundary_clean", + "secret_boundary_rejection" in input_keys + and package.get("operator_secret_boundary") == "future_shell_only" + and package.get("secret_material_included") is False + and package.get("secret_material_required_in_preview") is False + and package.get("reads_secret_in_preview") is False, + { + "operator_secret_boundary": package.get("operator_secret_boundary"), + "secret_material_included": package.get("secret_material_included"), + "secret_material_required_in_preview": package.get( + "secret_material_required_in_preview" + ), + "reads_secret_in_preview": package.get("reads_secret_in_preview"), + }, + "abort_on_secret_boundary_violation", + ), + _authorization_signing_issuer_guard_check( + "signing_inputs_and_rejection_policy_complete", + len(input_requirements) == 10 + and len(rejection_policy) == 11 + and "authorization_signing_requested_from_preflight" in rejection_policy + and "direct_database_apply_requested_from_signing_preflight" in rejection_policy, + { + "signing_decision_input_requirement_count": len(input_requirements), + "signing_decision_rejection_reason_count": len(rejection_policy), + }, + "wait_for_signing_inputs_and_rejection_policy", + ), + _authorization_signing_issuer_guard_check( + "preview_has_no_side_effects_and_no_signing", + side_effect_free + and signing_closeout.get("issues_database_apply_authorization") is False + and signing_closeout.get("ready_for_database_apply_now") is False, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + }, + "abort_on_preview_side_effect_or_signing", + ), + _authorization_signing_issuer_guard_check( + "signable_boundary_is_future_only", + contract.get("permits_future_unsigned_signing_decision_package_lane") is True + and contract.get("issues_database_apply_authorization") is False + and contract.get("ready_for_database_apply_now") is False + and contract.get("signs_database_apply_authorization") is False + and contract.get("writes_database") is False, + { + "permits_future_unsigned_signing_decision_package_lane": contract.get( + "permits_future_unsigned_signing_decision_package_lane" + ), + "issues_database_apply_authorization": contract.get( + "issues_database_apply_authorization" + ), + "ready_for_database_apply_now": contract.get("ready_for_database_apply_now"), + "signs_database_apply_authorization": contract.get( + "signs_database_apply_authorization" + ), + "writes_database": contract.get("writes_database"), + }, + "block_if_signable_boundary_authorizes_now", + ), + _authorization_signing_issuer_guard_check( + "manual_review_regression_absent", + int(summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY) or 0) == 0 + and signing_closeout.get("manual_review_mode") == "exception_only", + { + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + "manual_review_mode": signing_closeout.get("manual_review_mode"), + }, + "route_failed_verifier_to_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + guard_ready = not waiting_checks + guard_status = ( + "DB_APPLY_AUTHORIZATION_SIGNING_ISSUER_GUARD_READY" + if guard_ready + else "WAITING_FOR_DB_APPLY_AUTHORIZATION_SIGNING_DECISION_CLOSEOUT" + ) + guard_id = _db_apply_authorization_signing_issuer_guard_id(closeout) + future_authorization_signing_issuer_guard = { + "guard_id": guard_id, + "source_signing_decision_closeout_id": signing_closeout.get("closeout_id"), + "source_signing_decision_preflight_id": package.get( + "source_signing_decision_preflight_id" + ), + "source_issuer_gate_id": package.get("source_issuer_gate_id"), + "source_nonsecret_envelope_id": package.get("source_nonsecret_envelope_id"), + "source_decision_closeout_id": package.get("source_decision_closeout_id"), + "source_decision_preflight_id": package.get("source_decision_preflight_id"), + "source_lane_guard_id": package.get("source_lane_guard_id"), + "source_closeout_package_id": package.get("source_closeout_package_id"), + "source_intake_id": package.get("source_intake_id"), + "source_closeout_boundary_id": package.get("source_closeout_boundary_id"), + "status": guard_status, + "ready_for_future_signing_issuer_guard": guard_ready, + "can_enter_future_authorization_signing_issuer_lane": guard_ready, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + signable_request_boundary = { + "boundary_id": guard_id, + "source_signing_decision_closeout_id": signing_closeout.get("closeout_id"), + "source_unsigned_signing_decision_package_id": package.get("package_id"), + "source_signing_decision_preflight_id": package.get( + "source_signing_decision_preflight_id" + ), + "source_issuer_gate_id": package.get("source_issuer_gate_id"), + "source_nonsecret_envelope_id": package.get("source_nonsecret_envelope_id"), + "source_decision_closeout_id": package.get("source_decision_closeout_id"), + "source_decision_preflight_id": package.get("source_decision_preflight_id"), + "source_lane_guard_id": package.get("source_lane_guard_id"), + "source_closeout_package_id": package.get("source_closeout_package_id"), + "source_intake_id": package.get("source_intake_id"), + "source_closeout_boundary_id": package.get("source_closeout_boundary_id"), + "status": guard_status, + "request_boundary_type": "future_signable_request_boundary", + "ready_for_future_signable_request_boundary": guard_ready, + "can_enter_future_authorization_signing_issuer_lane": guard_ready, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "target_file": package.get("target_file"), + "expected_sha256": package.get("expected_sha256"), + "actual_sha256": package.get("actual_sha256"), + "hash_matches": package.get("hash_matches"), + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "post_apply_verifier_endpoint": package.get("post_apply_verifier_endpoint"), + "operator_secret_boundary": "future_shell_only", + "secret_material_included": False, + "secret_material_required_in_preview": False, + "reads_secret_in_preview": False, + "executes_shell_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "signing_decision_input_requirement_count": len(input_requirements), + "signing_decision_rejection_reason_count": len(rejection_policy), + } + signing_issuer_guard_contract = { + "mode": "future_signing_issuer_guard_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-issuer-guard" + ), + "source_signing_decision_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-decision-closeout" + ), + "machine_verifiable": True, + "permits_future_authorization_signing_issuer_lane": guard_ready, + "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", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_AUTHORIZATION_SIGNING_ISSUER_GUARD_POLICY, + "result": guard_status, + "success": bool(closeout.get("success")), + "generated_at": closeout.get("generated_at"), + "source_policy": closeout.get("policy"), + "stats": closeout.get("stats") or {}, + "summary": { + "authorization_signing_issuer_guard_ready_count": 1 if guard_ready else 0, + "signing_issuer_guard_check_count": len(checks), + "signing_issuer_guard_pass_count": passed_count, + "signing_issuer_guard_waiting_count": len(waiting_checks), + "authorization_signing_decision_closeout_ready_count": summary.get( + "authorization_signing_decision_closeout_ready_count", 0 + ), + "signing_decision_closeout_check_count": summary.get( + "signing_decision_closeout_check_count", 0 + ), + "signing_decision_input_requirement_count": len(input_requirements), + "signing_decision_rejection_reason_count": len(rejection_policy), + "required_issuer_evidence_count": summary.get("required_issuer_evidence_count", 0), + "nonsecret_authorization_claim_count": summary.get( + "nonsecret_authorization_claim_count", 0 + ), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "future_authorization_signing_issuer_guard": future_authorization_signing_issuer_guard, + "signable_request_boundary": signable_request_boundary, + "signing_issuer_guard_contract": signing_issuer_guard_contract, + "signing_issuer_guard_checks": checks, + "source_signing_decision_closeout_summary": summary, + "source_signing_decision_closeout_contract": contract, + "source_unsigned_signing_decision_package": package, + "safety": { + "read_only_db_apply_authorization_signing_issuer_guard": True, + "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, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this guard to pass a signable request boundary to a future explicit authorization signing issuer lane.", + "Keep authorization signing, secret reads, shell execution, SQL, and DB writes out of this guard.", + "Require fresh production truth, secret rejection, rollback boundary, and post-apply verifier inside the future signing issuer run.", + ], + } + + +def build_pchome_auto_policy_db_apply_authorization_signing_issuer_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out the future signing issuer boundary without signing authorization.""" + guard = build_pchome_auto_policy_db_apply_authorization_signing_issuer_guard( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + issuer_guard = guard.get("future_authorization_signing_issuer_guard") or {} + boundary = guard.get("signable_request_boundary") or {} + contract = guard.get("signing_issuer_guard_contract") or {} + summary = guard.get("summary") or {} + safety = guard.get("safety") or {} + side_effect_free = ( + int(summary.get("writes_script_count") or 0) == 0 + and int(summary.get("writes_artifact_count") or 0) == 0 + and int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_migration_count") or 0) == 0 + and int(summary.get("executes_endpoint_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and int(summary.get("signs_database_apply_authorization_count") or 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_script") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("signs_database_apply_authorization") is False + and boundary.get("secret_material_included") is False + and boundary.get("secret_material_required_in_preview") is False + and boundary.get("reads_secret_in_preview") is False + and boundary.get("executes_shell_in_preview") is False + and boundary.get("executes_sql_in_preview") is False + and boundary.get("writes_database_in_preview") is False + ) + checks = [ + _authorization_signing_issuer_closeout_check( + "signing_issuer_guard_ready", + guard.get("result") == "DB_APPLY_AUTHORIZATION_SIGNING_ISSUER_GUARD_READY" + and issuer_guard.get("ready_for_future_signing_issuer_guard") is True + and boundary.get("ready_for_future_signable_request_boundary") is True, + { + "result": guard.get("result"), + "ready_for_future_signing_issuer_guard": issuer_guard.get( + "ready_for_future_signing_issuer_guard" + ), + "ready_for_future_signable_request_boundary": boundary.get( + "ready_for_future_signable_request_boundary" + ), + }, + "wait_for_signing_issuer_guard", + ), + _authorization_signing_issuer_closeout_check( + "final_signable_boundary_complete", + bool(boundary.get("boundary_id")) + and boundary.get("request_boundary_type") == "future_signable_request_boundary" + and boundary.get("can_enter_future_authorization_signing_issuer_lane") is True, + { + "boundary_id": boundary.get("boundary_id"), + "request_boundary_type": boundary.get("request_boundary_type"), + "can_enter_future_authorization_signing_issuer_lane": boundary.get( + "can_enter_future_authorization_signing_issuer_lane" + ), + }, + "wait_for_signable_request_boundary", + ), + _authorization_signing_issuer_closeout_check( + "source_chain_ids_present", + bool(issuer_guard.get("guard_id")) + and bool(boundary.get("source_signing_decision_closeout_id")) + and bool(boundary.get("source_unsigned_signing_decision_package_id")) + and bool(boundary.get("source_signing_decision_preflight_id")) + and bool(boundary.get("source_issuer_gate_id")) + and bool(boundary.get("source_nonsecret_envelope_id")) + and bool(boundary.get("source_decision_closeout_id")) + and bool(boundary.get("source_decision_preflight_id")) + and bool(boundary.get("source_lane_guard_id")) + and bool(boundary.get("source_closeout_package_id")) + and bool(boundary.get("source_intake_id")) + and bool(boundary.get("source_closeout_boundary_id")), + { + "guard_id": issuer_guard.get("guard_id"), + "source_signing_decision_closeout_id": boundary.get( + "source_signing_decision_closeout_id" + ), + "source_unsigned_signing_decision_package_id": boundary.get( + "source_unsigned_signing_decision_package_id" + ), + "source_issuer_gate_id": boundary.get("source_issuer_gate_id"), + "source_nonsecret_envelope_id": boundary.get("source_nonsecret_envelope_id"), + }, + "wait_for_source_authorization_chain_ids", + ), + _authorization_signing_issuer_closeout_check( + "same_run_production_truth_required", + boundary.get("requires_fresh_production_truth_in_same_run") is True + and int(summary.get("same_run_truth_required_count") or 0) == 1, + { + "requires_fresh_production_truth_in_same_run": boundary.get( + "requires_fresh_production_truth_in_same_run" + ), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + }, + "require_same_run_production_truth", + ), + _authorization_signing_issuer_closeout_check( + "post_apply_verifier_required", + boundary.get("requires_post_apply_verifier") is True + and bool(boundary.get("post_apply_verifier_endpoint")) + and int(summary.get("post_apply_verifier_required_count") or 0) == 1, + { + "requires_post_apply_verifier": boundary.get("requires_post_apply_verifier"), + "post_apply_verifier_endpoint": boundary.get("post_apply_verifier_endpoint"), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + }, + "require_post_apply_verifier", + ), + _authorization_signing_issuer_closeout_check( + "migration_file_hash_locked", + boundary.get("target_file") == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and boundary.get("hash_matches") is True + and bool(boundary.get("expected_sha256")) + and boundary.get("expected_sha256") == boundary.get("actual_sha256"), + { + "target_file": boundary.get("target_file"), + "hash_matches": boundary.get("hash_matches"), + "expected_sha256": boundary.get("expected_sha256"), + "actual_sha256": boundary.get("actual_sha256"), + }, + "abort_on_migration_file_hash_mismatch", + ), + _authorization_signing_issuer_closeout_check( + "signing_inputs_and_rejection_policy_carried_forward", + int(summary.get("signing_decision_input_requirement_count") or 0) == 10 + and int(summary.get("signing_decision_rejection_reason_count") or 0) == 11 + and boundary.get("signing_decision_input_requirement_count") == 10 + and boundary.get("signing_decision_rejection_reason_count") == 11, + { + "signing_decision_input_requirement_count": summary.get( + "signing_decision_input_requirement_count", 0 + ), + "signing_decision_rejection_reason_count": summary.get( + "signing_decision_rejection_reason_count", 0 + ), + }, + "wait_for_signing_input_contract", + ), + _authorization_signing_issuer_closeout_check( + "contract_is_future_only_and_non_executing", + contract.get("permits_future_authorization_signing_issuer_lane") is True + and contract.get("issues_database_apply_authorization") is False + and contract.get("ready_for_database_apply_now") is False + and contract.get("signs_database_apply_authorization") is False + and contract.get("writes_database") is False + and contract.get("executes_in_preview") is False, + { + "permits_future_authorization_signing_issuer_lane": contract.get( + "permits_future_authorization_signing_issuer_lane" + ), + "issues_database_apply_authorization": contract.get( + "issues_database_apply_authorization" + ), + "ready_for_database_apply_now": contract.get("ready_for_database_apply_now"), + "signs_database_apply_authorization": contract.get( + "signs_database_apply_authorization" + ), + }, + "block_if_contract_authorizes_or_executes", + ), + _authorization_signing_issuer_closeout_check( + "signable_boundary_has_no_secret_material", + boundary.get("operator_secret_boundary") == "future_shell_only" + and boundary.get("secret_material_included") is False + and boundary.get("secret_material_required_in_preview") is False + and boundary.get("reads_secret_in_preview") is False, + { + "operator_secret_boundary": boundary.get("operator_secret_boundary"), + "secret_material_included": boundary.get("secret_material_included"), + "secret_material_required_in_preview": boundary.get( + "secret_material_required_in_preview" + ), + "reads_secret_in_preview": boundary.get("reads_secret_in_preview"), + }, + "abort_on_secret_boundary_violation", + ), + _authorization_signing_issuer_closeout_check( + "final_package_does_not_authorize_sign_or_apply", + issuer_guard.get("issues_database_apply_authorization") is False + and issuer_guard.get("ready_for_database_apply_now") is False + and issuer_guard.get("signs_database_apply_authorization") is False + and boundary.get("issues_database_apply_authorization") is False + and boundary.get("ready_for_database_apply_now") is False + and boundary.get("signs_database_apply_authorization") is False, + { + "issuer_guard_issues_database_apply_authorization": issuer_guard.get( + "issues_database_apply_authorization" + ), + "issuer_guard_ready_for_database_apply_now": issuer_guard.get( + "ready_for_database_apply_now" + ), + "boundary_signs_database_apply_authorization": boundary.get( + "signs_database_apply_authorization" + ), + }, + "block_if_closeout_authorizes_signs_or_applies", + ), + _authorization_signing_issuer_closeout_check( + "preview_has_no_side_effects_and_no_signing", + side_effect_free, + { + "writes_script_count": summary.get("writes_script_count", 0), + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + }, + "abort_on_preview_side_effect_or_signing", + ), + _authorization_signing_issuer_closeout_check( + "manual_review_regression_absent", + int(summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY) or 0) == 0 + and issuer_guard.get("manual_review_mode") == "exception_only", + { + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + "manual_review_mode": issuer_guard.get("manual_review_mode"), + }, + "route_failed_verifier_to_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_AUTHORIZATION_SIGNING_ISSUER_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_AUTHORIZATION_SIGNING_ISSUER_GUARD" + ) + closeout_id = _db_apply_authorization_signing_issuer_closeout_id(guard) + future_authorization_signing_issuer_closeout = { + "closeout_id": closeout_id, + "source_signing_issuer_guard_id": issuer_guard.get("guard_id"), + "source_signable_request_boundary_id": boundary.get("boundary_id"), + "source_signing_decision_closeout_id": boundary.get( + "source_signing_decision_closeout_id" + ), + "source_unsigned_signing_decision_package_id": boundary.get( + "source_unsigned_signing_decision_package_id" + ), + "source_signing_decision_preflight_id": boundary.get( + "source_signing_decision_preflight_id" + ), + "source_issuer_gate_id": boundary.get("source_issuer_gate_id"), + "source_nonsecret_envelope_id": boundary.get("source_nonsecret_envelope_id"), + "source_decision_closeout_id": boundary.get("source_decision_closeout_id"), + "source_decision_preflight_id": boundary.get("source_decision_preflight_id"), + "source_lane_guard_id": boundary.get("source_lane_guard_id"), + "source_closeout_package_id": boundary.get("source_closeout_package_id"), + "source_intake_id": boundary.get("source_intake_id"), + "source_closeout_boundary_id": boundary.get("source_closeout_boundary_id"), + "status": closeout_status, + "ready_for_future_signing_issuer_closeout": closeout_ready, + "can_enter_future_final_signable_request_package_lane": closeout_ready, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + final_signable_request_package = { + "package_id": closeout_id, + "source_signing_issuer_guard_id": issuer_guard.get("guard_id"), + "source_signable_request_boundary_id": boundary.get("boundary_id"), + "source_unsigned_signing_decision_package_id": boundary.get( + "source_unsigned_signing_decision_package_id" + ), + "source_signing_decision_closeout_id": boundary.get( + "source_signing_decision_closeout_id" + ), + "source_signing_decision_preflight_id": boundary.get( + "source_signing_decision_preflight_id" + ), + "source_issuer_gate_id": boundary.get("source_issuer_gate_id"), + "source_nonsecret_envelope_id": boundary.get("source_nonsecret_envelope_id"), + "source_decision_closeout_id": boundary.get("source_decision_closeout_id"), + "source_decision_preflight_id": boundary.get("source_decision_preflight_id"), + "source_lane_guard_id": boundary.get("source_lane_guard_id"), + "source_closeout_package_id": boundary.get("source_closeout_package_id"), + "source_intake_id": boundary.get("source_intake_id"), + "source_closeout_boundary_id": boundary.get("source_closeout_boundary_id"), + "status": closeout_status, + "authorization_material_type": "final_signable_request_package", + "ready_for_future_final_signable_request_package": closeout_ready, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "target_file": boundary.get("target_file"), + "expected_sha256": boundary.get("expected_sha256"), + "actual_sha256": boundary.get("actual_sha256"), + "hash_matches": boundary.get("hash_matches"), + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "post_apply_verifier_endpoint": boundary.get("post_apply_verifier_endpoint"), + "operator_secret_boundary": "future_shell_only", + "secret_material_included": False, + "secret_material_required_in_preview": False, + "reads_secret_in_preview": False, + "executes_shell_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "signing_decision_input_requirement_count": boundary.get( + "signing_decision_input_requirement_count" + ), + "signing_decision_rejection_reason_count": boundary.get( + "signing_decision_rejection_reason_count" + ), + } + signing_issuer_closeout_contract = { + "mode": "final_signable_request_closeout_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-issuer-closeout" + ), + "source_signing_issuer_guard_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-issuer-guard" + ), + "machine_verifiable": True, + "permits_future_final_signable_request_package_lane": closeout_ready, + "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", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_AUTHORIZATION_SIGNING_ISSUER_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(guard.get("success")), + "generated_at": guard.get("generated_at"), + "source_policy": guard.get("policy"), + "stats": guard.get("stats") or {}, + "summary": { + "authorization_signing_issuer_closeout_ready_count": 1 if closeout_ready else 0, + "signing_issuer_closeout_check_count": len(checks), + "signing_issuer_closeout_pass_count": passed_count, + "signing_issuer_closeout_waiting_count": len(waiting_checks), + "authorization_signing_issuer_guard_ready_count": summary.get( + "authorization_signing_issuer_guard_ready_count", 0 + ), + "signing_issuer_guard_check_count": summary.get( + "signing_issuer_guard_check_count", 0 + ), + "signing_issuer_guard_pass_count": summary.get( + "signing_issuer_guard_pass_count", 0 + ), + "signing_decision_input_requirement_count": summary.get( + "signing_decision_input_requirement_count", 0 + ), + "signing_decision_rejection_reason_count": summary.get( + "signing_decision_rejection_reason_count", 0 + ), + "required_issuer_evidence_count": summary.get("required_issuer_evidence_count", 0), + "nonsecret_authorization_claim_count": summary.get( + "nonsecret_authorization_claim_count", 0 + ), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "future_authorization_signing_issuer_closeout": ( + future_authorization_signing_issuer_closeout + ), + "final_signable_request_package": final_signable_request_package, + "signing_issuer_closeout_contract": signing_issuer_closeout_contract, + "signing_issuer_closeout_checks": checks, + "source_signing_issuer_guard_summary": summary, + "source_signing_issuer_guard_contract": contract, + "source_signable_request_boundary": boundary, + "safety": { + "read_only_db_apply_authorization_signing_issuer_closeout": True, + "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, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout as the final signable request package for a future explicit authorization signing lane.", + "Keep authorization signing, secret reads, shell execution, SQL, and DB writes out of this closeout.", + "Require fresh production truth, operator-held secret material, rollback boundary, and post-apply verifier inside the future signing run.", + ], + } + + +def build_pchome_auto_policy_db_apply_authorization_signing_execution_preflight( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Preflight a future explicit authorization signing execution without signing.""" + closeout = build_pchome_auto_policy_db_apply_authorization_signing_issuer_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + issuer_closeout = closeout.get("future_authorization_signing_issuer_closeout") or {} + final_package = closeout.get("final_signable_request_package") or {} + closeout_contract = closeout.get("signing_issuer_closeout_contract") or {} + summary = closeout.get("summary") or {} + safety = closeout.get("safety") or {} + preflight_id = _db_apply_authorization_signing_execution_preflight_id(closeout) + + required_nonsecret_inputs = [ + { + "key": "final_signable_request_package_id", + "value": final_package.get("package_id"), + "secret": False, + }, + { + "key": "signing_issuer_closeout_id", + "value": issuer_closeout.get("closeout_id"), + "secret": False, + }, + {"key": "target_file", "value": final_package.get("target_file"), "secret": False}, + { + "key": "migration_expected_sha256", + "value": final_package.get("expected_sha256"), + "secret": False, + }, + { + "key": "migration_actual_sha256", + "value": final_package.get("actual_sha256"), + "secret": False, + }, + { + "key": "same_run_production_truth", + "value": "required_before_future_signing_execution", + "secret": False, + }, + { + "key": "post_apply_verifier_endpoint", + "value": final_package.get("post_apply_verifier_endpoint"), + "secret": False, + }, + { + "key": "rollback_boundary", + "value": "required_before_future_db_apply", + "secret": False, + }, + { + "key": "abort_conditions", + "value": "fail_closed_before_future_signing_execution", + "secret": False, + }, + { + "key": "operator_held_secret_reference", + "value": "external_runtime_reference_only", + "secret": False, + }, + ] + abort_conditions = [ + "abort_if_production_truth_missing_or_stale", + "abort_if_final_signable_request_package_not_ready", + "abort_if_migration_hash_mismatch", + "abort_if_post_apply_verifier_missing", + "abort_if_secret_value_enters_preview_payload", + "abort_if_future_runner_cannot_use_check_mode", + "abort_if_authorization_signing_would_write_database", + "abort_if_manual_review_gate_reappears_in_primary_flow", + ] + rollback_boundary = [ + "capture_prewrite_snapshot_before_future_apply", + "keep_migration_rollback_sql_bound_to_same_hash", + "require_post_apply_readback_before_success_receipt", + "route_failed_verifier_to_exception_only_auto_resolution", + ] + operator_held_secret_boundary_contract = { + "mode": "operator_held_secret_reference_only", + "secret_material_owner": "external_runtime_signing_boundary", + "secret_reference_mode": "external_runtime_reference_only", + "secret_reference_placeholder": "OPERATOR_HELD_SIGNING_SECRET_REF", + "secret_material_included": False, + "secret_material_required_in_preview": False, + "secret_material_required_in_future_signing": True, + "reads_secret_in_preview": False, + "accepts_plaintext_secret": False, + "permits_secret_value_logging": False, + "ai_controlled_preflight_produces_nonsecret_envelope": True, + "manual_review_mode": "exception_only", + } + command_preview = { + "mode": "future_command_shape_only", + "command_shape": ( + "sign-db-apply-authorization --request " + "--secret-ref --check-mode" + ), + "redacts_secret_values": True, + "executes_in_preview": False, + "signs_database_apply_authorization": False, + "writes_database": False, + } + side_effect_free = ( + int(summary.get("writes_script_count") or 0) == 0 + and int(summary.get("writes_artifact_count") or 0) == 0 + and int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_migration_count") or 0) == 0 + and int(summary.get("executes_endpoint_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and int(summary.get("signs_database_apply_authorization_count") or 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_script") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("signs_database_apply_authorization") is False + and final_package.get("secret_material_included") is False + and final_package.get("secret_material_required_in_preview") is False + and final_package.get("reads_secret_in_preview") is False + and final_package.get("executes_shell_in_preview") is False + and final_package.get("executes_sql_in_preview") is False + and final_package.get("writes_database_in_preview") is False + ) + checks = [ + _authorization_signing_execution_preflight_check( + "signing_issuer_closeout_ready", + closeout.get("result") == "DB_APPLY_AUTHORIZATION_SIGNING_ISSUER_CLOSEOUT_READY" + and issuer_closeout.get("ready_for_future_signing_issuer_closeout") is True + and final_package.get("ready_for_future_final_signable_request_package") is True, + { + "result": closeout.get("result"), + "ready_for_future_signing_issuer_closeout": issuer_closeout.get( + "ready_for_future_signing_issuer_closeout" + ), + "ready_for_future_final_signable_request_package": final_package.get( + "ready_for_future_final_signable_request_package" + ), + }, + "wait_for_signing_issuer_closeout", + ), + _authorization_signing_execution_preflight_check( + "final_signable_request_package_complete", + final_package.get("authorization_material_type") == "final_signable_request_package" + and bool(final_package.get("package_id")) + and bool(final_package.get("source_signable_request_boundary_id")) + and final_package.get("ready_for_database_apply_now") is False + and final_package.get("issues_database_apply_authorization") is False + and final_package.get("signs_database_apply_authorization") is False, + { + "package_id": final_package.get("package_id"), + "authorization_material_type": final_package.get("authorization_material_type"), + "source_signable_request_boundary_id": final_package.get( + "source_signable_request_boundary_id" + ), + }, + "wait_for_final_signable_request_package", + ), + _authorization_signing_execution_preflight_check( + "source_chain_ids_present", + bool(issuer_closeout.get("closeout_id")) + and bool(final_package.get("source_signing_issuer_guard_id")) + and bool(final_package.get("source_unsigned_signing_decision_package_id")) + and bool(final_package.get("source_issuer_gate_id")) + and bool(final_package.get("source_nonsecret_envelope_id")) + and bool(final_package.get("source_lane_guard_id")) + and bool(final_package.get("source_intake_id")), + { + "closeout_id": issuer_closeout.get("closeout_id"), + "source_signing_issuer_guard_id": final_package.get( + "source_signing_issuer_guard_id" + ), + "source_unsigned_signing_decision_package_id": final_package.get( + "source_unsigned_signing_decision_package_id" + ), + "source_issuer_gate_id": final_package.get("source_issuer_gate_id"), + }, + "wait_for_source_authorization_chain_ids", + ), + _authorization_signing_execution_preflight_check( + "same_run_production_truth_required", + final_package.get("requires_fresh_production_truth_in_same_run") is True + and int(summary.get("same_run_truth_required_count") or 0) == 1, + { + "requires_fresh_production_truth_in_same_run": final_package.get( + "requires_fresh_production_truth_in_same_run" + ), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + }, + "require_same_run_production_truth", + ), + _authorization_signing_execution_preflight_check( + "post_apply_verifier_required", + final_package.get("requires_post_apply_verifier") is True + and bool(final_package.get("post_apply_verifier_endpoint")) + and int(summary.get("post_apply_verifier_required_count") or 0) == 1, + { + "requires_post_apply_verifier": final_package.get( + "requires_post_apply_verifier" + ), + "post_apply_verifier_endpoint": final_package.get( + "post_apply_verifier_endpoint" + ), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + }, + "require_post_apply_verifier", + ), + _authorization_signing_execution_preflight_check( + "migration_file_hash_locked", + final_package.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and final_package.get("hash_matches") is True + and bool(final_package.get("expected_sha256")) + and final_package.get("expected_sha256") == final_package.get("actual_sha256"), + { + "target_file": final_package.get("target_file"), + "hash_matches": final_package.get("hash_matches"), + "expected_sha256": final_package.get("expected_sha256"), + "actual_sha256": final_package.get("actual_sha256"), + }, + "abort_on_migration_file_hash_mismatch", + ), + _authorization_signing_execution_preflight_check( + "required_nonsecret_signing_inputs_complete", + len(required_nonsecret_inputs) == 10 + and all(item.get("secret") is False for item in required_nonsecret_inputs) + and all(item.get("value") for item in required_nonsecret_inputs), + { + "required_nonsecret_input_count": len(required_nonsecret_inputs), + "secret_input_count": sum(1 for item in required_nonsecret_inputs if item.get("secret")), + }, + "wait_for_nonsecret_signing_inputs", + ), + _authorization_signing_execution_preflight_check( + "operator_held_secret_boundary_is_externalized", + operator_held_secret_boundary_contract.get("secret_reference_mode") + == "external_runtime_reference_only" + and operator_held_secret_boundary_contract.get("secret_material_included") is False + and operator_held_secret_boundary_contract.get("secret_material_required_in_preview") + is False + and operator_held_secret_boundary_contract.get("reads_secret_in_preview") is False + and operator_held_secret_boundary_contract.get("accepts_plaintext_secret") is False + and operator_held_secret_boundary_contract.get("permits_secret_value_logging") is False, + { + "secret_reference_mode": operator_held_secret_boundary_contract.get( + "secret_reference_mode" + ), + "secret_material_included": operator_held_secret_boundary_contract.get( + "secret_material_included" + ), + "reads_secret_in_preview": operator_held_secret_boundary_contract.get( + "reads_secret_in_preview" + ), + }, + "abort_on_secret_boundary_violation", + ), + _authorization_signing_execution_preflight_check( + "future_command_preview_is_non_executing_and_redacted", + command_preview.get("mode") == "future_command_shape_only" + and command_preview.get("redacts_secret_values") is True + and command_preview.get("executes_in_preview") is False + and command_preview.get("signs_database_apply_authorization") is False + and command_preview.get("writes_database") is False, + { + "mode": command_preview.get("mode"), + "redacts_secret_values": command_preview.get("redacts_secret_values"), + "executes_in_preview": command_preview.get("executes_in_preview"), + }, + "block_if_command_preview_executes_or_exposes_secret", + ), + _authorization_signing_execution_preflight_check( + "rollback_and_abort_boundaries_present", + len(rollback_boundary) == 4 and len(abort_conditions) == 8, + { + "rollback_boundary_count": len(rollback_boundary), + "abort_condition_count": len(abort_conditions), + }, + "wait_for_rollback_or_abort_boundary", + ), + _authorization_signing_execution_preflight_check( + "preview_has_no_side_effects_and_no_signing", + side_effect_free, + { + "writes_script_count": summary.get("writes_script_count", 0), + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + }, + "abort_on_preview_side_effect_or_signing", + ), + _authorization_signing_execution_preflight_check( + "manual_review_regression_absent", + int(summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY) or 0) == 0 + and issuer_closeout.get("manual_review_mode") == "exception_only", + { + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + "manual_review_mode": issuer_closeout.get("manual_review_mode"), + }, + "route_failed_verifier_to_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + preflight_ready = not waiting_checks + preflight_status = ( + "DB_APPLY_AUTHORIZATION_SIGNING_EXECUTION_PREFLIGHT_READY" + if preflight_ready + else "WAITING_FOR_DB_APPLY_AUTHORIZATION_SIGNING_ISSUER_CLOSEOUT" + ) + future_authorization_signing_execution_preflight = { + "preflight_id": preflight_id, + "source_signing_issuer_closeout_id": issuer_closeout.get("closeout_id"), + "source_final_signable_request_package_id": final_package.get("package_id"), + "status": preflight_status, + "ready_for_future_signing_execution_preflight": preflight_ready, + "can_enter_future_authorization_signing_execution_lane": preflight_ready, + "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, + "reads_secret_in_preview": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + signing_execution_preflight_package = { + "package_id": preflight_id, + "authorization_material_type": "signing_execution_preflight_package", + "source_final_signable_request_package_id": final_package.get("package_id"), + "source_signing_issuer_closeout_id": issuer_closeout.get("closeout_id"), + "status": preflight_status, + "ready_for_future_signing_execution_preflight": preflight_ready, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "required_nonsecret_inputs": required_nonsecret_inputs, + "required_nonsecret_input_count": len(required_nonsecret_inputs), + "operator_held_secret_boundary_contract": operator_held_secret_boundary_contract, + "command_preview": command_preview, + "abort_conditions": abort_conditions, + "rollback_boundary": rollback_boundary, + "target_file": final_package.get("target_file"), + "expected_sha256": final_package.get("expected_sha256"), + "actual_sha256": final_package.get("actual_sha256"), + "hash_matches": final_package.get("hash_matches"), + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "post_apply_verifier_endpoint": final_package.get("post_apply_verifier_endpoint"), + "secret_material_included": False, + "secret_material_required_in_preview": False, + "reads_secret_in_preview": False, + "executes_shell_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + } + signing_execution_preflight_contract = { + "mode": "explicit_authorization_signing_execution_preflight_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-execution-preflight" + ), + "source_signing_issuer_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-issuer-closeout" + ), + "machine_verifiable": True, + "permits_future_explicit_authorization_signing_execution_lane": preflight_ready, + "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", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_AUTHORIZATION_SIGNING_EXECUTION_PREFLIGHT_POLICY, + "result": preflight_status, + "success": bool(closeout.get("success")), + "generated_at": closeout.get("generated_at"), + "source_policy": closeout.get("policy"), + "stats": closeout.get("stats") or {}, + "summary": { + "authorization_signing_execution_preflight_ready_count": ( + 1 if preflight_ready else 0 + ), + "signing_execution_preflight_check_count": len(checks), + "signing_execution_preflight_pass_count": passed_count, + "signing_execution_preflight_waiting_count": len(waiting_checks), + "authorization_signing_issuer_closeout_ready_count": summary.get( + "authorization_signing_issuer_closeout_ready_count", 0 + ), + "signing_issuer_closeout_check_count": summary.get( + "signing_issuer_closeout_check_count", 0 + ), + "signing_issuer_closeout_pass_count": summary.get( + "signing_issuer_closeout_pass_count", 0 + ), + "final_signable_request_package_ready_count": ( + 1 + if final_package.get("ready_for_future_final_signable_request_package") is True + else 0 + ), + "operator_held_secret_boundary_count": 1, + "signing_execution_input_requirement_count": len(required_nonsecret_inputs), + "signing_execution_abort_condition_count": len(abort_conditions), + "rollback_boundary_count": len(rollback_boundary), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "future_authorization_signing_execution_preflight": ( + future_authorization_signing_execution_preflight + ), + "signing_execution_preflight_package": signing_execution_preflight_package, + "operator_held_secret_boundary_contract": operator_held_secret_boundary_contract, + "signing_execution_preflight_contract": signing_execution_preflight_contract, + "signing_execution_preflight_checks": checks, + "source_signing_issuer_closeout_summary": summary, + "source_signing_issuer_closeout_contract": closeout_contract, + "source_final_signable_request_package": final_package, + "safety": { + "read_only_db_apply_authorization_signing_execution_preflight": True, + "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, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this preflight to validate a future explicit authorization signing execution request.", + "Keep secret values outside AI payloads; the preview only carries an external runtime reference placeholder.", + "Require same-run production truth, hash lock, rollback boundary, and post-apply verifier before any future signing run.", + ], + } + + +def build_pchome_auto_policy_db_apply_authorization_signing_execution_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out the future signing execution preflight without signing authorization.""" + preflight = build_pchome_auto_policy_db_apply_authorization_signing_execution_preflight( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + future_preflight = preflight.get("future_authorization_signing_execution_preflight") or {} + package = preflight.get("signing_execution_preflight_package") or {} + boundary = preflight.get("operator_held_secret_boundary_contract") or {} + preflight_contract = preflight.get("signing_execution_preflight_contract") or {} + summary = preflight.get("summary") or {} + safety = preflight.get("safety") or {} + required_inputs = list(package.get("required_nonsecret_inputs") or []) + abort_conditions = list(package.get("abort_conditions") or []) + rollback_boundary = list(package.get("rollback_boundary") or []) + command_preview = package.get("command_preview") or {} + side_effect_free = ( + int(summary.get("writes_script_count") or 0) == 0 + and int(summary.get("writes_artifact_count") or 0) == 0 + and int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_migration_count") or 0) == 0 + and int(summary.get("executes_endpoint_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and int(summary.get("signs_database_apply_authorization_count") or 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_script") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("signs_database_apply_authorization") is False + and package.get("secret_material_included") is False + and package.get("secret_material_required_in_preview") is False + and package.get("reads_secret_in_preview") is False + and package.get("executes_shell_in_preview") is False + and package.get("executes_sql_in_preview") is False + and package.get("writes_database_in_preview") is False + and boundary.get("secret_material_included") is False + and boundary.get("reads_secret_in_preview") is False + ) + checks = [ + _authorization_signing_execution_closeout_check( + "signing_execution_preflight_ready", + preflight.get("result") == "DB_APPLY_AUTHORIZATION_SIGNING_EXECUTION_PREFLIGHT_READY" + and future_preflight.get("ready_for_future_signing_execution_preflight") is True + and package.get("ready_for_future_signing_execution_preflight") is True, + { + "result": preflight.get("result"), + "ready_for_future_signing_execution_preflight": future_preflight.get( + "ready_for_future_signing_execution_preflight" + ), + "package_ready_for_future_signing_execution_preflight": package.get( + "ready_for_future_signing_execution_preflight" + ), + }, + "wait_for_signing_execution_preflight", + ), + _authorization_signing_execution_closeout_check( + "unsigned_receipt_boundary_source_package_complete", + package.get("authorization_material_type") == "signing_execution_preflight_package" + and bool(package.get("package_id")) + and bool(package.get("source_final_signable_request_package_id")) + and bool(package.get("source_signing_issuer_closeout_id")) + and package.get("ready_for_database_apply_now") is False + and package.get("issues_database_apply_authorization") is False + and package.get("signs_database_apply_authorization") is False, + { + "package_id": package.get("package_id"), + "source_final_signable_request_package_id": package.get( + "source_final_signable_request_package_id" + ), + "source_signing_issuer_closeout_id": package.get( + "source_signing_issuer_closeout_id" + ), + }, + "wait_for_signing_execution_preflight_package", + ), + _authorization_signing_execution_closeout_check( + "source_chain_ids_present", + bool(future_preflight.get("preflight_id")) + and bool(future_preflight.get("source_signing_issuer_closeout_id")) + and bool(future_preflight.get("source_final_signable_request_package_id")) + and bool(package.get("source_final_signable_request_package_id")) + and bool(package.get("source_signing_issuer_closeout_id")), + { + "preflight_id": future_preflight.get("preflight_id"), + "source_signing_issuer_closeout_id": future_preflight.get( + "source_signing_issuer_closeout_id" + ), + "source_final_signable_request_package_id": future_preflight.get( + "source_final_signable_request_package_id" + ), + }, + "wait_for_source_authorization_chain_ids", + ), + _authorization_signing_execution_closeout_check( + "operator_held_secret_boundary_carried_forward", + boundary.get("secret_reference_mode") == "external_runtime_reference_only" + and boundary.get("secret_material_included") is False + and boundary.get("secret_material_required_in_preview") is False + and boundary.get("reads_secret_in_preview") is False + and boundary.get("accepts_plaintext_secret") is False + and boundary.get("permits_secret_value_logging") is False, + { + "secret_reference_mode": boundary.get("secret_reference_mode"), + "secret_material_included": boundary.get("secret_material_included"), + "reads_secret_in_preview": boundary.get("reads_secret_in_preview"), + "accepts_plaintext_secret": boundary.get("accepts_plaintext_secret"), + }, + "abort_on_secret_boundary_violation", + ), + _authorization_signing_execution_closeout_check( + "required_nonsecret_inputs_carried_forward", + int(summary.get("signing_execution_input_requirement_count") or 0) == 10 + and len(required_inputs) == 10 + and all(item.get("secret") is False for item in required_inputs) + and all(item.get("value") for item in required_inputs), + { + "summary_input_count": summary.get("signing_execution_input_requirement_count", 0), + "required_input_count": len(required_inputs), + "secret_input_count": sum(1 for item in required_inputs if item.get("secret")), + }, + "wait_for_nonsecret_signing_inputs", + ), + _authorization_signing_execution_closeout_check( + "same_run_production_truth_required", + package.get("requires_fresh_production_truth_in_same_run") is True + and int(summary.get("same_run_truth_required_count") or 0) == 1, + { + "requires_fresh_production_truth_in_same_run": package.get( + "requires_fresh_production_truth_in_same_run" + ), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + }, + "require_same_run_production_truth", + ), + _authorization_signing_execution_closeout_check( + "post_apply_verifier_required", + package.get("requires_post_apply_verifier") is True + and bool(package.get("post_apply_verifier_endpoint")) + and int(summary.get("post_apply_verifier_required_count") or 0) == 1, + { + "requires_post_apply_verifier": package.get("requires_post_apply_verifier"), + "post_apply_verifier_endpoint": package.get("post_apply_verifier_endpoint"), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + }, + "require_post_apply_verifier", + ), + _authorization_signing_execution_closeout_check( + "migration_file_hash_locked", + package.get("target_file") == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and package.get("hash_matches") is True + and bool(package.get("expected_sha256")) + and package.get("expected_sha256") == package.get("actual_sha256"), + { + "target_file": package.get("target_file"), + "hash_matches": package.get("hash_matches"), + "expected_sha256": package.get("expected_sha256"), + "actual_sha256": package.get("actual_sha256"), + }, + "abort_on_migration_file_hash_mismatch", + ), + _authorization_signing_execution_closeout_check( + "future_command_preview_is_non_executing_and_redacted", + command_preview.get("mode") == "future_command_shape_only" + and command_preview.get("redacts_secret_values") is True + and command_preview.get("executes_in_preview") is False + and command_preview.get("signs_database_apply_authorization") is False + and command_preview.get("writes_database") is False, + { + "mode": command_preview.get("mode"), + "redacts_secret_values": command_preview.get("redacts_secret_values"), + "executes_in_preview": command_preview.get("executes_in_preview"), + }, + "block_if_command_preview_executes_or_exposes_secret", + ), + _authorization_signing_execution_closeout_check( + "rollback_and_abort_boundaries_carried_forward", + len(rollback_boundary) == 4 + and len(abort_conditions) == 8 + and int(summary.get("rollback_boundary_count") or 0) == 4 + and int(summary.get("signing_execution_abort_condition_count") or 0) == 8, + { + "rollback_boundary_count": len(rollback_boundary), + "abort_condition_count": len(abort_conditions), + "summary_rollback_boundary_count": summary.get("rollback_boundary_count", 0), + "summary_abort_condition_count": summary.get( + "signing_execution_abort_condition_count", 0 + ), + }, + "wait_for_rollback_or_abort_boundary", + ), + _authorization_signing_execution_closeout_check( + "closeout_does_not_authorize_sign_or_apply", + preflight_contract.get("permits_future_explicit_authorization_signing_execution_lane") + is True + and preflight_contract.get("issues_database_apply_authorization") is False + and preflight_contract.get("ready_for_database_apply_now") is False + and preflight_contract.get("signs_database_apply_authorization") is False + and preflight_contract.get("writes_database") is False + and preflight_contract.get("executes_in_preview") is False + and future_preflight.get("issues_database_apply_authorization") is False + and future_preflight.get("ready_for_database_apply_now") is False + and future_preflight.get("signs_database_apply_authorization") is False, + { + "permits_future_explicit_authorization_signing_execution_lane": ( + preflight_contract.get( + "permits_future_explicit_authorization_signing_execution_lane" + ) + ), + "issues_database_apply_authorization": preflight_contract.get( + "issues_database_apply_authorization" + ), + "signs_database_apply_authorization": preflight_contract.get( + "signs_database_apply_authorization" + ), + }, + "block_if_closeout_authorizes_signs_or_applies", + ), + _authorization_signing_execution_closeout_check( + "preview_has_no_side_effects_and_no_signing", + side_effect_free, + { + "writes_script_count": summary.get("writes_script_count", 0), + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + }, + "abort_on_preview_side_effect_or_signing", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_AUTHORIZATION_SIGNING_EXECUTION_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_AUTHORIZATION_SIGNING_EXECUTION_PREFLIGHT" + ) + closeout_id = _db_apply_authorization_signing_execution_closeout_id(preflight) + future_authorization_signing_execution_closeout = { + "closeout_id": closeout_id, + "source_signing_execution_preflight_id": future_preflight.get("preflight_id"), + "source_final_signable_request_package_id": future_preflight.get( + "source_final_signable_request_package_id" + ), + "source_signing_issuer_closeout_id": future_preflight.get( + "source_signing_issuer_closeout_id" + ), + "status": closeout_status, + "ready_for_future_signing_execution_closeout": closeout_ready, + "can_enter_future_unsigned_signed_authorization_receipt_boundary": closeout_ready, + "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, + "reads_secret_in_preview": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + unsigned_signed_authorization_receipt_boundary = { + "boundary_id": closeout_id, + "authorization_material_type": "unsigned_signed_authorization_receipt_boundary", + "source_signing_execution_preflight_id": future_preflight.get("preflight_id"), + "source_signing_execution_preflight_package_id": package.get("package_id"), + "source_final_signable_request_package_id": package.get( + "source_final_signable_request_package_id" + ), + "source_signing_issuer_closeout_id": package.get("source_signing_issuer_closeout_id"), + "status": closeout_status, + "ready_for_future_unsigned_signed_authorization_receipt_boundary": closeout_ready, + "ready_for_future_signed_authorization_receipt_lane": closeout_ready, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": 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, + "executes_shell_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "required_nonsecret_inputs": required_inputs, + "operator_held_secret_boundary_contract": boundary, + "command_preview": command_preview, + "abort_conditions": abort_conditions, + "rollback_boundary": rollback_boundary, + "target_file": package.get("target_file"), + "expected_sha256": package.get("expected_sha256"), + "actual_sha256": package.get("actual_sha256"), + "hash_matches": package.get("hash_matches"), + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "post_apply_verifier_endpoint": package.get("post_apply_verifier_endpoint"), + } + signing_execution_closeout_contract = { + "mode": "explicit_authorization_signing_execution_closeout_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-execution-closeout" + ), + "source_signing_execution_preflight_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-execution-preflight" + ), + "machine_verifiable": True, + "permits_future_unsigned_signed_authorization_receipt_boundary": closeout_ready, + "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", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_AUTHORIZATION_SIGNING_EXECUTION_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(preflight.get("success")), + "generated_at": preflight.get("generated_at"), + "source_policy": preflight.get("policy"), + "stats": preflight.get("stats") or {}, + "summary": { + "authorization_signing_execution_closeout_ready_count": 1 if closeout_ready else 0, + "signing_execution_closeout_check_count": len(checks), + "signing_execution_closeout_pass_count": passed_count, + "signing_execution_closeout_waiting_count": len(waiting_checks), + "authorization_signing_execution_preflight_ready_count": summary.get( + "authorization_signing_execution_preflight_ready_count", 0 + ), + "signing_execution_preflight_check_count": summary.get( + "signing_execution_preflight_check_count", 0 + ), + "signing_execution_preflight_pass_count": summary.get( + "signing_execution_preflight_pass_count", 0 + ), + "unsigned_signed_authorization_receipt_boundary_count": 1, + "operator_held_secret_boundary_count": summary.get( + "operator_held_secret_boundary_count", 0 + ), + "signing_execution_input_requirement_count": summary.get( + "signing_execution_input_requirement_count", 0 + ), + "signing_execution_abort_condition_count": summary.get( + "signing_execution_abort_condition_count", 0 + ), + "rollback_boundary_count": summary.get("rollback_boundary_count", 0), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "future_authorization_signing_execution_closeout": ( + future_authorization_signing_execution_closeout + ), + "unsigned_signed_authorization_receipt_boundary": ( + unsigned_signed_authorization_receipt_boundary + ), + "signing_execution_closeout_contract": signing_execution_closeout_contract, + "signing_execution_closeout_checks": checks, + "source_signing_execution_preflight_summary": summary, + "source_signing_execution_preflight_contract": preflight_contract, + "source_signing_execution_preflight_package": package, + "safety": { + "read_only_db_apply_authorization_signing_execution_closeout": True, + "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, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout as the unsigned receipt boundary for a future signed authorization receipt lane.", + "Keep signing, secret reads, shell execution, SQL, and DB writes out of this closeout.", + "Require same-run production truth, hash lock, rollback boundary, and post-apply verifier before any future signing receipt run.", + ], + } + + +def build_pchome_auto_policy_db_apply_authorization_signed_receipt_preflight( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Preflight a future externally signed authorization receipt without signing.""" + closeout = build_pchome_auto_policy_db_apply_authorization_signing_execution_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + future_closeout = closeout.get("future_authorization_signing_execution_closeout") or {} + unsigned_boundary = closeout.get("unsigned_signed_authorization_receipt_boundary") or {} + closeout_contract = closeout.get("signing_execution_closeout_contract") or {} + summary = closeout.get("summary") or {} + safety = closeout.get("safety") or {} + preflight_id = _db_apply_authorization_signed_receipt_preflight_id(closeout) + required_inputs = list(unsigned_boundary.get("required_nonsecret_inputs") or []) + abort_conditions = list(unsigned_boundary.get("abort_conditions") or []) + rollback_boundary = list(unsigned_boundary.get("rollback_boundary") or []) + operator_secret_boundary = ( + unsigned_boundary.get("operator_held_secret_boundary_contract") or {} + ) + command_preview = unsigned_boundary.get("command_preview") or {} + required_external_receipt_evidence = [ + "external_receipt_id", + "source_unsigned_receipt_boundary_id", + "source_signing_execution_closeout_id", + "source_final_signable_request_package_id", + "signer_key_id_reference", + "signature_algorithm_reference", + "signed_at_utc", + "payload_sha256", + "receipt_sha256", + "detached_signature_verification_status", + ] + external_receipt_acceptance_gates = [ + "production_truth_matches_receipt_generation_run", + "source_unsigned_boundary_id_matches", + "payload_hash_matches_final_signable_request_package", + "receipt_hash_is_present_and_nonempty", + "detached_signature_verification_status_is_passed", + "signer_key_id_is_reference_only", + "no_secret_or_signature_material_in_ai_payload", + "post_apply_verifier_still_required", + ] + side_effect_free = ( + int(summary.get("writes_script_count") or 0) == 0 + and int(summary.get("writes_artifact_count") or 0) == 0 + and int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_migration_count") or 0) == 0 + and int(summary.get("executes_endpoint_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and int(summary.get("signs_database_apply_authorization_count") or 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_script") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("signs_database_apply_authorization") is False + and unsigned_boundary.get("signed_authorization_receipt_included") is False + and unsigned_boundary.get("signature_material_included") is False + and unsigned_boundary.get("secret_material_included") is False + and unsigned_boundary.get("secret_material_required_in_preview") is False + and unsigned_boundary.get("reads_secret_in_preview") is False + and unsigned_boundary.get("executes_shell_in_preview") is False + and unsigned_boundary.get("executes_sql_in_preview") is False + and unsigned_boundary.get("writes_database_in_preview") is False + ) + checks = [ + _authorization_signed_receipt_preflight_check( + "signing_execution_closeout_ready", + closeout.get("result") == "DB_APPLY_AUTHORIZATION_SIGNING_EXECUTION_CLOSEOUT_READY" + and future_closeout.get("ready_for_future_signing_execution_closeout") is True + and unsigned_boundary.get( + "ready_for_future_unsigned_signed_authorization_receipt_boundary" + ) + is True, + { + "result": closeout.get("result"), + "ready_for_future_signing_execution_closeout": future_closeout.get( + "ready_for_future_signing_execution_closeout" + ), + "ready_for_future_unsigned_signed_authorization_receipt_boundary": ( + unsigned_boundary.get( + "ready_for_future_unsigned_signed_authorization_receipt_boundary" + ) + ), + }, + "wait_for_signing_execution_closeout", + ), + _authorization_signed_receipt_preflight_check( + "unsigned_receipt_boundary_complete", + unsigned_boundary.get("authorization_material_type") + == "unsigned_signed_authorization_receipt_boundary" + and bool(unsigned_boundary.get("boundary_id")) + and bool(unsigned_boundary.get("source_signing_execution_preflight_id")) + and bool(unsigned_boundary.get("source_final_signable_request_package_id")) + and unsigned_boundary.get("ready_for_database_apply_now") is False + and unsigned_boundary.get("issues_database_apply_authorization") is False + and unsigned_boundary.get("signs_database_apply_authorization") is False, + { + "boundary_id": unsigned_boundary.get("boundary_id"), + "authorization_material_type": unsigned_boundary.get( + "authorization_material_type" + ), + "source_final_signable_request_package_id": unsigned_boundary.get( + "source_final_signable_request_package_id" + ), + }, + "wait_for_unsigned_receipt_boundary", + ), + _authorization_signed_receipt_preflight_check( + "source_chain_ids_present", + bool(future_closeout.get("closeout_id")) + and bool(future_closeout.get("source_signing_execution_preflight_id")) + and bool(future_closeout.get("source_final_signable_request_package_id")) + and bool(future_closeout.get("source_signing_issuer_closeout_id")) + and bool(unsigned_boundary.get("source_signing_execution_preflight_package_id")) + and bool(unsigned_boundary.get("source_signing_issuer_closeout_id")), + { + "closeout_id": future_closeout.get("closeout_id"), + "source_signing_execution_preflight_id": future_closeout.get( + "source_signing_execution_preflight_id" + ), + "source_final_signable_request_package_id": future_closeout.get( + "source_final_signable_request_package_id" + ), + }, + "wait_for_source_authorization_chain_ids", + ), + _authorization_signed_receipt_preflight_check( + "external_receipt_evidence_contract_complete", + len(required_external_receipt_evidence) == 10 + and len(external_receipt_acceptance_gates) == 8, + { + "required_external_receipt_evidence_count": len( + required_external_receipt_evidence + ), + "external_receipt_acceptance_gate_count": len( + external_receipt_acceptance_gates + ), + }, + "wait_for_external_receipt_evidence_contract", + ), + _authorization_signed_receipt_preflight_check( + "operator_held_secret_boundary_carried_forward", + operator_secret_boundary.get("secret_reference_mode") + == "external_runtime_reference_only" + and operator_secret_boundary.get("secret_material_included") is False + and operator_secret_boundary.get("secret_material_required_in_preview") is False + and operator_secret_boundary.get("reads_secret_in_preview") is False + and operator_secret_boundary.get("accepts_plaintext_secret") is False + and operator_secret_boundary.get("permits_secret_value_logging") is False, + { + "secret_reference_mode": operator_secret_boundary.get("secret_reference_mode"), + "secret_material_included": operator_secret_boundary.get( + "secret_material_included" + ), + "reads_secret_in_preview": operator_secret_boundary.get( + "reads_secret_in_preview" + ), + }, + "abort_on_secret_boundary_violation", + ), + _authorization_signed_receipt_preflight_check( + "same_run_production_truth_required", + unsigned_boundary.get("requires_fresh_production_truth_in_same_run") is True + and int(summary.get("same_run_truth_required_count") or 0) == 1, + { + "requires_fresh_production_truth_in_same_run": unsigned_boundary.get( + "requires_fresh_production_truth_in_same_run" + ), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + }, + "require_same_run_production_truth", + ), + _authorization_signed_receipt_preflight_check( + "post_apply_verifier_required", + unsigned_boundary.get("requires_post_apply_verifier") is True + and bool(unsigned_boundary.get("post_apply_verifier_endpoint")) + and int(summary.get("post_apply_verifier_required_count") or 0) == 1, + { + "requires_post_apply_verifier": unsigned_boundary.get( + "requires_post_apply_verifier" + ), + "post_apply_verifier_endpoint": unsigned_boundary.get( + "post_apply_verifier_endpoint" + ), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + }, + "require_post_apply_verifier", + ), + _authorization_signed_receipt_preflight_check( + "migration_file_hash_locked", + unsigned_boundary.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and unsigned_boundary.get("hash_matches") is True + and bool(unsigned_boundary.get("expected_sha256")) + and unsigned_boundary.get("expected_sha256") == unsigned_boundary.get("actual_sha256"), + { + "target_file": unsigned_boundary.get("target_file"), + "hash_matches": unsigned_boundary.get("hash_matches"), + "expected_sha256": unsigned_boundary.get("expected_sha256"), + "actual_sha256": unsigned_boundary.get("actual_sha256"), + }, + "abort_on_migration_file_hash_mismatch", + ), + _authorization_signed_receipt_preflight_check( + "nonsecret_inputs_and_command_preview_carried_forward", + int(summary.get("signing_execution_input_requirement_count") or 0) == 10 + and len(required_inputs) == 10 + and all(item.get("secret") is False for item in required_inputs) + and command_preview.get("mode") == "future_command_shape_only" + and command_preview.get("redacts_secret_values") is True + and command_preview.get("executes_in_preview") is False + and command_preview.get("signs_database_apply_authorization") is False, + { + "required_input_count": len(required_inputs), + "command_preview_mode": command_preview.get("mode"), + "redacts_secret_values": command_preview.get("redacts_secret_values"), + }, + "wait_for_nonsecret_inputs_or_command_preview", + ), + _authorization_signed_receipt_preflight_check( + "rollback_and_abort_boundaries_carried_forward", + len(rollback_boundary) == 4 + and len(abort_conditions) == 8 + and int(summary.get("rollback_boundary_count") or 0) == 4 + and int(summary.get("signing_execution_abort_condition_count") or 0) == 8, + { + "rollback_boundary_count": len(rollback_boundary), + "abort_condition_count": len(abort_conditions), + "summary_rollback_boundary_count": summary.get("rollback_boundary_count", 0), + "summary_abort_condition_count": summary.get( + "signing_execution_abort_condition_count", 0 + ), + }, + "wait_for_rollback_or_abort_boundary", + ), + _authorization_signed_receipt_preflight_check( + "preflight_has_no_signed_receipt_signature_or_authorization", + unsigned_boundary.get("signed_authorization_receipt_included") is False + and unsigned_boundary.get("signature_material_included") is False + and unsigned_boundary.get("secret_material_included") is False + and closeout_contract.get( + "permits_future_unsigned_signed_authorization_receipt_boundary" + ) + is True + and closeout_contract.get("issues_database_apply_authorization") is False + and closeout_contract.get("ready_for_database_apply_now") is False + and closeout_contract.get("signs_database_apply_authorization") is False, + { + "signed_authorization_receipt_included": unsigned_boundary.get( + "signed_authorization_receipt_included" + ), + "signature_material_included": unsigned_boundary.get( + "signature_material_included" + ), + "signs_database_apply_authorization": closeout_contract.get( + "signs_database_apply_authorization" + ), + }, + "block_if_signed_receipt_or_authorization_is_present", + ), + _authorization_signed_receipt_preflight_check( + "preview_has_no_side_effects_and_no_signing", + side_effect_free, + { + "writes_script_count": summary.get("writes_script_count", 0), + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + }, + "abort_on_preview_side_effect_or_signing", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + preflight_ready = not waiting_checks + preflight_status = ( + "DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_PREFLIGHT_READY" + if preflight_ready + else "WAITING_FOR_DB_APPLY_AUTHORIZATION_SIGNING_EXECUTION_CLOSEOUT" + ) + future_authorization_signed_receipt_preflight = { + "preflight_id": preflight_id, + "source_signing_execution_closeout_id": future_closeout.get("closeout_id"), + "source_unsigned_receipt_boundary_id": unsigned_boundary.get("boundary_id"), + "source_final_signable_request_package_id": unsigned_boundary.get( + "source_final_signable_request_package_id" + ), + "status": preflight_status, + "ready_for_future_signed_authorization_receipt_preflight": preflight_ready, + "can_enter_future_external_signing_receipt_evidence_boundary": preflight_ready, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": 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, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + external_signing_receipt_evidence_boundary = { + "boundary_id": preflight_id, + "authorization_material_type": "external_signing_receipt_evidence_boundary", + "source_signing_execution_closeout_id": future_closeout.get("closeout_id"), + "source_unsigned_receipt_boundary_id": unsigned_boundary.get("boundary_id"), + "source_signing_execution_preflight_id": unsigned_boundary.get( + "source_signing_execution_preflight_id" + ), + "source_final_signable_request_package_id": unsigned_boundary.get( + "source_final_signable_request_package_id" + ), + "status": preflight_status, + "ready_for_future_external_signing_receipt_evidence_boundary": preflight_ready, + "ready_for_future_signed_authorization_receipt_lane": preflight_ready, + "required_external_receipt_evidence": required_external_receipt_evidence, + "required_external_receipt_evidence_count": len(required_external_receipt_evidence), + "external_receipt_acceptance_gates": external_receipt_acceptance_gates, + "external_receipt_acceptance_gate_count": len(external_receipt_acceptance_gates), + "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, + "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, + "operator_held_secret_boundary_contract": operator_secret_boundary, + "target_file": unsigned_boundary.get("target_file"), + "expected_sha256": unsigned_boundary.get("expected_sha256"), + "actual_sha256": unsigned_boundary.get("actual_sha256"), + "hash_matches": unsigned_boundary.get("hash_matches"), + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "post_apply_verifier_endpoint": unsigned_boundary.get("post_apply_verifier_endpoint"), + } + signed_receipt_preflight_contract = { + "mode": "signed_authorization_receipt_preflight_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-preflight" + ), + "source_signing_execution_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-execution-closeout" + ), + "machine_verifiable": True, + "permits_future_external_signing_receipt_evidence_boundary": preflight_ready, + "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", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_PREFLIGHT_POLICY, + "result": preflight_status, + "success": bool(closeout.get("success")), + "generated_at": closeout.get("generated_at"), + "source_policy": closeout.get("policy"), + "stats": closeout.get("stats") or {}, + "summary": { + "authorization_signed_receipt_preflight_ready_count": 1 if preflight_ready else 0, + "signed_receipt_preflight_check_count": len(checks), + "signed_receipt_preflight_pass_count": passed_count, + "signed_receipt_preflight_waiting_count": len(waiting_checks), + "authorization_signing_execution_closeout_ready_count": summary.get( + "authorization_signing_execution_closeout_ready_count", 0 + ), + "signing_execution_closeout_check_count": summary.get( + "signing_execution_closeout_check_count", 0 + ), + "unsigned_signed_authorization_receipt_boundary_count": summary.get( + "unsigned_signed_authorization_receipt_boundary_count", 0 + ), + "external_signing_receipt_evidence_boundary_count": 1, + "required_external_receipt_evidence_count": len(required_external_receipt_evidence), + "external_receipt_acceptance_gate_count": len(external_receipt_acceptance_gates), + "operator_held_secret_boundary_count": summary.get( + "operator_held_secret_boundary_count", 0 + ), + "signing_execution_input_requirement_count": summary.get( + "signing_execution_input_requirement_count", 0 + ), + "signing_execution_abort_condition_count": summary.get( + "signing_execution_abort_condition_count", 0 + ), + "rollback_boundary_count": summary.get("rollback_boundary_count", 0), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "future_authorization_signed_receipt_preflight": ( + future_authorization_signed_receipt_preflight + ), + "external_signing_receipt_evidence_boundary": ( + external_signing_receipt_evidence_boundary + ), + "signed_receipt_preflight_contract": signed_receipt_preflight_contract, + "signed_receipt_preflight_checks": checks, + "source_signing_execution_closeout_summary": summary, + "source_signing_execution_closeout_contract": closeout_contract, + "source_unsigned_signed_authorization_receipt_boundary": unsigned_boundary, + "safety": { + "read_only_db_apply_authorization_signed_receipt_preflight": True, + "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, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this preflight to validate future external signed authorization receipt evidence.", + "Keep signed receipt content, signature material, secret values, shell execution, SQL, and DB writes out of this preflight.", + "Require detached receipt verification, same-run production truth, hash lock, rollback boundary, and post-apply verifier before any future receipt closeout.", + ], + } + + +def build_pchome_auto_policy_db_apply_authorization_signed_receipt_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out future signed receipt preflight without carrying signed material.""" + preflight = build_pchome_auto_policy_db_apply_authorization_signed_receipt_preflight( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + future_preflight = preflight.get("future_authorization_signed_receipt_preflight") or {} + evidence_boundary = preflight.get("external_signing_receipt_evidence_boundary") or {} + preflight_contract = preflight.get("signed_receipt_preflight_contract") or {} + summary = preflight.get("summary") or {} + safety = preflight.get("safety") or {} + closeout_id = _db_apply_authorization_signed_receipt_closeout_id(preflight) + required_evidence = list(evidence_boundary.get("required_external_receipt_evidence") or []) + acceptance_gates = list(evidence_boundary.get("external_receipt_acceptance_gates") or []) + operator_secret_boundary = ( + evidence_boundary.get("operator_held_secret_boundary_contract") or {} + ) + detached_verification_checks = [ + "receipt_id_present", + "source_unsigned_receipt_boundary_id_matches", + "source_signing_execution_closeout_id_matches", + "payload_sha256_matches_final_signable_request_package", + "receipt_sha256_present", + "signer_key_id_is_reference_only", + "signature_algorithm_is_reference_only", + "detached_signature_verification_status_passed", + "same_run_production_truth_verified", + "no_secret_or_signature_material_in_ai_payload", + ] + side_effect_free = ( + int(summary.get("writes_script_count") or 0) == 0 + and int(summary.get("writes_artifact_count") or 0) == 0 + and int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_migration_count") or 0) == 0 + and int(summary.get("executes_endpoint_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and int(summary.get("signs_database_apply_authorization_count") or 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_script") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("signs_database_apply_authorization") is False + and evidence_boundary.get("external_signed_authorization_receipt_included") is False + and evidence_boundary.get("signed_authorization_receipt_included") is False + and evidence_boundary.get("signature_material_included") is False + and evidence_boundary.get("secret_material_included") is False + and evidence_boundary.get("secret_material_required_in_preview") is False + and evidence_boundary.get("reads_secret_in_preview") is False + and evidence_boundary.get("executes_shell_in_preview") is False + and evidence_boundary.get("executes_sql_in_preview") is False + and evidence_boundary.get("writes_database_in_preview") is False + ) + checks = [ + _authorization_signed_receipt_closeout_check( + "signed_receipt_preflight_ready", + preflight.get("result") == "DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_PREFLIGHT_READY" + and future_preflight.get("ready_for_future_signed_authorization_receipt_preflight") + is True + and evidence_boundary.get( + "ready_for_future_external_signing_receipt_evidence_boundary" + ) + is True, + { + "result": preflight.get("result"), + "ready_for_future_signed_authorization_receipt_preflight": ( + future_preflight.get( + "ready_for_future_signed_authorization_receipt_preflight" + ) + ), + "ready_for_future_external_signing_receipt_evidence_boundary": ( + evidence_boundary.get( + "ready_for_future_external_signing_receipt_evidence_boundary" + ) + ), + }, + "wait_for_signed_receipt_preflight", + ), + _authorization_signed_receipt_closeout_check( + "external_receipt_evidence_boundary_complete", + evidence_boundary.get("authorization_material_type") + == "external_signing_receipt_evidence_boundary" + and bool(evidence_boundary.get("boundary_id")) + and bool(evidence_boundary.get("source_signing_execution_closeout_id")) + and bool(evidence_boundary.get("source_unsigned_receipt_boundary_id")) + and bool(evidence_boundary.get("source_final_signable_request_package_id")) + and evidence_boundary.get("ready_for_database_apply_now") is False + and evidence_boundary.get("issues_database_apply_authorization") is False + and evidence_boundary.get("signs_database_apply_authorization") is False, + { + "boundary_id": evidence_boundary.get("boundary_id"), + "authorization_material_type": evidence_boundary.get("authorization_material_type"), + "source_unsigned_receipt_boundary_id": evidence_boundary.get( + "source_unsigned_receipt_boundary_id" + ), + }, + "wait_for_external_receipt_evidence_boundary", + ), + _authorization_signed_receipt_closeout_check( + "source_chain_ids_present", + bool(future_preflight.get("preflight_id")) + and bool(future_preflight.get("source_signing_execution_closeout_id")) + and bool(future_preflight.get("source_unsigned_receipt_boundary_id")) + and bool(future_preflight.get("source_final_signable_request_package_id")) + and bool(evidence_boundary.get("source_signing_execution_preflight_id")), + { + "preflight_id": future_preflight.get("preflight_id"), + "source_signing_execution_closeout_id": future_preflight.get( + "source_signing_execution_closeout_id" + ), + "source_unsigned_receipt_boundary_id": future_preflight.get( + "source_unsigned_receipt_boundary_id" + ), + }, + "wait_for_source_authorization_chain_ids", + ), + _authorization_signed_receipt_closeout_check( + "external_receipt_evidence_contract_carried_forward", + len(required_evidence) == 10 + and len(acceptance_gates) == 8 + and int(summary.get("required_external_receipt_evidence_count") or 0) == 10 + and int(summary.get("external_receipt_acceptance_gate_count") or 0) == 8, + { + "required_external_receipt_evidence_count": len(required_evidence), + "external_receipt_acceptance_gate_count": len(acceptance_gates), + "summary_required_external_receipt_evidence_count": summary.get( + "required_external_receipt_evidence_count", 0 + ), + "summary_external_receipt_acceptance_gate_count": summary.get( + "external_receipt_acceptance_gate_count", 0 + ), + }, + "wait_for_external_receipt_evidence_contract", + ), + _authorization_signed_receipt_closeout_check( + "detached_receipt_verification_boundary_contract_complete", + len(detached_verification_checks) == 10 + and "detached_signature_verification_status_passed" in detached_verification_checks, + { + "detached_receipt_verification_check_count": len( + detached_verification_checks + ), + "requires_detached_signature_verification": ( + "detached_signature_verification_status_passed" + in detached_verification_checks + ), + }, + "wait_for_detached_receipt_verification_contract", + ), + _authorization_signed_receipt_closeout_check( + "operator_held_secret_boundary_carried_forward", + operator_secret_boundary.get("secret_reference_mode") + == "external_runtime_reference_only" + and operator_secret_boundary.get("secret_material_included") is False + and operator_secret_boundary.get("secret_material_required_in_preview") is False + and operator_secret_boundary.get("reads_secret_in_preview") is False + and operator_secret_boundary.get("accepts_plaintext_secret") is False + and operator_secret_boundary.get("permits_secret_value_logging") is False, + { + "secret_reference_mode": operator_secret_boundary.get("secret_reference_mode"), + "secret_material_included": operator_secret_boundary.get( + "secret_material_included" + ), + "reads_secret_in_preview": operator_secret_boundary.get( + "reads_secret_in_preview" + ), + }, + "abort_on_secret_boundary_violation", + ), + _authorization_signed_receipt_closeout_check( + "same_run_production_truth_required", + evidence_boundary.get("requires_fresh_production_truth_in_same_run") is True + and int(summary.get("same_run_truth_required_count") or 0) == 1, + { + "requires_fresh_production_truth_in_same_run": evidence_boundary.get( + "requires_fresh_production_truth_in_same_run" + ), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + }, + "require_same_run_production_truth", + ), + _authorization_signed_receipt_closeout_check( + "post_apply_verifier_required", + evidence_boundary.get("requires_post_apply_verifier") is True + and bool(evidence_boundary.get("post_apply_verifier_endpoint")) + and int(summary.get("post_apply_verifier_required_count") or 0) == 1, + { + "requires_post_apply_verifier": evidence_boundary.get( + "requires_post_apply_verifier" + ), + "post_apply_verifier_endpoint": evidence_boundary.get( + "post_apply_verifier_endpoint" + ), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + }, + "require_post_apply_verifier", + ), + _authorization_signed_receipt_closeout_check( + "migration_file_hash_locked", + evidence_boundary.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and evidence_boundary.get("hash_matches") is True + and bool(evidence_boundary.get("expected_sha256")) + and evidence_boundary.get("expected_sha256") == evidence_boundary.get("actual_sha256"), + { + "target_file": evidence_boundary.get("target_file"), + "hash_matches": evidence_boundary.get("hash_matches"), + "expected_sha256": evidence_boundary.get("expected_sha256"), + "actual_sha256": evidence_boundary.get("actual_sha256"), + }, + "abort_on_migration_file_hash_mismatch", + ), + _authorization_signed_receipt_closeout_check( + "closeout_has_no_signed_receipt_signature_or_authorization", + evidence_boundary.get("external_signed_authorization_receipt_included") is False + and evidence_boundary.get("signed_authorization_receipt_included") is False + and evidence_boundary.get("signature_material_included") is False + and evidence_boundary.get("secret_material_included") is False + and preflight_contract.get( + "permits_future_external_signing_receipt_evidence_boundary" + ) + is True + and preflight_contract.get("issues_database_apply_authorization") is False + and preflight_contract.get("ready_for_database_apply_now") is False + and preflight_contract.get("signs_database_apply_authorization") is False, + { + "external_signed_authorization_receipt_included": evidence_boundary.get( + "external_signed_authorization_receipt_included" + ), + "signed_authorization_receipt_included": evidence_boundary.get( + "signed_authorization_receipt_included" + ), + "signature_material_included": evidence_boundary.get( + "signature_material_included" + ), + "signs_database_apply_authorization": preflight_contract.get( + "signs_database_apply_authorization" + ), + }, + "block_if_signed_receipt_signature_or_authorization_is_present", + ), + _authorization_signed_receipt_closeout_check( + "preview_has_no_side_effects_and_no_signing", + side_effect_free, + { + "writes_script_count": summary.get("writes_script_count", 0), + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + }, + "abort_on_preview_side_effect_or_signing", + ), + _authorization_signed_receipt_closeout_check( + "manual_review_regression_absent", + int(summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY) or 0) == 0 + and future_preflight.get("manual_review_mode") == "exception_only", + { + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + "manual_review_mode": future_preflight.get("manual_review_mode"), + }, + "route_failed_verifier_to_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_PREFLIGHT" + ) + future_authorization_signed_receipt_closeout = { + "closeout_id": closeout_id, + "source_signed_receipt_preflight_id": future_preflight.get("preflight_id"), + "source_external_receipt_evidence_boundary_id": evidence_boundary.get("boundary_id"), + "source_signing_execution_closeout_id": future_preflight.get( + "source_signing_execution_closeout_id" + ), + "source_unsigned_receipt_boundary_id": future_preflight.get( + "source_unsigned_receipt_boundary_id" + ), + "source_final_signable_request_package_id": future_preflight.get( + "source_final_signable_request_package_id" + ), + "status": closeout_status, + "ready_for_future_signed_authorization_receipt_closeout": closeout_ready, + "can_enter_future_detached_receipt_verification_boundary": closeout_ready, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": 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, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + detached_receipt_verification_boundary = { + "boundary_id": closeout_id, + "authorization_material_type": "detached_receipt_verification_boundary", + "source_signed_receipt_preflight_id": future_preflight.get("preflight_id"), + "source_external_receipt_evidence_boundary_id": evidence_boundary.get("boundary_id"), + "source_signing_execution_closeout_id": evidence_boundary.get( + "source_signing_execution_closeout_id" + ), + "source_unsigned_receipt_boundary_id": evidence_boundary.get( + "source_unsigned_receipt_boundary_id" + ), + "source_final_signable_request_package_id": evidence_boundary.get( + "source_final_signable_request_package_id" + ), + "status": closeout_status, + "ready_for_future_detached_receipt_verification_boundary": closeout_ready, + "ready_for_future_signed_authorization_receipt_verification_lane": closeout_ready, + "required_external_receipt_evidence": required_evidence, + "required_external_receipt_evidence_count": len(required_evidence), + "external_receipt_acceptance_gates": acceptance_gates, + "external_receipt_acceptance_gate_count": len(acceptance_gates), + "detached_receipt_verification_checks": detached_verification_checks, + "detached_receipt_verification_check_count": len(detached_verification_checks), + "requires_detached_signature_verification": True, + "detached_signature_verification_performed": False, + "detached_signature_verification_status_required_in_future": True, + "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, + "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, + "operator_held_secret_boundary_contract": operator_secret_boundary, + "target_file": evidence_boundary.get("target_file"), + "expected_sha256": evidence_boundary.get("expected_sha256"), + "actual_sha256": evidence_boundary.get("actual_sha256"), + "hash_matches": evidence_boundary.get("hash_matches"), + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "post_apply_verifier_endpoint": evidence_boundary.get("post_apply_verifier_endpoint"), + } + signed_receipt_closeout_contract = { + "mode": "signed_authorization_receipt_closeout_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-closeout" + ), + "source_signed_receipt_preflight_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-preflight" + ), + "machine_verifiable": True, + "permits_future_detached_receipt_verification_boundary": closeout_ready, + "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", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(preflight.get("success")), + "generated_at": preflight.get("generated_at"), + "source_policy": preflight.get("policy"), + "stats": preflight.get("stats") or {}, + "summary": { + "authorization_signed_receipt_closeout_ready_count": 1 if closeout_ready else 0, + "signed_receipt_closeout_check_count": len(checks), + "signed_receipt_closeout_pass_count": passed_count, + "signed_receipt_closeout_waiting_count": len(waiting_checks), + "authorization_signed_receipt_preflight_ready_count": summary.get( + "authorization_signed_receipt_preflight_ready_count", 0 + ), + "signed_receipt_preflight_check_count": summary.get( + "signed_receipt_preflight_check_count", 0 + ), + "external_signing_receipt_evidence_boundary_count": summary.get( + "external_signing_receipt_evidence_boundary_count", 0 + ), + "detached_receipt_verification_boundary_count": 1, + "required_external_receipt_evidence_count": summary.get( + "required_external_receipt_evidence_count", 0 + ), + "external_receipt_acceptance_gate_count": summary.get( + "external_receipt_acceptance_gate_count", 0 + ), + "detached_receipt_verification_check_count": len(detached_verification_checks), + "operator_held_secret_boundary_count": summary.get( + "operator_held_secret_boundary_count", 0 + ), + "signing_execution_input_requirement_count": summary.get( + "signing_execution_input_requirement_count", 0 + ), + "signing_execution_abort_condition_count": summary.get( + "signing_execution_abort_condition_count", 0 + ), + "rollback_boundary_count": summary.get("rollback_boundary_count", 0), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "future_authorization_signed_receipt_closeout": ( + future_authorization_signed_receipt_closeout + ), + "detached_receipt_verification_boundary": detached_receipt_verification_boundary, + "signed_receipt_closeout_contract": signed_receipt_closeout_contract, + "signed_receipt_closeout_checks": checks, + "source_signed_receipt_preflight_summary": summary, + "source_signed_receipt_preflight_contract": preflight_contract, + "source_external_signing_receipt_evidence_boundary": evidence_boundary, + "safety": { + "read_only_db_apply_authorization_signed_receipt_closeout": True, + "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, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout as the detached receipt verification boundary for a future signed authorization receipt evidence lane.", + "Keep signed receipt content, signature material, secret values, shell execution, SQL, and DB writes out of this closeout.", + "Require real detached verification evidence in a future separate lane before any database apply authorization can be considered.", + ], + } + + +def build_pchome_auto_policy_db_apply_authorization_signed_receipt_evidence_intake( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Define the future signed receipt evidence intake schema without taking secrets.""" + closeout = build_pchome_auto_policy_db_apply_authorization_signed_receipt_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + future_closeout = closeout.get("future_authorization_signed_receipt_closeout") or {} + detached_boundary = closeout.get("detached_receipt_verification_boundary") or {} + closeout_contract = closeout.get("signed_receipt_closeout_contract") or {} + summary = closeout.get("summary") or {} + safety = closeout.get("safety") or {} + intake_id = _db_apply_authorization_signed_receipt_evidence_intake_id(closeout) + required_external_evidence = list( + detached_boundary.get("required_external_receipt_evidence") or [] + ) + external_acceptance_gates = list( + detached_boundary.get("external_receipt_acceptance_gates") or [] + ) + detached_checks = list( + detached_boundary.get("detached_receipt_verification_checks") or [] + ) + operator_secret_boundary = ( + detached_boundary.get("operator_held_secret_boundary_contract") or {} + ) + 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_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", + ] + side_effect_free = ( + int(summary.get("writes_script_count") or 0) == 0 + and int(summary.get("writes_artifact_count") or 0) == 0 + and int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_migration_count") or 0) == 0 + and int(summary.get("executes_endpoint_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and int(summary.get("signs_database_apply_authorization_count") or 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_script") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("signs_database_apply_authorization") is False + and detached_boundary.get("detached_signature_verification_performed") is False + and detached_boundary.get("external_signed_authorization_receipt_included") is False + and detached_boundary.get("signed_authorization_receipt_included") is False + and detached_boundary.get("signature_material_included") is False + and detached_boundary.get("secret_material_included") is False + and detached_boundary.get("secret_material_required_in_preview") is False + and detached_boundary.get("reads_secret_in_preview") is False + and detached_boundary.get("executes_shell_in_preview") is False + and detached_boundary.get("executes_sql_in_preview") is False + and detached_boundary.get("writes_database_in_preview") is False + ) + checks = [ + _authorization_signed_receipt_evidence_intake_check( + "signed_receipt_closeout_ready", + closeout.get("result") == "DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_CLOSEOUT_READY" + and future_closeout.get("ready_for_future_signed_authorization_receipt_closeout") + is True + and detached_boundary.get("ready_for_future_detached_receipt_verification_boundary") + is True, + { + "result": closeout.get("result"), + "ready_for_future_signed_authorization_receipt_closeout": ( + future_closeout.get( + "ready_for_future_signed_authorization_receipt_closeout" + ) + ), + "ready_for_future_detached_receipt_verification_boundary": ( + detached_boundary.get( + "ready_for_future_detached_receipt_verification_boundary" + ) + ), + }, + "wait_for_signed_receipt_closeout", + ), + _authorization_signed_receipt_evidence_intake_check( + "detached_receipt_verification_boundary_complete", + detached_boundary.get("authorization_material_type") + == "detached_receipt_verification_boundary" + and bool(detached_boundary.get("boundary_id")) + and bool(detached_boundary.get("source_signed_receipt_preflight_id")) + and bool(detached_boundary.get("source_external_receipt_evidence_boundary_id")) + and detached_boundary.get("ready_for_database_apply_now") is False + and detached_boundary.get("issues_database_apply_authorization") is False + and detached_boundary.get("signs_database_apply_authorization") is False, + { + "boundary_id": detached_boundary.get("boundary_id"), + "authorization_material_type": detached_boundary.get( + "authorization_material_type" + ), + "source_external_receipt_evidence_boundary_id": detached_boundary.get( + "source_external_receipt_evidence_boundary_id" + ), + }, + "wait_for_detached_receipt_verification_boundary", + ), + _authorization_signed_receipt_evidence_intake_check( + "source_chain_ids_present", + bool(future_closeout.get("closeout_id")) + and bool(future_closeout.get("source_signed_receipt_preflight_id")) + and bool(future_closeout.get("source_external_receipt_evidence_boundary_id")) + and bool(future_closeout.get("source_signing_execution_closeout_id")) + and bool(detached_boundary.get("source_final_signable_request_package_id")), + { + "closeout_id": future_closeout.get("closeout_id"), + "source_signed_receipt_preflight_id": future_closeout.get( + "source_signed_receipt_preflight_id" + ), + "source_final_signable_request_package_id": detached_boundary.get( + "source_final_signable_request_package_id" + ), + }, + "wait_for_source_authorization_chain_ids", + ), + _authorization_signed_receipt_evidence_intake_check( + "external_receipt_contract_carried_forward", + len(required_external_evidence) == 10 + and len(external_acceptance_gates) == 8 + and int(summary.get("required_external_receipt_evidence_count") or 0) == 10 + and int(summary.get("external_receipt_acceptance_gate_count") or 0) == 8, + { + "required_external_receipt_evidence_count": len(required_external_evidence), + "external_receipt_acceptance_gate_count": len(external_acceptance_gates), + "summary_required_external_receipt_evidence_count": summary.get( + "required_external_receipt_evidence_count", 0 + ), + "summary_external_receipt_acceptance_gate_count": summary.get( + "external_receipt_acceptance_gate_count", 0 + ), + }, + "wait_for_external_receipt_contract", + ), + _authorization_signed_receipt_evidence_intake_check( + "detached_verification_evidence_schema_complete", + len(detached_verification_evidence_fields) == 12 + and len(detached_verification_acceptance_gates) == 10 + and len(detached_checks) == 10 + and "detached_signature_verification_status_passed" in detached_checks, + { + "detached_verification_evidence_field_count": len( + detached_verification_evidence_fields + ), + "detached_verification_acceptance_gate_count": len( + detached_verification_acceptance_gates + ), + "source_detached_check_count": len(detached_checks), + }, + "wait_for_detached_verification_evidence_schema", + ), + _authorization_signed_receipt_evidence_intake_check( + "signer_and_algorithm_references_only", + detached_boundary.get("signer_key_id_reference_only") is True + and detached_boundary.get("signature_algorithm_reference_only") is True + and "signer_key_id_reference" in detached_verification_evidence_fields + and "signature_algorithm_reference" in detached_verification_evidence_fields, + { + "signer_key_id_reference_only": detached_boundary.get( + "signer_key_id_reference_only" + ), + "signature_algorithm_reference_only": detached_boundary.get( + "signature_algorithm_reference_only" + ), + }, + "abort_on_plaintext_key_or_algorithm_material", + ), + _authorization_signed_receipt_evidence_intake_check( + "no_signed_receipt_signature_secret_or_verification_execution", + detached_boundary.get("detached_signature_verification_performed") is False + and detached_boundary.get("external_signed_authorization_receipt_included") + is False + and detached_boundary.get("signed_authorization_receipt_included") is False + and detached_boundary.get("signature_material_included") is False + and detached_boundary.get("secret_material_included") is False + and operator_secret_boundary.get("secret_reference_mode") + == "external_runtime_reference_only" + and operator_secret_boundary.get("accepts_plaintext_secret") is False, + { + "detached_signature_verification_performed": detached_boundary.get( + "detached_signature_verification_performed" + ), + "external_signed_authorization_receipt_included": detached_boundary.get( + "external_signed_authorization_receipt_included" + ), + "signature_material_included": detached_boundary.get( + "signature_material_included" + ), + "secret_reference_mode": operator_secret_boundary.get( + "secret_reference_mode" + ), + }, + "abort_on_signed_receipt_signature_secret_or_verification_execution", + ), + _authorization_signed_receipt_evidence_intake_check( + "same_run_production_truth_required", + detached_boundary.get("requires_fresh_production_truth_in_same_run") is True + and int(summary.get("same_run_truth_required_count") or 0) == 1, + { + "requires_fresh_production_truth_in_same_run": detached_boundary.get( + "requires_fresh_production_truth_in_same_run" + ), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + }, + "require_same_run_production_truth", + ), + _authorization_signed_receipt_evidence_intake_check( + "post_apply_verifier_required", + detached_boundary.get("requires_post_apply_verifier") is True + and bool(detached_boundary.get("post_apply_verifier_endpoint")) + and int(summary.get("post_apply_verifier_required_count") or 0) == 1, + { + "requires_post_apply_verifier": detached_boundary.get( + "requires_post_apply_verifier" + ), + "post_apply_verifier_endpoint": detached_boundary.get( + "post_apply_verifier_endpoint" + ), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + }, + "require_post_apply_verifier", + ), + _authorization_signed_receipt_evidence_intake_check( + "migration_file_hash_locked", + detached_boundary.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and detached_boundary.get("hash_matches") is True + and bool(detached_boundary.get("expected_sha256")) + and detached_boundary.get("expected_sha256") + == detached_boundary.get("actual_sha256"), + { + "target_file": detached_boundary.get("target_file"), + "hash_matches": detached_boundary.get("hash_matches"), + "expected_sha256": detached_boundary.get("expected_sha256"), + "actual_sha256": detached_boundary.get("actual_sha256"), + }, + "abort_on_migration_file_hash_mismatch", + ), + _authorization_signed_receipt_evidence_intake_check( + "preview_has_no_side_effects_and_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + }, + "abort_on_preview_side_effect_or_signing", + ), + _authorization_signed_receipt_evidence_intake_check( + "manual_review_regression_absent", + int(summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY) or 0) == 0 + and future_closeout.get("manual_review_mode") == "exception_only", + { + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + "manual_review_mode": future_closeout.get("manual_review_mode"), + }, + "route_failed_verifier_to_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + intake_ready = not waiting_checks + intake_status = ( + "DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_EVIDENCE_INTAKE_READY" + if intake_ready + else "WAITING_FOR_DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_CLOSEOUT" + ) + future_signed_authorization_receipt_evidence_intake = { + "intake_id": intake_id, + "source_signed_receipt_closeout_id": future_closeout.get("closeout_id"), + "source_detached_receipt_verification_boundary_id": detached_boundary.get( + "boundary_id" + ), + "source_signed_receipt_preflight_id": detached_boundary.get( + "source_signed_receipt_preflight_id" + ), + "source_external_receipt_evidence_boundary_id": detached_boundary.get( + "source_external_receipt_evidence_boundary_id" + ), + "source_final_signable_request_package_id": detached_boundary.get( + "source_final_signable_request_package_id" + ), + "status": intake_status, + "ready_for_future_signed_authorization_receipt_evidence_intake": intake_ready, + "can_enter_future_detached_verification_evidence_validation": intake_ready, + "external_signed_authorization_receipt_evidence_schema_ready": intake_ready, + "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, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + detached_verification_evidence_schema = { + "schema_id": intake_id, + "authorization_material_type": "detached_verification_evidence_schema", + "source_signed_receipt_closeout_id": future_closeout.get("closeout_id"), + "source_detached_receipt_verification_boundary_id": detached_boundary.get( + "boundary_id" + ), + "source_signed_receipt_preflight_id": detached_boundary.get( + "source_signed_receipt_preflight_id" + ), + "source_external_receipt_evidence_boundary_id": detached_boundary.get( + "source_external_receipt_evidence_boundary_id" + ), + "source_final_signable_request_package_id": detached_boundary.get( + "source_final_signable_request_package_id" + ), + "status": intake_status, + "ready_for_future_detached_verification_evidence_schema": intake_ready, + "required_external_receipt_evidence": required_external_evidence, + "required_external_receipt_evidence_count": len(required_external_evidence), + "external_receipt_acceptance_gates": external_acceptance_gates, + "external_receipt_acceptance_gate_count": len(external_acceptance_gates), + "detached_receipt_verification_checks": detached_checks, + "detached_receipt_verification_check_count": len(detached_checks), + "detached_verification_evidence_fields": detached_verification_evidence_fields, + "detached_verification_evidence_field_count": len( + detached_verification_evidence_fields + ), + "detached_verification_acceptance_gates": detached_verification_acceptance_gates, + "detached_verification_acceptance_gate_count": len( + detached_verification_acceptance_gates + ), + "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, + "operator_held_secret_boundary_contract": operator_secret_boundary, + "target_file": detached_boundary.get("target_file"), + "expected_sha256": detached_boundary.get("expected_sha256"), + "actual_sha256": detached_boundary.get("actual_sha256"), + "hash_matches": detached_boundary.get("hash_matches"), + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "post_apply_verifier_endpoint": detached_boundary.get( + "post_apply_verifier_endpoint" + ), + } + signed_receipt_evidence_intake_contract = { + "mode": "signed_authorization_receipt_evidence_intake_schema_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-evidence-intake" + ), + "source_signed_receipt_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-closeout" + ), + "machine_verifiable": True, + "permits_future_detached_verification_evidence_validation": intake_ready, + "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", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_EVIDENCE_INTAKE_POLICY, + "result": intake_status, + "success": bool(closeout.get("success")), + "generated_at": closeout.get("generated_at"), + "source_policy": closeout.get("policy"), + "stats": closeout.get("stats") or {}, + "summary": { + "authorization_signed_receipt_evidence_intake_ready_count": ( + 1 if intake_ready else 0 + ), + "signed_receipt_evidence_intake_check_count": len(checks), + "signed_receipt_evidence_intake_pass_count": passed_count, + "signed_receipt_evidence_intake_waiting_count": len(waiting_checks), + "authorization_signed_receipt_closeout_ready_count": summary.get( + "authorization_signed_receipt_closeout_ready_count", 0 + ), + "signed_receipt_closeout_check_count": summary.get( + "signed_receipt_closeout_check_count", 0 + ), + "authorization_signed_receipt_preflight_ready_count": summary.get( + "authorization_signed_receipt_preflight_ready_count", 0 + ), + "signed_receipt_preflight_check_count": summary.get( + "signed_receipt_preflight_check_count", 0 + ), + "external_signing_receipt_evidence_boundary_count": summary.get( + "external_signing_receipt_evidence_boundary_count", 0 + ), + "detached_receipt_verification_boundary_count": summary.get( + "detached_receipt_verification_boundary_count", 0 + ), + "detached_verification_evidence_schema_count": 1, + "required_external_receipt_evidence_count": summary.get( + "required_external_receipt_evidence_count", 0 + ), + "external_receipt_acceptance_gate_count": summary.get( + "external_receipt_acceptance_gate_count", 0 + ), + "detached_receipt_verification_check_count": summary.get( + "detached_receipt_verification_check_count", 0 + ), + "detached_verification_evidence_field_count": len( + detached_verification_evidence_fields + ), + "detached_verification_acceptance_gate_count": len( + detached_verification_acceptance_gates + ), + "operator_held_secret_boundary_count": summary.get( + "operator_held_secret_boundary_count", 0 + ), + "rollback_boundary_count": summary.get("rollback_boundary_count", 0), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "future_signed_authorization_receipt_evidence_intake": ( + future_signed_authorization_receipt_evidence_intake + ), + "detached_verification_evidence_schema": detached_verification_evidence_schema, + "signed_receipt_evidence_intake_contract": signed_receipt_evidence_intake_contract, + "signed_receipt_evidence_intake_checks": checks, + "source_signed_receipt_closeout_summary": summary, + "source_signed_receipt_closeout_contract": closeout_contract, + "source_detached_receipt_verification_boundary": detached_boundary, + "safety": { + "read_only_db_apply_authorization_signed_receipt_evidence_intake": True, + "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", + }, + "next_actions": [ + "Use this intake schema for a future detached verification evidence validation lane.", + "Keep signed receipt body, signature material, secret values, shell execution, SQL, and DB writes out of the AI payload.", + "Only a later verifier lane may validate detached signature evidence; this intake still does not authorize DB apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_authorization_detached_verification_evidence_validation( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Validate the detached verification evidence schema without verifying signatures.""" + intake = build_pchome_auto_policy_db_apply_authorization_signed_receipt_evidence_intake( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + future_intake = intake.get("future_signed_authorization_receipt_evidence_intake") or {} + schema = intake.get("detached_verification_evidence_schema") or {} + intake_contract = intake.get("signed_receipt_evidence_intake_contract") or {} + summary = intake.get("summary") or {} + safety = intake.get("safety") or {} + validation_id = _db_apply_authorization_detached_verification_evidence_validation_id( + intake + ) + required_external_evidence = list(schema.get("required_external_receipt_evidence") or []) + external_acceptance_gates = list(schema.get("external_receipt_acceptance_gates") or []) + detached_checks = list(schema.get("detached_receipt_verification_checks") or []) + evidence_fields = list(schema.get("detached_verification_evidence_fields") or []) + evidence_acceptance_gates = list( + schema.get("detached_verification_acceptance_gates") or [] + ) + operator_secret_boundary = ( + schema.get("operator_held_secret_boundary_contract") or {} + ) + verifier_receipt_fields = [ + "verifier_receipt_id", + "source_signed_receipt_evidence_intake_id", + "source_detached_verification_evidence_schema_id", + "source_signed_receipt_closeout_id", + "source_external_receipt_evidence_boundary_id", + "external_receipt_id_reference", + "payload_sha256", + "receipt_sha256", + "detached_signature_verification_status", + "verifier_receipt_sha256", + "verified_at_utc", + "post_apply_verifier_endpoint", + ] + verifier_receipt_acceptance_gates = [ + "signed_receipt_evidence_intake_ready", + "source_detached_verification_evidence_schema_id_matches", + "source_external_receipt_evidence_boundary_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_passed", + "no_secret_signature_or_signed_receipt_body_in_ai_payload", + "post_apply_verifier_still_required", + ] + side_effect_free = ( + int(summary.get("writes_script_count") or 0) == 0 + and int(summary.get("writes_artifact_count") or 0) == 0 + and int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_migration_count") or 0) == 0 + and int(summary.get("executes_endpoint_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and int(summary.get("signs_database_apply_authorization_count") or 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_script") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("signs_database_apply_authorization") is False + and safety.get("performs_detached_signature_verification") is False + and schema.get("detached_signature_verification_performed") is False + 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("secret_material_included") is False + and schema.get("secret_material_required_in_preview") is False + and schema.get("accepts_plaintext_secret") is False + and schema.get("reads_secret_in_preview") is False + and schema.get("executes_shell_in_preview") is False + and schema.get("executes_sql_in_preview") is False + and schema.get("writes_database_in_preview") is False + ) + checks = [ + _authorization_detached_verification_evidence_validation_check( + "signed_receipt_evidence_intake_ready", + intake.get("result") + == "DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_EVIDENCE_INTAKE_READY" + and future_intake.get( + "ready_for_future_signed_authorization_receipt_evidence_intake" + ) + is True + and schema.get("ready_for_future_detached_verification_evidence_schema") + is True, + { + "result": intake.get("result"), + "ready_for_future_signed_authorization_receipt_evidence_intake": ( + future_intake.get( + "ready_for_future_signed_authorization_receipt_evidence_intake" + ) + ), + "ready_for_future_detached_verification_evidence_schema": schema.get( + "ready_for_future_detached_verification_evidence_schema" + ), + }, + "wait_for_signed_receipt_evidence_intake", + ), + _authorization_detached_verification_evidence_validation_check( + "detached_verification_evidence_schema_complete", + schema.get("authorization_material_type") + == "detached_verification_evidence_schema" + and bool(schema.get("schema_id")) + and bool(schema.get("source_signed_receipt_closeout_id")) + and bool(schema.get("source_detached_receipt_verification_boundary_id")) + and schema.get("ready_for_database_apply_now") is False + and schema.get("issues_database_apply_authorization") is False + and schema.get("signs_database_apply_authorization") is False, + { + "schema_id": schema.get("schema_id"), + "authorization_material_type": schema.get("authorization_material_type"), + "source_detached_receipt_verification_boundary_id": schema.get( + "source_detached_receipt_verification_boundary_id" + ), + }, + "wait_for_detached_verification_evidence_schema", + ), + _authorization_detached_verification_evidence_validation_check( + "source_chain_ids_present", + bool(future_intake.get("intake_id")) + and bool(future_intake.get("source_signed_receipt_closeout_id")) + and bool(future_intake.get("source_detached_receipt_verification_boundary_id")) + and bool(future_intake.get("source_external_receipt_evidence_boundary_id")) + and bool(schema.get("source_final_signable_request_package_id")), + { + "intake_id": future_intake.get("intake_id"), + "source_signed_receipt_closeout_id": future_intake.get( + "source_signed_receipt_closeout_id" + ), + "source_final_signable_request_package_id": schema.get( + "source_final_signable_request_package_id" + ), + }, + "wait_for_source_authorization_chain_ids", + ), + _authorization_detached_verification_evidence_validation_check( + "external_receipt_contract_carried_forward", + len(required_external_evidence) == 10 + and len(external_acceptance_gates) == 8 + and int(summary.get("required_external_receipt_evidence_count") or 0) == 10 + and int(summary.get("external_receipt_acceptance_gate_count") or 0) == 8, + { + "required_external_receipt_evidence_count": len(required_external_evidence), + "external_receipt_acceptance_gate_count": len(external_acceptance_gates), + "summary_required_external_receipt_evidence_count": summary.get( + "required_external_receipt_evidence_count", 0 + ), + "summary_external_receipt_acceptance_gate_count": summary.get( + "external_receipt_acceptance_gate_count", 0 + ), + }, + "wait_for_external_receipt_contract", + ), + _authorization_detached_verification_evidence_validation_check( + "detached_verification_evidence_requirements_carried_forward", + len(detached_checks) == 10 + and len(evidence_fields) == 12 + and len(evidence_acceptance_gates) == 10 + and int(summary.get("detached_verification_evidence_field_count") or 0) == 12 + and int(summary.get("detached_verification_acceptance_gate_count") or 0) == 10, + { + "detached_receipt_verification_check_count": len(detached_checks), + "detached_verification_evidence_field_count": len(evidence_fields), + "detached_verification_acceptance_gate_count": len( + evidence_acceptance_gates + ), + }, + "wait_for_detached_verification_evidence_requirements", + ), + _authorization_detached_verification_evidence_validation_check( + "verifier_receipt_closeout_boundary_contract_complete", + len(verifier_receipt_fields) == 12 + and len(verifier_receipt_acceptance_gates) == 10 + and "detached_signature_verification_status_passed" + in verifier_receipt_acceptance_gates + and "verifier_receipt_sha256" in verifier_receipt_fields, + { + "verifier_receipt_field_count": len(verifier_receipt_fields), + "verifier_receipt_acceptance_gate_count": len( + verifier_receipt_acceptance_gates + ), + "requires_status_passed": ( + "detached_signature_verification_status_passed" + in verifier_receipt_acceptance_gates + ), + }, + "wait_for_verifier_receipt_closeout_boundary_contract", + ), + _authorization_detached_verification_evidence_validation_check( + "signer_and_algorithm_references_only", + schema.get("signer_key_id_reference_only") is True + and schema.get("signature_algorithm_reference_only") is True + and "signer_key_id_reference" in evidence_fields + and "signature_algorithm_reference" in evidence_fields, + { + "signer_key_id_reference_only": schema.get( + "signer_key_id_reference_only" + ), + "signature_algorithm_reference_only": schema.get( + "signature_algorithm_reference_only" + ), + }, + "abort_on_plaintext_key_or_algorithm_material", + ), + _authorization_detached_verification_evidence_validation_check( + "secret_and_signed_material_boundary_enforced", + schema.get("detached_signature_verification_performed") is False + 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("secret_material_included") is False + and schema.get("accepts_plaintext_secret") is False + and operator_secret_boundary.get("secret_reference_mode") + == "external_runtime_reference_only" + and operator_secret_boundary.get("accepts_plaintext_secret") is False, + { + "detached_signature_verification_performed": schema.get( + "detached_signature_verification_performed" + ), + "external_signed_authorization_receipt_included": schema.get( + "external_signed_authorization_receipt_included" + ), + "signature_material_included": schema.get("signature_material_included"), + "accepts_plaintext_secret": schema.get("accepts_plaintext_secret"), + "secret_reference_mode": operator_secret_boundary.get( + "secret_reference_mode" + ), + }, + "abort_on_signed_material_or_secret_boundary_violation", + ), + _authorization_detached_verification_evidence_validation_check( + "same_run_production_truth_required", + schema.get("requires_fresh_production_truth_in_same_run") is True + and int(summary.get("same_run_truth_required_count") or 0) == 1, + { + "requires_fresh_production_truth_in_same_run": schema.get( + "requires_fresh_production_truth_in_same_run" + ), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + }, + "require_same_run_production_truth", + ), + _authorization_detached_verification_evidence_validation_check( + "post_apply_verifier_and_hash_lock_required", + schema.get("requires_post_apply_verifier") is True + and bool(schema.get("post_apply_verifier_endpoint")) + and int(summary.get("post_apply_verifier_required_count") or 0) == 1 + and schema.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and schema.get("hash_matches") is True + and bool(schema.get("expected_sha256")) + and schema.get("expected_sha256") == schema.get("actual_sha256"), + { + "requires_post_apply_verifier": schema.get("requires_post_apply_verifier"), + "post_apply_verifier_endpoint": schema.get("post_apply_verifier_endpoint"), + "target_file": schema.get("target_file"), + "hash_matches": schema.get("hash_matches"), + }, + "require_post_apply_verifier_and_hash_lock", + ), + _authorization_detached_verification_evidence_validation_check( + "preview_has_no_side_effects_no_verification_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + "performs_detached_signature_verification": safety.get( + "performs_detached_signature_verification" + ), + }, + "abort_on_preview_side_effect_verification_or_signing", + ), + _authorization_detached_verification_evidence_validation_check( + "manual_review_regression_absent", + int(summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY) or 0) == 0 + and future_intake.get("manual_review_mode") == "exception_only", + { + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + "manual_review_mode": future_intake.get("manual_review_mode"), + }, + "route_failed_verifier_to_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + validation_ready = not waiting_checks + validation_status = ( + "DB_APPLY_AUTHORIZATION_DETACHED_VERIFICATION_EVIDENCE_VALIDATION_READY" + if validation_ready + else "WAITING_FOR_DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_EVIDENCE_INTAKE" + ) + future_detached_verification_evidence_validation = { + "validation_id": validation_id, + "source_signed_receipt_evidence_intake_id": future_intake.get("intake_id"), + "source_detached_verification_evidence_schema_id": schema.get("schema_id"), + "source_signed_receipt_closeout_id": schema.get( + "source_signed_receipt_closeout_id" + ), + "source_detached_receipt_verification_boundary_id": schema.get( + "source_detached_receipt_verification_boundary_id" + ), + "source_external_receipt_evidence_boundary_id": schema.get( + "source_external_receipt_evidence_boundary_id" + ), + "source_final_signable_request_package_id": schema.get( + "source_final_signable_request_package_id" + ), + "status": validation_status, + "ready_for_future_detached_verification_evidence_validation": validation_ready, + "can_enter_future_verifier_receipt_closeout": validation_ready, + "verifier_receipt_closeout_boundary_ready": validation_ready, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "detached_signature_verification_performed": False, + "verifier_receipt_persisted": 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, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + verifier_receipt_closeout_boundary = { + "boundary_id": validation_id, + "authorization_material_type": "verifier_receipt_closeout_boundary", + "source_signed_receipt_evidence_intake_id": future_intake.get("intake_id"), + "source_detached_verification_evidence_schema_id": schema.get("schema_id"), + "source_signed_receipt_closeout_id": schema.get( + "source_signed_receipt_closeout_id" + ), + "source_detached_receipt_verification_boundary_id": schema.get( + "source_detached_receipt_verification_boundary_id" + ), + "source_external_receipt_evidence_boundary_id": schema.get( + "source_external_receipt_evidence_boundary_id" + ), + "source_final_signable_request_package_id": schema.get( + "source_final_signable_request_package_id" + ), + "status": validation_status, + "ready_for_future_verifier_receipt_closeout_boundary": validation_ready, + "required_external_receipt_evidence": required_external_evidence, + "required_external_receipt_evidence_count": len(required_external_evidence), + "external_receipt_acceptance_gates": external_acceptance_gates, + "external_receipt_acceptance_gate_count": len(external_acceptance_gates), + "detached_receipt_verification_checks": detached_checks, + "detached_receipt_verification_check_count": len(detached_checks), + "detached_verification_evidence_fields": evidence_fields, + "detached_verification_evidence_field_count": len(evidence_fields), + "detached_verification_acceptance_gates": evidence_acceptance_gates, + "detached_verification_acceptance_gate_count": len(evidence_acceptance_gates), + "verifier_receipt_fields": verifier_receipt_fields, + "verifier_receipt_field_count": len(verifier_receipt_fields), + "verifier_receipt_acceptance_gates": verifier_receipt_acceptance_gates, + "verifier_receipt_acceptance_gate_count": len(verifier_receipt_acceptance_gates), + "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, + "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, + "operator_held_secret_boundary_contract": operator_secret_boundary, + "target_file": schema.get("target_file"), + "expected_sha256": schema.get("expected_sha256"), + "actual_sha256": schema.get("actual_sha256"), + "hash_matches": schema.get("hash_matches"), + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "post_apply_verifier_endpoint": schema.get("post_apply_verifier_endpoint"), + } + detached_verification_evidence_validation_contract = { + "mode": "detached_verification_evidence_validation_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-detached-verification-evidence-validation" + ), + "source_signed_receipt_evidence_intake_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-evidence-intake" + ), + "machine_verifiable": True, + "permits_future_verifier_receipt_closeout": validation_ready, + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": 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", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_AUTHORIZATION_DETACHED_VERIFICATION_EVIDENCE_VALIDATION_POLICY, + "result": validation_status, + "success": bool(intake.get("success")), + "generated_at": intake.get("generated_at"), + "source_policy": intake.get("policy"), + "stats": intake.get("stats") or {}, + "summary": { + "authorization_detached_verification_evidence_validation_ready_count": ( + 1 if validation_ready else 0 + ), + "detached_verification_evidence_validation_check_count": len(checks), + "detached_verification_evidence_validation_pass_count": passed_count, + "detached_verification_evidence_validation_waiting_count": len(waiting_checks), + "authorization_signed_receipt_evidence_intake_ready_count": summary.get( + "authorization_signed_receipt_evidence_intake_ready_count", 0 + ), + "signed_receipt_evidence_intake_check_count": summary.get( + "signed_receipt_evidence_intake_check_count", 0 + ), + "authorization_signed_receipt_closeout_ready_count": summary.get( + "authorization_signed_receipt_closeout_ready_count", 0 + ), + "signed_receipt_closeout_check_count": summary.get( + "signed_receipt_closeout_check_count", 0 + ), + "detached_receipt_verification_boundary_count": summary.get( + "detached_receipt_verification_boundary_count", 0 + ), + "detached_verification_evidence_schema_count": summary.get( + "detached_verification_evidence_schema_count", 0 + ), + "verifier_receipt_closeout_boundary_count": 1, + "required_external_receipt_evidence_count": summary.get( + "required_external_receipt_evidence_count", 0 + ), + "external_receipt_acceptance_gate_count": summary.get( + "external_receipt_acceptance_gate_count", 0 + ), + "detached_receipt_verification_check_count": summary.get( + "detached_receipt_verification_check_count", 0 + ), + "detached_verification_evidence_field_count": summary.get( + "detached_verification_evidence_field_count", 0 + ), + "detached_verification_acceptance_gate_count": summary.get( + "detached_verification_acceptance_gate_count", 0 + ), + "verifier_receipt_field_count": len(verifier_receipt_fields), + "verifier_receipt_acceptance_gate_count": len( + verifier_receipt_acceptance_gates + ), + "operator_held_secret_boundary_count": summary.get( + "operator_held_secret_boundary_count", 0 + ), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "future_detached_verification_evidence_validation": ( + future_detached_verification_evidence_validation + ), + "verifier_receipt_closeout_boundary": verifier_receipt_closeout_boundary, + "detached_verification_evidence_validation_contract": ( + detached_verification_evidence_validation_contract + ), + "detached_verification_evidence_validation_checks": checks, + "source_signed_receipt_evidence_intake_summary": summary, + "source_signed_receipt_evidence_intake_contract": intake_contract, + "source_detached_verification_evidence_schema": schema, + "safety": { + "read_only_db_apply_authorization_detached_verification_evidence_validation": ( + True + ), + "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_verifier_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this validation boundary for a future verifier receipt closeout lane.", + "Keep signed receipt body, signature material, secret values, shell execution, SQL, and DB writes out of the AI payload.", + "Only a separate verifier receipt closeout may carry validation receipts; this validation lane still does not authorize DB apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_authorization_verifier_receipt_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out verifier receipt evidence handoff without persisting receipts.""" + validation = ( + build_pchome_auto_policy_db_apply_authorization_detached_verification_evidence_validation( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_validation = validation.get("future_detached_verification_evidence_validation") or {} + boundary = validation.get("verifier_receipt_closeout_boundary") or {} + validation_contract = ( + validation.get("detached_verification_evidence_validation_contract") or {} + ) + summary = validation.get("summary") or {} + safety = validation.get("safety") or {} + closeout_id = _db_apply_authorization_verifier_receipt_closeout_id(validation) + required_external_evidence = list(boundary.get("required_external_receipt_evidence") or []) + external_acceptance_gates = list(boundary.get("external_receipt_acceptance_gates") or []) + verifier_receipt_fields = list(boundary.get("verifier_receipt_fields") or []) + verifier_receipt_acceptance_gates = list( + boundary.get("verifier_receipt_acceptance_gates") or [] + ) + evidence_fields = list(boundary.get("detached_verification_evidence_fields") or []) + evidence_acceptance_gates = list( + boundary.get("detached_verification_acceptance_gates") or [] + ) + operator_secret_boundary = ( + boundary.get("operator_held_secret_boundary_contract") or {} + ) + verifier_receipt_evidence_handoff_fields = [ + "handoff_id", + "source_verifier_receipt_closeout_boundary_id", + "source_detached_verification_evidence_validation_id", + "source_signed_receipt_evidence_intake_id", + "source_final_signable_request_package_id", + "verifier_receipt_id_reference", + "external_receipt_id_reference", + "payload_sha256", + "receipt_sha256", + "verifier_receipt_sha256", + "detached_signature_verification_status", + "post_apply_verifier_endpoint", + ] + verifier_receipt_handoff_acceptance_gates = [ + "detached_verification_evidence_validation_ready", + "verifier_receipt_closeout_boundary_ready", + "source_chain_ids_match", + "payload_hash_matches_final_signable_request_package", + "receipt_hash_is_present_and_nonempty", + "verifier_receipt_hash_is_present_and_nonempty", + "detached_signature_verification_status_passed", + "no_secret_signature_or_signed_receipt_body_in_ai_payload", + "verifier_receipt_not_persisted_by_preview", + "post_apply_verifier_still_required", + ] + side_effect_free = ( + int(summary.get("writes_script_count") or 0) == 0 + and int(summary.get("writes_artifact_count") or 0) == 0 + and int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_migration_count") or 0) == 0 + and int(summary.get("executes_endpoint_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and int(summary.get("signs_database_apply_authorization_count") or 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_script") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("signs_database_apply_authorization") is False + and safety.get("performs_detached_signature_verification") is False + and safety.get("persists_verifier_receipt") is False + and boundary.get("detached_signature_verification_performed") is False + and boundary.get("verifier_receipt_persisted") is False + and boundary.get("external_signed_authorization_receipt_included") is False + and boundary.get("signed_authorization_receipt_included") is False + and boundary.get("signature_material_included") is False + and boundary.get("secret_material_included") is False + and boundary.get("secret_material_required_in_preview") is False + and boundary.get("accepts_plaintext_secret") is False + and boundary.get("reads_secret_in_preview") is False + and boundary.get("executes_shell_in_preview") is False + and boundary.get("executes_sql_in_preview") is False + and boundary.get("writes_database_in_preview") is False + ) + checks = [ + _authorization_verifier_receipt_closeout_check( + "detached_verification_evidence_validation_ready", + validation.get("result") + == "DB_APPLY_AUTHORIZATION_DETACHED_VERIFICATION_EVIDENCE_VALIDATION_READY" + and future_validation.get( + "ready_for_future_detached_verification_evidence_validation" + ) + is True + and boundary.get("ready_for_future_verifier_receipt_closeout_boundary") + is True, + { + "result": validation.get("result"), + "ready_for_future_detached_verification_evidence_validation": ( + future_validation.get( + "ready_for_future_detached_verification_evidence_validation" + ) + ), + "ready_for_future_verifier_receipt_closeout_boundary": boundary.get( + "ready_for_future_verifier_receipt_closeout_boundary" + ), + }, + "wait_for_detached_verification_evidence_validation", + ), + _authorization_verifier_receipt_closeout_check( + "verifier_receipt_closeout_boundary_complete", + boundary.get("authorization_material_type") + == "verifier_receipt_closeout_boundary" + and bool(boundary.get("boundary_id")) + and bool(boundary.get("source_detached_verification_evidence_schema_id")) + and bool(boundary.get("source_signed_receipt_evidence_intake_id")) + and boundary.get("ready_for_database_apply_now") is False + and boundary.get("issues_database_apply_authorization") is False + and boundary.get("signs_database_apply_authorization") is False, + { + "boundary_id": boundary.get("boundary_id"), + "authorization_material_type": boundary.get("authorization_material_type"), + "source_detached_verification_evidence_schema_id": boundary.get( + "source_detached_verification_evidence_schema_id" + ), + }, + "wait_for_verifier_receipt_closeout_boundary", + ), + _authorization_verifier_receipt_closeout_check( + "source_chain_ids_present", + bool(future_validation.get("validation_id")) + and bool(future_validation.get("source_signed_receipt_evidence_intake_id")) + and bool(future_validation.get("source_detached_verification_evidence_schema_id")) + and bool(future_validation.get("source_signed_receipt_closeout_id")) + and bool(boundary.get("source_final_signable_request_package_id")), + { + "validation_id": future_validation.get("validation_id"), + "source_signed_receipt_evidence_intake_id": future_validation.get( + "source_signed_receipt_evidence_intake_id" + ), + "source_final_signable_request_package_id": boundary.get( + "source_final_signable_request_package_id" + ), + }, + "wait_for_source_authorization_chain_ids", + ), + _authorization_verifier_receipt_closeout_check( + "verifier_receipt_contract_carried_forward", + len(verifier_receipt_fields) == 12 + and len(verifier_receipt_acceptance_gates) == 10 + and int(summary.get("verifier_receipt_field_count") or 0) == 12 + and int(summary.get("verifier_receipt_acceptance_gate_count") or 0) == 10, + { + "verifier_receipt_field_count": len(verifier_receipt_fields), + "verifier_receipt_acceptance_gate_count": len( + verifier_receipt_acceptance_gates + ), + "summary_verifier_receipt_field_count": summary.get( + "verifier_receipt_field_count", 0 + ), + "summary_verifier_receipt_acceptance_gate_count": summary.get( + "verifier_receipt_acceptance_gate_count", 0 + ), + }, + "wait_for_verifier_receipt_contract", + ), + _authorization_verifier_receipt_closeout_check( + "detached_evidence_and_external_receipt_contracts_carried_forward", + len(required_external_evidence) == 10 + and len(external_acceptance_gates) == 8 + and len(evidence_fields) == 12 + and len(evidence_acceptance_gates) == 10, + { + "required_external_receipt_evidence_count": len(required_external_evidence), + "external_receipt_acceptance_gate_count": len(external_acceptance_gates), + "detached_verification_evidence_field_count": len(evidence_fields), + "detached_verification_acceptance_gate_count": len( + evidence_acceptance_gates + ), + }, + "wait_for_detached_evidence_or_external_receipt_contract", + ), + _authorization_verifier_receipt_closeout_check( + "verifier_receipt_evidence_handoff_contract_complete", + len(verifier_receipt_evidence_handoff_fields) == 12 + and len(verifier_receipt_handoff_acceptance_gates) == 10 + and "verifier_receipt_sha256" in verifier_receipt_evidence_handoff_fields + and "verifier_receipt_not_persisted_by_preview" + in verifier_receipt_handoff_acceptance_gates, + { + "verifier_receipt_evidence_handoff_field_count": len( + verifier_receipt_evidence_handoff_fields + ), + "verifier_receipt_handoff_acceptance_gate_count": len( + verifier_receipt_handoff_acceptance_gates + ), + }, + "wait_for_verifier_receipt_evidence_handoff_contract", + ), + _authorization_verifier_receipt_closeout_check( + "secret_signed_material_and_receipt_persistence_boundary_enforced", + boundary.get("detached_signature_verification_performed") is False + and boundary.get("verifier_receipt_persisted") is False + and boundary.get("external_signed_authorization_receipt_included") is False + and boundary.get("signed_authorization_receipt_included") is False + and boundary.get("signature_material_included") is False + and boundary.get("secret_material_included") is False + and boundary.get("accepts_plaintext_secret") is False + and operator_secret_boundary.get("secret_reference_mode") + == "external_runtime_reference_only" + and operator_secret_boundary.get("accepts_plaintext_secret") is False, + { + "detached_signature_verification_performed": boundary.get( + "detached_signature_verification_performed" + ), + "verifier_receipt_persisted": boundary.get( + "verifier_receipt_persisted" + ), + "external_signed_authorization_receipt_included": boundary.get( + "external_signed_authorization_receipt_included" + ), + "signature_material_included": boundary.get("signature_material_included"), + "accepts_plaintext_secret": boundary.get("accepts_plaintext_secret"), + }, + "abort_on_signed_material_secret_or_receipt_persistence", + ), + _authorization_verifier_receipt_closeout_check( + "same_run_production_truth_required", + boundary.get("requires_fresh_production_truth_in_same_run") is True + and int(summary.get("same_run_truth_required_count") or 0) == 1, + { + "requires_fresh_production_truth_in_same_run": boundary.get( + "requires_fresh_production_truth_in_same_run" + ), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + }, + "require_same_run_production_truth", + ), + _authorization_verifier_receipt_closeout_check( + "post_apply_verifier_and_hash_lock_required", + boundary.get("requires_post_apply_verifier") is True + and bool(boundary.get("post_apply_verifier_endpoint")) + and int(summary.get("post_apply_verifier_required_count") or 0) == 1 + and boundary.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and boundary.get("hash_matches") is True + and bool(boundary.get("expected_sha256")) + and boundary.get("expected_sha256") == boundary.get("actual_sha256"), + { + "requires_post_apply_verifier": boundary.get("requires_post_apply_verifier"), + "post_apply_verifier_endpoint": boundary.get( + "post_apply_verifier_endpoint" + ), + "target_file": boundary.get("target_file"), + "hash_matches": boundary.get("hash_matches"), + }, + "require_post_apply_verifier_and_hash_lock", + ), + _authorization_verifier_receipt_closeout_check( + "closeout_contract_blocks_database_apply", + validation_contract.get("permits_future_verifier_receipt_closeout") is True + and validation_contract.get("issues_database_apply_authorization") is False + and validation_contract.get("ready_for_database_apply_now") is False + and validation_contract.get("signs_database_apply_authorization") is False + and validation_contract.get("persists_verifier_receipt") is False + and validation_contract.get("performs_detached_signature_verification") is False, + { + "permits_future_verifier_receipt_closeout": validation_contract.get( + "permits_future_verifier_receipt_closeout" + ), + "ready_for_database_apply_now": validation_contract.get( + "ready_for_database_apply_now" + ), + "persists_verifier_receipt": validation_contract.get( + "persists_verifier_receipt" + ), + }, + "abort_if_validation_contract_authorizes_database_apply", + ), + _authorization_verifier_receipt_closeout_check( + "preview_has_no_side_effects_no_verification_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + "performs_detached_signature_verification": safety.get( + "performs_detached_signature_verification" + ), + "persists_verifier_receipt": safety.get("persists_verifier_receipt"), + }, + "abort_on_preview_side_effect_verification_signing_or_persistence", + ), + _authorization_verifier_receipt_closeout_check( + "manual_review_regression_absent", + int(summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY) or 0) == 0 + and future_validation.get("manual_review_mode") == "exception_only", + { + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + "manual_review_mode": future_validation.get("manual_review_mode"), + }, + "route_failed_verifier_to_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_AUTHORIZATION_VERIFIER_RECEIPT_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_AUTHORIZATION_DETACHED_VERIFICATION_EVIDENCE_VALIDATION" + ) + future_verifier_receipt_closeout = { + "closeout_id": closeout_id, + "source_detached_verification_evidence_validation_id": future_validation.get( + "validation_id" + ), + "source_verifier_receipt_closeout_boundary_id": boundary.get("boundary_id"), + "source_signed_receipt_evidence_intake_id": boundary.get( + "source_signed_receipt_evidence_intake_id" + ), + "source_detached_verification_evidence_schema_id": boundary.get( + "source_detached_verification_evidence_schema_id" + ), + "source_final_signable_request_package_id": boundary.get( + "source_final_signable_request_package_id" + ), + "status": closeout_status, + "ready_for_future_verifier_receipt_closeout": closeout_ready, + "can_enter_future_database_apply_authorization_verifier_handoff": closeout_ready, + "verifier_receipt_evidence_handoff_ready": closeout_ready, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "detached_signature_verification_performed": False, + "verifier_receipt_persisted": 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, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + verifier_receipt_evidence_handoff = { + "handoff_id": closeout_id, + "authorization_material_type": "verifier_receipt_evidence_handoff", + "source_detached_verification_evidence_validation_id": future_validation.get( + "validation_id" + ), + "source_verifier_receipt_closeout_boundary_id": boundary.get("boundary_id"), + "source_signed_receipt_evidence_intake_id": boundary.get( + "source_signed_receipt_evidence_intake_id" + ), + "source_detached_verification_evidence_schema_id": boundary.get( + "source_detached_verification_evidence_schema_id" + ), + "source_signed_receipt_closeout_id": boundary.get( + "source_signed_receipt_closeout_id" + ), + "source_external_receipt_evidence_boundary_id": boundary.get( + "source_external_receipt_evidence_boundary_id" + ), + "source_final_signable_request_package_id": boundary.get( + "source_final_signable_request_package_id" + ), + "status": closeout_status, + "ready_for_future_verifier_receipt_evidence_handoff": closeout_ready, + "required_external_receipt_evidence": required_external_evidence, + "required_external_receipt_evidence_count": len(required_external_evidence), + "external_receipt_acceptance_gates": external_acceptance_gates, + "external_receipt_acceptance_gate_count": len(external_acceptance_gates), + "verifier_receipt_fields": verifier_receipt_fields, + "verifier_receipt_field_count": len(verifier_receipt_fields), + "verifier_receipt_acceptance_gates": verifier_receipt_acceptance_gates, + "verifier_receipt_acceptance_gate_count": len(verifier_receipt_acceptance_gates), + "verifier_receipt_evidence_handoff_fields": ( + verifier_receipt_evidence_handoff_fields + ), + "verifier_receipt_evidence_handoff_field_count": len( + verifier_receipt_evidence_handoff_fields + ), + "verifier_receipt_handoff_acceptance_gates": ( + verifier_receipt_handoff_acceptance_gates + ), + "verifier_receipt_handoff_acceptance_gate_count": len( + verifier_receipt_handoff_acceptance_gates + ), + "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, + "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, + "operator_held_secret_boundary_contract": operator_secret_boundary, + "target_file": boundary.get("target_file"), + "expected_sha256": boundary.get("expected_sha256"), + "actual_sha256": boundary.get("actual_sha256"), + "hash_matches": boundary.get("hash_matches"), + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "post_apply_verifier_endpoint": boundary.get("post_apply_verifier_endpoint"), + } + verifier_receipt_closeout_contract = { + "mode": "verifier_receipt_closeout_handoff_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-verifier-receipt-closeout" + ), + "source_detached_verification_evidence_validation_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-detached-verification-evidence-validation" + ), + "machine_verifiable": True, + "permits_future_database_apply_authorization_verifier_handoff": closeout_ready, + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": 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", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_AUTHORIZATION_VERIFIER_RECEIPT_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(validation.get("success")), + "generated_at": validation.get("generated_at"), + "source_policy": validation.get("policy"), + "stats": validation.get("stats") or {}, + "summary": { + "authorization_verifier_receipt_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "verifier_receipt_closeout_check_count": len(checks), + "verifier_receipt_closeout_pass_count": passed_count, + "verifier_receipt_closeout_waiting_count": len(waiting_checks), + "authorization_detached_verification_evidence_validation_ready_count": ( + summary.get( + "authorization_detached_verification_evidence_validation_ready_count", + 0, + ) + ), + "detached_verification_evidence_validation_check_count": summary.get( + "detached_verification_evidence_validation_check_count", 0 + ), + "authorization_signed_receipt_evidence_intake_ready_count": summary.get( + "authorization_signed_receipt_evidence_intake_ready_count", 0 + ), + "signed_receipt_evidence_intake_check_count": summary.get( + "signed_receipt_evidence_intake_check_count", 0 + ), + "verifier_receipt_closeout_boundary_count": summary.get( + "verifier_receipt_closeout_boundary_count", 0 + ), + "verifier_receipt_evidence_handoff_count": 1, + "required_external_receipt_evidence_count": summary.get( + "required_external_receipt_evidence_count", 0 + ), + "external_receipt_acceptance_gate_count": summary.get( + "external_receipt_acceptance_gate_count", 0 + ), + "verifier_receipt_field_count": summary.get( + "verifier_receipt_field_count", 0 + ), + "verifier_receipt_acceptance_gate_count": summary.get( + "verifier_receipt_acceptance_gate_count", 0 + ), + "verifier_receipt_evidence_handoff_field_count": len( + verifier_receipt_evidence_handoff_fields + ), + "verifier_receipt_handoff_acceptance_gate_count": len( + verifier_receipt_handoff_acceptance_gates + ), + "detached_verification_evidence_field_count": summary.get( + "detached_verification_evidence_field_count", 0 + ), + "detached_verification_acceptance_gate_count": summary.get( + "detached_verification_acceptance_gate_count", 0 + ), + "operator_held_secret_boundary_count": summary.get( + "operator_held_secret_boundary_count", 0 + ), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "future_verifier_receipt_closeout": future_verifier_receipt_closeout, + "verifier_receipt_evidence_handoff": verifier_receipt_evidence_handoff, + "verifier_receipt_closeout_contract": verifier_receipt_closeout_contract, + "verifier_receipt_closeout_checks": checks, + "source_detached_verification_evidence_validation_summary": summary, + "source_detached_verification_evidence_validation_contract": validation_contract, + "source_verifier_receipt_closeout_boundary": boundary, + "safety": { + "read_only_db_apply_authorization_verifier_receipt_closeout": True, + "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_verifier_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this verifier receipt closeout as the evidence handoff for a future database apply authorization verifier lane.", + "Keep signed receipt body, signature material, secret values, shell execution, SQL, and DB writes out of this handoff.", + "A later verifier handoff may accept external verifier receipts; this closeout still does not authorize DB apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_authorization_evidence_execution_preflight( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Preflight future authorization evidence execution without executing it.""" + closeout = build_pchome_auto_policy_db_apply_authorization_verifier_receipt_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + future_closeout = closeout.get("future_verifier_receipt_closeout") or {} + handoff = closeout.get("verifier_receipt_evidence_handoff") or {} + closeout_contract = closeout.get("verifier_receipt_closeout_contract") or {} + summary = closeout.get("summary") or {} + safety = closeout.get("safety") or {} + preflight_id = _db_apply_authorization_evidence_execution_preflight_id(closeout) + verifier_receipt_fields = list(handoff.get("verifier_receipt_fields") or []) + verifier_receipt_acceptance_gates = list( + handoff.get("verifier_receipt_acceptance_gates") or [] + ) + handoff_fields = list( + handoff.get("verifier_receipt_evidence_handoff_fields") or [] + ) + handoff_acceptance_gates = list( + handoff.get("verifier_receipt_handoff_acceptance_gates") or [] + ) + required_external_evidence = list(handoff.get("required_external_receipt_evidence") or []) + external_acceptance_gates = list(handoff.get("external_receipt_acceptance_gates") or []) + operator_secret_boundary = ( + handoff.get("operator_held_secret_boundary_contract") or {} + ) + authorization_evidence_execution_fields = [ + "execution_preflight_id", + "source_verifier_receipt_closeout_id", + "source_verifier_receipt_evidence_handoff_id", + "source_detached_verification_evidence_validation_id", + "source_final_signable_request_package_id", + "verifier_receipt_id_reference", + "external_receipt_id_reference", + "payload_sha256", + "receipt_sha256", + "verifier_receipt_sha256", + "detached_signature_verification_status", + "post_apply_verifier_endpoint", + ] + authorization_evidence_execution_acceptance_gates = [ + "verifier_receipt_closeout_ready", + "verifier_receipt_evidence_handoff_ready", + "source_chain_ids_match", + "production_truth_matches_preflight_run", + "payload_hash_matches_final_signable_request_package", + "receipt_hash_is_present_and_nonempty", + "verifier_receipt_hash_is_present_and_nonempty", + "detached_signature_verification_status_passed", + "post_apply_verifier_still_required", + "no_secret_signature_or_database_write_in_preflight", + ] + side_effect_free = ( + int(summary.get("writes_script_count") or 0) == 0 + and int(summary.get("writes_artifact_count") or 0) == 0 + and int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_migration_count") or 0) == 0 + and int(summary.get("executes_endpoint_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and int(summary.get("signs_database_apply_authorization_count") or 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_script") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("signs_database_apply_authorization") is False + and safety.get("performs_detached_signature_verification") is False + and safety.get("persists_verifier_receipt") is False + and handoff.get("detached_signature_verification_performed") is False + and handoff.get("verifier_receipt_persisted") is False + 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("secret_material_included") is False + and handoff.get("secret_material_required_in_preview") is False + and handoff.get("accepts_plaintext_secret") is False + and handoff.get("reads_secret_in_preview") is False + and handoff.get("executes_shell_in_preview") is False + and handoff.get("executes_sql_in_preview") is False + and handoff.get("writes_database_in_preview") is False + ) + checks = [ + _authorization_evidence_execution_preflight_check( + "verifier_receipt_closeout_ready", + closeout.get("result") == "DB_APPLY_AUTHORIZATION_VERIFIER_RECEIPT_CLOSEOUT_READY" + and future_closeout.get("ready_for_future_verifier_receipt_closeout") is True + and handoff.get("ready_for_future_verifier_receipt_evidence_handoff") is True, + { + "result": closeout.get("result"), + "ready_for_future_verifier_receipt_closeout": future_closeout.get( + "ready_for_future_verifier_receipt_closeout" + ), + "ready_for_future_verifier_receipt_evidence_handoff": handoff.get( + "ready_for_future_verifier_receipt_evidence_handoff" + ), + }, + "wait_for_verifier_receipt_closeout", + ), + _authorization_evidence_execution_preflight_check( + "verifier_receipt_evidence_handoff_complete", + handoff.get("authorization_material_type") == "verifier_receipt_evidence_handoff" + and bool(handoff.get("handoff_id")) + and bool(handoff.get("source_detached_verification_evidence_validation_id")) + and bool(handoff.get("source_final_signable_request_package_id")) + and handoff.get("ready_for_database_apply_now") is False + and handoff.get("issues_database_apply_authorization") is False + and handoff.get("signs_database_apply_authorization") is False, + { + "handoff_id": handoff.get("handoff_id"), + "authorization_material_type": handoff.get("authorization_material_type"), + "source_final_signable_request_package_id": handoff.get( + "source_final_signable_request_package_id" + ), + }, + "wait_for_verifier_receipt_evidence_handoff", + ), + _authorization_evidence_execution_preflight_check( + "source_chain_ids_present", + bool(future_closeout.get("closeout_id")) + and bool(future_closeout.get("source_detached_verification_evidence_validation_id")) + and bool(future_closeout.get("source_verifier_receipt_closeout_boundary_id")) + and bool(future_closeout.get("source_signed_receipt_evidence_intake_id")) + and bool(handoff.get("source_final_signable_request_package_id")), + { + "closeout_id": future_closeout.get("closeout_id"), + "source_detached_verification_evidence_validation_id": future_closeout.get( + "source_detached_verification_evidence_validation_id" + ), + "source_final_signable_request_package_id": handoff.get( + "source_final_signable_request_package_id" + ), + }, + "wait_for_source_authorization_chain_ids", + ), + _authorization_evidence_execution_preflight_check( + "verifier_receipt_handoff_contract_carried_forward", + len(verifier_receipt_fields) == 12 + and len(verifier_receipt_acceptance_gates) == 10 + and len(handoff_fields) == 12 + and len(handoff_acceptance_gates) == 10 + and int(summary.get("verifier_receipt_field_count") or 0) == 12 + and int(summary.get("verifier_receipt_handoff_acceptance_gate_count") or 0) + == 10, + { + "verifier_receipt_field_count": len(verifier_receipt_fields), + "verifier_receipt_acceptance_gate_count": len( + verifier_receipt_acceptance_gates + ), + "verifier_receipt_evidence_handoff_field_count": len(handoff_fields), + "verifier_receipt_handoff_acceptance_gate_count": len( + handoff_acceptance_gates + ), + }, + "wait_for_verifier_receipt_handoff_contract", + ), + _authorization_evidence_execution_preflight_check( + "authorization_evidence_execution_preflight_contract_complete", + len(authorization_evidence_execution_fields) == 12 + and len(authorization_evidence_execution_acceptance_gates) == 10 + and "verifier_receipt_sha256" in authorization_evidence_execution_fields + and "no_secret_signature_or_database_write_in_preflight" + in authorization_evidence_execution_acceptance_gates, + { + "authorization_evidence_execution_field_count": len( + authorization_evidence_execution_fields + ), + "authorization_evidence_execution_acceptance_gate_count": len( + authorization_evidence_execution_acceptance_gates + ), + }, + "wait_for_authorization_evidence_execution_preflight_contract", + ), + _authorization_evidence_execution_preflight_check( + "external_receipt_contract_carried_forward", + len(required_external_evidence) == 10 + and len(external_acceptance_gates) == 8 + and int(summary.get("required_external_receipt_evidence_count") or 0) == 10 + and int(summary.get("external_receipt_acceptance_gate_count") or 0) == 8, + { + "required_external_receipt_evidence_count": len(required_external_evidence), + "external_receipt_acceptance_gate_count": len(external_acceptance_gates), + "summary_required_external_receipt_evidence_count": summary.get( + "required_external_receipt_evidence_count", 0 + ), + "summary_external_receipt_acceptance_gate_count": summary.get( + "external_receipt_acceptance_gate_count", 0 + ), + }, + "wait_for_external_receipt_contract", + ), + _authorization_evidence_execution_preflight_check( + "secret_signed_material_and_execution_boundary_enforced", + handoff.get("detached_signature_verification_performed") is False + and handoff.get("verifier_receipt_persisted") is False + 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("secret_material_included") is False + and handoff.get("accepts_plaintext_secret") is False + and operator_secret_boundary.get("secret_reference_mode") + == "external_runtime_reference_only" + and operator_secret_boundary.get("accepts_plaintext_secret") is False, + { + "detached_signature_verification_performed": handoff.get( + "detached_signature_verification_performed" + ), + "verifier_receipt_persisted": handoff.get("verifier_receipt_persisted"), + "signature_material_included": handoff.get("signature_material_included"), + "secret_material_included": handoff.get("secret_material_included"), + "accepts_plaintext_secret": handoff.get("accepts_plaintext_secret"), + }, + "abort_on_secret_signed_material_or_execution_boundary_violation", + ), + _authorization_evidence_execution_preflight_check( + "same_run_production_truth_required", + handoff.get("requires_fresh_production_truth_in_same_run") is True + and int(summary.get("same_run_truth_required_count") or 0) == 1, + { + "requires_fresh_production_truth_in_same_run": handoff.get( + "requires_fresh_production_truth_in_same_run" + ), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + }, + "require_same_run_production_truth", + ), + _authorization_evidence_execution_preflight_check( + "post_apply_verifier_and_hash_lock_required", + handoff.get("requires_post_apply_verifier") is True + and bool(handoff.get("post_apply_verifier_endpoint")) + and int(summary.get("post_apply_verifier_required_count") or 0) == 1 + and handoff.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and handoff.get("hash_matches") is True + and bool(handoff.get("expected_sha256")) + and handoff.get("expected_sha256") == handoff.get("actual_sha256"), + { + "requires_post_apply_verifier": handoff.get("requires_post_apply_verifier"), + "post_apply_verifier_endpoint": handoff.get("post_apply_verifier_endpoint"), + "target_file": handoff.get("target_file"), + "hash_matches": handoff.get("hash_matches"), + }, + "require_post_apply_verifier_and_hash_lock", + ), + _authorization_evidence_execution_preflight_check( + "closeout_contract_blocks_database_apply", + closeout_contract.get( + "permits_future_database_apply_authorization_verifier_handoff" + ) + is True + and closeout_contract.get("issues_database_apply_authorization") is False + and closeout_contract.get("ready_for_database_apply_now") is False + and closeout_contract.get("signs_database_apply_authorization") is False + and closeout_contract.get("persists_verifier_receipt") is False + and closeout_contract.get("performs_detached_signature_verification") is False, + { + "permits_future_database_apply_authorization_verifier_handoff": ( + closeout_contract.get( + "permits_future_database_apply_authorization_verifier_handoff" + ) + ), + "ready_for_database_apply_now": closeout_contract.get( + "ready_for_database_apply_now" + ), + "persists_verifier_receipt": closeout_contract.get( + "persists_verifier_receipt" + ), + }, + "abort_if_closeout_contract_authorizes_database_apply", + ), + _authorization_evidence_execution_preflight_check( + "preview_has_no_side_effects_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + "performs_detached_signature_verification": safety.get( + "performs_detached_signature_verification" + ), + "persists_verifier_receipt": safety.get("persists_verifier_receipt"), + }, + "abort_on_preview_side_effect_execution_or_signing", + ), + _authorization_evidence_execution_preflight_check( + "manual_review_regression_absent", + int(summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY) or 0) == 0 + and future_closeout.get("manual_review_mode") == "exception_only", + { + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + "manual_review_mode": future_closeout.get("manual_review_mode"), + }, + "route_failed_verifier_to_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + preflight_ready = not waiting_checks + preflight_status = ( + "DB_APPLY_AUTHORIZATION_EVIDENCE_EXECUTION_PREFLIGHT_READY" + if preflight_ready + else "WAITING_FOR_DB_APPLY_AUTHORIZATION_VERIFIER_RECEIPT_CLOSEOUT" + ) + future_database_apply_authorization_verifier_handoff = { + "preflight_id": preflight_id, + "source_verifier_receipt_closeout_id": future_closeout.get("closeout_id"), + "source_verifier_receipt_evidence_handoff_id": handoff.get("handoff_id"), + "source_detached_verification_evidence_validation_id": handoff.get( + "source_detached_verification_evidence_validation_id" + ), + "source_final_signable_request_package_id": handoff.get( + "source_final_signable_request_package_id" + ), + "status": preflight_status, + "ready_for_future_database_apply_authorization_verifier_handoff": ( + preflight_ready + ), + "can_enter_future_authorization_evidence_execution_closeout": preflight_ready, + "authorization_evidence_execution_preflight_ready": preflight_ready, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "detached_signature_verification_performed": False, + "verifier_receipt_persisted": 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, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + authorization_evidence_execution_preflight = { + "preflight_id": preflight_id, + "authorization_material_type": "authorization_evidence_execution_preflight", + "source_verifier_receipt_closeout_id": future_closeout.get("closeout_id"), + "source_verifier_receipt_evidence_handoff_id": handoff.get("handoff_id"), + "source_detached_verification_evidence_validation_id": handoff.get( + "source_detached_verification_evidence_validation_id" + ), + "source_signed_receipt_evidence_intake_id": handoff.get( + "source_signed_receipt_evidence_intake_id" + ), + "source_final_signable_request_package_id": handoff.get( + "source_final_signable_request_package_id" + ), + "status": preflight_status, + "ready_for_future_authorization_evidence_execution_preflight": ( + preflight_ready + ), + "authorization_evidence_execution_fields": authorization_evidence_execution_fields, + "authorization_evidence_execution_field_count": len( + authorization_evidence_execution_fields + ), + "authorization_evidence_execution_acceptance_gates": ( + authorization_evidence_execution_acceptance_gates + ), + "authorization_evidence_execution_acceptance_gate_count": len( + authorization_evidence_execution_acceptance_gates + ), + "verifier_receipt_fields": verifier_receipt_fields, + "verifier_receipt_field_count": len(verifier_receipt_fields), + "verifier_receipt_acceptance_gates": verifier_receipt_acceptance_gates, + "verifier_receipt_acceptance_gate_count": len(verifier_receipt_acceptance_gates), + "verifier_receipt_evidence_handoff_fields": handoff_fields, + "verifier_receipt_evidence_handoff_field_count": len(handoff_fields), + "verifier_receipt_handoff_acceptance_gates": handoff_acceptance_gates, + "verifier_receipt_handoff_acceptance_gate_count": len(handoff_acceptance_gates), + "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, + "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, + "executes_authorization_evidence": False, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "operator_held_secret_boundary_contract": operator_secret_boundary, + "target_file": handoff.get("target_file"), + "expected_sha256": handoff.get("expected_sha256"), + "actual_sha256": handoff.get("actual_sha256"), + "hash_matches": handoff.get("hash_matches"), + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "post_apply_verifier_endpoint": handoff.get("post_apply_verifier_endpoint"), + } + authorization_evidence_execution_preflight_contract = { + "mode": "authorization_evidence_execution_preflight_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-evidence-execution-preflight" + ), + "source_verifier_receipt_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-verifier-receipt-closeout" + ), + "machine_verifiable": True, + "permits_future_authorization_evidence_execution_closeout": preflight_ready, + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": False, + "executes_authorization_evidence": 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", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_AUTHORIZATION_EVIDENCE_EXECUTION_PREFLIGHT_POLICY, + "result": preflight_status, + "success": bool(closeout.get("success")), + "generated_at": closeout.get("generated_at"), + "source_policy": closeout.get("policy"), + "stats": closeout.get("stats") or {}, + "summary": { + "authorization_evidence_execution_preflight_ready_count": ( + 1 if preflight_ready else 0 + ), + "authorization_evidence_execution_preflight_check_count": len(checks), + "authorization_evidence_execution_preflight_pass_count": passed_count, + "authorization_evidence_execution_preflight_waiting_count": len( + waiting_checks + ), + "authorization_verifier_receipt_closeout_ready_count": summary.get( + "authorization_verifier_receipt_closeout_ready_count", 0 + ), + "verifier_receipt_closeout_check_count": summary.get( + "verifier_receipt_closeout_check_count", 0 + ), + "authorization_detached_verification_evidence_validation_ready_count": ( + summary.get( + "authorization_detached_verification_evidence_validation_ready_count", + 0, + ) + ), + "detached_verification_evidence_validation_check_count": summary.get( + "detached_verification_evidence_validation_check_count", 0 + ), + "verifier_receipt_evidence_handoff_count": summary.get( + "verifier_receipt_evidence_handoff_count", 0 + ), + "authorization_evidence_execution_preflight_count": 1, + "authorization_evidence_execution_field_count": len( + authorization_evidence_execution_fields + ), + "authorization_evidence_execution_acceptance_gate_count": len( + authorization_evidence_execution_acceptance_gates + ), + "verifier_receipt_field_count": summary.get( + "verifier_receipt_field_count", 0 + ), + "verifier_receipt_acceptance_gate_count": summary.get( + "verifier_receipt_acceptance_gate_count", 0 + ), + "verifier_receipt_evidence_handoff_field_count": summary.get( + "verifier_receipt_evidence_handoff_field_count", 0 + ), + "verifier_receipt_handoff_acceptance_gate_count": summary.get( + "verifier_receipt_handoff_acceptance_gate_count", 0 + ), + "required_external_receipt_evidence_count": summary.get( + "required_external_receipt_evidence_count", 0 + ), + "external_receipt_acceptance_gate_count": summary.get( + "external_receipt_acceptance_gate_count", 0 + ), + "operator_held_secret_boundary_count": summary.get( + "operator_held_secret_boundary_count", 0 + ), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "future_database_apply_authorization_verifier_handoff": ( + future_database_apply_authorization_verifier_handoff + ), + "authorization_evidence_execution_preflight": ( + authorization_evidence_execution_preflight + ), + "authorization_evidence_execution_preflight_contract": ( + authorization_evidence_execution_preflight_contract + ), + "authorization_evidence_execution_preflight_checks": checks, + "source_verifier_receipt_closeout_summary": summary, + "source_verifier_receipt_closeout_contract": closeout_contract, + "source_verifier_receipt_evidence_handoff": handoff, + "safety": { + "read_only_db_apply_authorization_evidence_execution_preflight": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this preflight for a future authorization evidence execution closeout lane.", + "Keep signed receipt body, signature material, secret values, shell execution, SQL, and DB writes out of this preflight.", + "A later execution closeout may validate external evidence receipt readiness; this preflight still does not authorize DB apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_authorization_evidence_execution_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out authorization evidence execution readiness without executing it.""" + preflight = build_pchome_auto_policy_db_apply_authorization_evidence_execution_preflight( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + verifier_handoff = ( + preflight.get("future_database_apply_authorization_verifier_handoff") or {} + ) + execution_preflight = ( + preflight.get("authorization_evidence_execution_preflight") or {} + ) + preflight_contract = ( + preflight.get("authorization_evidence_execution_preflight_contract") or {} + ) + summary = preflight.get("summary") or {} + safety = preflight.get("safety") or {} + closeout_id = _db_apply_authorization_evidence_execution_closeout_id(preflight) + execution_fields = list( + execution_preflight.get("authorization_evidence_execution_fields") or [] + ) + execution_acceptance_gates = list( + execution_preflight.get("authorization_evidence_execution_acceptance_gates") or [] + ) + verifier_receipt_fields = list( + execution_preflight.get("verifier_receipt_fields") or [] + ) + verifier_receipt_acceptance_gates = list( + execution_preflight.get("verifier_receipt_acceptance_gates") or [] + ) + handoff_fields = list( + execution_preflight.get("verifier_receipt_evidence_handoff_fields") or [] + ) + handoff_acceptance_gates = list( + execution_preflight.get("verifier_receipt_handoff_acceptance_gates") or [] + ) + closeout_fields = [ + "closeout_id", + "source_authorization_evidence_execution_preflight_id", + "source_verifier_receipt_closeout_id", + "source_verifier_receipt_evidence_handoff_id", + "source_final_signable_request_package_id", + "verifier_receipt_id_reference", + "external_receipt_id_reference", + "payload_sha256", + "receipt_sha256", + "verifier_receipt_sha256", + "final_verifier_gate_endpoint", + "post_apply_verifier_endpoint", + ] + closeout_acceptance_gates = [ + "authorization_evidence_execution_preflight_ready", + "final_verifier_handoff_ready", + "source_chain_ids_match", + "production_truth_same_run_required", + "payload_receipt_verifier_hashes_present", + "final_signable_request_package_hash_locked", + "post_apply_verifier_required", + "no_secret_signature_or_execution_closeout", + "no_database_apply_authorized_by_closeout", + "exception_only_failure_routing", + ] + side_effect_free = ( + int(summary.get("writes_script_count") or 0) == 0 + and int(summary.get("writes_artifact_count") or 0) == 0 + and int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_migration_count") or 0) == 0 + and int(summary.get("executes_endpoint_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and int(summary.get("signs_database_apply_authorization_count") or 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_script") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("signs_database_apply_authorization") is False + and safety.get("performs_detached_signature_verification") is False + and safety.get("persists_verifier_receipt") is False + and safety.get("executes_authorization_evidence") is False + and execution_preflight.get("detached_signature_verification_performed") is False + and execution_preflight.get("verifier_receipt_persisted") is False + and execution_preflight.get("external_signed_authorization_receipt_included") is False + and execution_preflight.get("signed_authorization_receipt_included") is False + and execution_preflight.get("signature_material_included") is False + and execution_preflight.get("secret_material_included") is False + and execution_preflight.get("secret_material_required_in_preview") is False + and execution_preflight.get("accepts_plaintext_secret") is False + and execution_preflight.get("reads_secret_in_preview") is False + and execution_preflight.get("executes_shell_in_preview") is False + and execution_preflight.get("executes_sql_in_preview") is False + and execution_preflight.get("writes_database_in_preview") is False + and execution_preflight.get("executes_authorization_evidence") is False + ) + hashes_present = ( + bool(execution_preflight.get("expected_sha256")) + and bool(execution_preflight.get("actual_sha256")) + and execution_preflight.get("hash_matches") is True + and "payload_sha256" in execution_fields + and "receipt_sha256" in execution_fields + and "verifier_receipt_sha256" in execution_fields + ) + checks = [ + _authorization_evidence_execution_closeout_check( + "authorization_evidence_execution_preflight_ready", + preflight.get("result") + == "DB_APPLY_AUTHORIZATION_EVIDENCE_EXECUTION_PREFLIGHT_READY" + and execution_preflight.get( + "ready_for_future_authorization_evidence_execution_preflight" + ) + is True, + { + "result": preflight.get("result"), + "ready_for_future_authorization_evidence_execution_preflight": ( + execution_preflight.get( + "ready_for_future_authorization_evidence_execution_preflight" + ) + ), + }, + "wait_for_authorization_evidence_execution_preflight", + ), + _authorization_evidence_execution_closeout_check( + "final_verifier_handoff_ready", + verifier_handoff.get( + "ready_for_future_database_apply_authorization_verifier_handoff" + ) + is True + and verifier_handoff.get( + "can_enter_future_authorization_evidence_execution_closeout" + ) + is True + and verifier_handoff.get("authorization_evidence_execution_preflight_ready") + is True, + { + "ready_for_future_database_apply_authorization_verifier_handoff": ( + verifier_handoff.get( + "ready_for_future_database_apply_authorization_verifier_handoff" + ) + ), + "can_enter_future_authorization_evidence_execution_closeout": ( + verifier_handoff.get( + "can_enter_future_authorization_evidence_execution_closeout" + ) + ), + }, + "wait_for_database_apply_authorization_verifier_handoff", + ), + _authorization_evidence_execution_closeout_check( + "authorization_evidence_execution_preflight_package_complete", + execution_preflight.get("authorization_material_type") + == "authorization_evidence_execution_preflight" + and bool(execution_preflight.get("preflight_id")) + and len(execution_fields) == 12 + and len(execution_acceptance_gates) == 10 + and execution_preflight.get("ready_for_database_apply_now") is False + and execution_preflight.get("issues_database_apply_authorization") is False + and execution_preflight.get("signs_database_apply_authorization") is False, + { + "preflight_id": execution_preflight.get("preflight_id"), + "authorization_evidence_execution_field_count": len(execution_fields), + "authorization_evidence_execution_acceptance_gate_count": len( + execution_acceptance_gates + ), + }, + "wait_for_authorization_evidence_execution_preflight_package", + ), + _authorization_evidence_execution_closeout_check( + "source_chain_ids_present", + bool(execution_preflight.get("source_verifier_receipt_closeout_id")) + and bool(execution_preflight.get("source_verifier_receipt_evidence_handoff_id")) + and bool( + execution_preflight.get( + "source_detached_verification_evidence_validation_id" + ) + ) + and bool(execution_preflight.get("source_signed_receipt_evidence_intake_id")) + and bool(execution_preflight.get("source_final_signable_request_package_id")), + { + "source_verifier_receipt_closeout_id": execution_preflight.get( + "source_verifier_receipt_closeout_id" + ), + "source_verifier_receipt_evidence_handoff_id": ( + execution_preflight.get("source_verifier_receipt_evidence_handoff_id") + ), + "source_final_signable_request_package_id": execution_preflight.get( + "source_final_signable_request_package_id" + ), + }, + "wait_for_source_authorization_chain_ids", + ), + _authorization_evidence_execution_closeout_check( + "authorization_evidence_execution_closeout_contract_complete", + len(closeout_fields) == 12 + and len(closeout_acceptance_gates) == 10 + and "final_verifier_gate_endpoint" in closeout_fields + and "no_database_apply_authorized_by_closeout" + in closeout_acceptance_gates, + { + "authorization_evidence_execution_closeout_field_count": len( + closeout_fields + ), + "authorization_evidence_execution_closeout_acceptance_gate_count": len( + closeout_acceptance_gates + ), + }, + "wait_for_authorization_evidence_execution_closeout_contract", + ), + _authorization_evidence_execution_closeout_check( + "verifier_receipt_handoff_contract_carried_forward", + len(verifier_receipt_fields) == 12 + and len(verifier_receipt_acceptance_gates) == 10 + and len(handoff_fields) == 12 + and len(handoff_acceptance_gates) == 10 + and int(summary.get("verifier_receipt_field_count") or 0) == 12 + and int(summary.get("verifier_receipt_handoff_acceptance_gate_count") or 0) + == 10, + { + "verifier_receipt_field_count": len(verifier_receipt_fields), + "verifier_receipt_acceptance_gate_count": len( + verifier_receipt_acceptance_gates + ), + "verifier_receipt_evidence_handoff_field_count": len(handoff_fields), + "verifier_receipt_handoff_acceptance_gate_count": len( + handoff_acceptance_gates + ), + }, + "wait_for_verifier_receipt_handoff_contract", + ), + _authorization_evidence_execution_closeout_check( + "verifier_hash_and_receipt_chain_locked", + hashes_present, + { + "hash_matches": execution_preflight.get("hash_matches"), + "expected_sha256_present": bool(execution_preflight.get("expected_sha256")), + "actual_sha256_present": bool(execution_preflight.get("actual_sha256")), + "payload_sha256_field_present": "payload_sha256" in execution_fields, + "receipt_sha256_field_present": "receipt_sha256" in execution_fields, + "verifier_receipt_sha256_field_present": ( + "verifier_receipt_sha256" in execution_fields + ), + }, + "require_payload_receipt_and_verifier_hash_lock", + ), + _authorization_evidence_execution_closeout_check( + "same_run_production_truth_and_post_apply_verifier_required", + execution_preflight.get("requires_fresh_production_truth_in_same_run") + is True + and execution_preflight.get("requires_post_apply_verifier") is True + and bool(execution_preflight.get("post_apply_verifier_endpoint")) + and int(summary.get("same_run_truth_required_count") or 0) == 1 + and int(summary.get("post_apply_verifier_required_count") or 0) == 1, + { + "requires_fresh_production_truth_in_same_run": ( + execution_preflight.get("requires_fresh_production_truth_in_same_run") + ), + "requires_post_apply_verifier": execution_preflight.get( + "requires_post_apply_verifier" + ), + "post_apply_verifier_endpoint": execution_preflight.get( + "post_apply_verifier_endpoint" + ), + }, + "require_same_run_production_truth_and_post_apply_verifier", + ), + _authorization_evidence_execution_closeout_check( + "secret_signed_material_and_execution_boundary_enforced", + execution_preflight.get("detached_signature_verification_performed") is False + and execution_preflight.get("verifier_receipt_persisted") is False + and execution_preflight.get("external_signed_authorization_receipt_included") + is False + and execution_preflight.get("signed_authorization_receipt_included") is False + and execution_preflight.get("signature_material_included") is False + and execution_preflight.get("secret_material_included") is False + and execution_preflight.get("accepts_plaintext_secret") is False + and execution_preflight.get("executes_authorization_evidence") is False + and verifier_handoff.get("executes_authorization_evidence") is False, + { + "detached_signature_verification_performed": execution_preflight.get( + "detached_signature_verification_performed" + ), + "verifier_receipt_persisted": execution_preflight.get( + "verifier_receipt_persisted" + ), + "signature_material_included": execution_preflight.get( + "signature_material_included" + ), + "secret_material_included": execution_preflight.get( + "secret_material_included" + ), + "executes_authorization_evidence": execution_preflight.get( + "executes_authorization_evidence" + ), + }, + "abort_on_secret_signed_material_execution_or_persistence", + ), + _authorization_evidence_execution_closeout_check( + "preflight_contract_blocks_database_apply", + preflight_contract.get( + "permits_future_authorization_evidence_execution_closeout" + ) + is True + and preflight_contract.get("issues_database_apply_authorization") is False + and preflight_contract.get("ready_for_database_apply_now") is False + and preflight_contract.get("signs_database_apply_authorization") is False + and preflight_contract.get("writes_database") is False + and preflight_contract.get("executes_authorization_evidence") is False, + { + "permits_future_authorization_evidence_execution_closeout": ( + preflight_contract.get( + "permits_future_authorization_evidence_execution_closeout" + ) + ), + "ready_for_database_apply_now": preflight_contract.get( + "ready_for_database_apply_now" + ), + "executes_authorization_evidence": preflight_contract.get( + "executes_authorization_evidence" + ), + }, + "abort_if_preflight_contract_authorizes_database_apply", + ), + _authorization_evidence_execution_closeout_check( + "preview_has_no_side_effects_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + "executes_authorization_evidence": safety.get( + "executes_authorization_evidence" + ), + }, + "abort_on_preview_side_effect_execution_or_signing", + ), + _authorization_evidence_execution_closeout_check( + "manual_review_regression_absent", + int(summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY) or 0) == 0 + and verifier_handoff.get("manual_review_mode") == "exception_only", + { + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get( + LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0 + ), + "manual_review_mode": verifier_handoff.get("manual_review_mode"), + }, + "route_failed_final_verifier_to_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_AUTHORIZATION_EVIDENCE_EXECUTION_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_AUTHORIZATION_EVIDENCE_EXECUTION_PREFLIGHT" + ) + final_verifier_gate_endpoint = ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-authorization-evidence-execution-closeout" + ) + future_database_apply_authorization_final_verifier_gate = { + "final_verifier_gate_id": closeout_id, + "source_authorization_evidence_execution_preflight_id": ( + execution_preflight.get("preflight_id") + ), + "source_verifier_receipt_closeout_id": execution_preflight.get( + "source_verifier_receipt_closeout_id" + ), + "source_verifier_receipt_evidence_handoff_id": execution_preflight.get( + "source_verifier_receipt_evidence_handoff_id" + ), + "source_final_signable_request_package_id": execution_preflight.get( + "source_final_signable_request_package_id" + ), + "status": closeout_status, + "ready_for_future_database_apply_authorization_final_verifier_gate": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_apply_final_preflight": ( + closeout_ready + ), + "authorization_evidence_execution_closeout_ready": closeout_ready, + "final_verifier_gate_ready": closeout_ready, + "final_verifier_gate_executed": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "detached_signature_verification_performed": False, + "verifier_receipt_persisted": 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, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + authorization_evidence_execution_closeout = { + "closeout_id": closeout_id, + "authorization_material_type": "authorization_evidence_execution_closeout", + "source_authorization_evidence_execution_preflight_id": ( + execution_preflight.get("preflight_id") + ), + "source_verifier_receipt_closeout_id": execution_preflight.get( + "source_verifier_receipt_closeout_id" + ), + "source_verifier_receipt_evidence_handoff_id": execution_preflight.get( + "source_verifier_receipt_evidence_handoff_id" + ), + "source_detached_verification_evidence_validation_id": ( + execution_preflight.get( + "source_detached_verification_evidence_validation_id" + ) + ), + "source_signed_receipt_evidence_intake_id": execution_preflight.get( + "source_signed_receipt_evidence_intake_id" + ), + "source_final_signable_request_package_id": execution_preflight.get( + "source_final_signable_request_package_id" + ), + "status": closeout_status, + "ready_for_future_authorization_evidence_execution_closeout": ( + closeout_ready + ), + "final_verifier_gate_endpoint": final_verifier_gate_endpoint, + "authorization_evidence_execution_closeout_fields": closeout_fields, + "authorization_evidence_execution_closeout_field_count": len(closeout_fields), + "authorization_evidence_execution_closeout_acceptance_gates": ( + closeout_acceptance_gates + ), + "authorization_evidence_execution_closeout_acceptance_gate_count": len( + closeout_acceptance_gates + ), + "authorization_evidence_execution_fields": execution_fields, + "authorization_evidence_execution_field_count": len(execution_fields), + "authorization_evidence_execution_acceptance_gates": ( + execution_acceptance_gates + ), + "authorization_evidence_execution_acceptance_gate_count": len( + execution_acceptance_gates + ), + "verifier_receipt_fields": verifier_receipt_fields, + "verifier_receipt_field_count": len(verifier_receipt_fields), + "verifier_receipt_acceptance_gates": verifier_receipt_acceptance_gates, + "verifier_receipt_acceptance_gate_count": len( + verifier_receipt_acceptance_gates + ), + "verifier_receipt_evidence_handoff_fields": handoff_fields, + "verifier_receipt_evidence_handoff_field_count": len(handoff_fields), + "verifier_receipt_handoff_acceptance_gates": handoff_acceptance_gates, + "verifier_receipt_handoff_acceptance_gate_count": len( + handoff_acceptance_gates + ), + "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, + "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_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "target_file": execution_preflight.get("target_file"), + "expected_sha256": execution_preflight.get("expected_sha256"), + "actual_sha256": execution_preflight.get("actual_sha256"), + "hash_matches": execution_preflight.get("hash_matches"), + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "post_apply_verifier_endpoint": execution_preflight.get( + "post_apply_verifier_endpoint" + ), + } + authorization_evidence_execution_closeout_contract = { + "mode": "authorization_evidence_execution_closeout_final_verifier_gate_only", + "source_endpoint": final_verifier_gate_endpoint, + "source_authorization_evidence_execution_preflight_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-authorization-evidence-execution-preflight" + ), + "machine_verifiable": True, + "permits_future_database_apply_authorization_final_verifier_gate": closeout_ready, + "permits_future_database_apply_controlled_apply_final_preflight": ( + closeout_ready + ), + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": 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", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_AUTHORIZATION_EVIDENCE_EXECUTION_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(preflight.get("success")), + "generated_at": preflight.get("generated_at"), + "source_policy": preflight.get("policy"), + "stats": preflight.get("stats") or {}, + "summary": { + "authorization_evidence_execution_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "authorization_evidence_execution_closeout_check_count": len(checks), + "authorization_evidence_execution_closeout_pass_count": passed_count, + "authorization_evidence_execution_closeout_waiting_count": len( + waiting_checks + ), + "authorization_evidence_execution_preflight_ready_count": summary.get( + "authorization_evidence_execution_preflight_ready_count", 0 + ), + "authorization_evidence_execution_preflight_check_count": summary.get( + "authorization_evidence_execution_preflight_check_count", 0 + ), + "authorization_verifier_receipt_closeout_ready_count": summary.get( + "authorization_verifier_receipt_closeout_ready_count", 0 + ), + "verifier_receipt_closeout_check_count": summary.get( + "verifier_receipt_closeout_check_count", 0 + ), + "authorization_detached_verification_evidence_validation_ready_count": ( + summary.get( + "authorization_detached_verification_evidence_validation_ready_count", + 0, + ) + ), + "detached_verification_evidence_validation_check_count": summary.get( + "detached_verification_evidence_validation_check_count", 0 + ), + "verifier_receipt_evidence_handoff_count": summary.get( + "verifier_receipt_evidence_handoff_count", 0 + ), + "authorization_evidence_execution_preflight_count": summary.get( + "authorization_evidence_execution_preflight_count", 0 + ), + "authorization_evidence_execution_closeout_count": 1, + "database_apply_final_verifier_gate_count": 1, + "database_apply_authorization_final_verifier_gate_ready_count": ( + 1 if closeout_ready else 0 + ), + "authorization_evidence_execution_closeout_field_count": len( + closeout_fields + ), + "authorization_evidence_execution_closeout_acceptance_gate_count": len( + closeout_acceptance_gates + ), + "authorization_evidence_execution_field_count": summary.get( + "authorization_evidence_execution_field_count", 0 + ), + "authorization_evidence_execution_acceptance_gate_count": summary.get( + "authorization_evidence_execution_acceptance_gate_count", 0 + ), + "verifier_receipt_field_count": summary.get( + "verifier_receipt_field_count", 0 + ), + "verifier_receipt_acceptance_gate_count": summary.get( + "verifier_receipt_acceptance_gate_count", 0 + ), + "verifier_receipt_evidence_handoff_field_count": summary.get( + "verifier_receipt_evidence_handoff_field_count", 0 + ), + "verifier_receipt_handoff_acceptance_gate_count": summary.get( + "verifier_receipt_handoff_acceptance_gate_count", 0 + ), + "required_external_receipt_evidence_count": summary.get( + "required_external_receipt_evidence_count", 0 + ), + "external_receipt_acceptance_gate_count": summary.get( + "external_receipt_acceptance_gate_count", 0 + ), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + }, + "future_database_apply_authorization_final_verifier_gate": ( + future_database_apply_authorization_final_verifier_gate + ), + "authorization_evidence_execution_closeout": ( + authorization_evidence_execution_closeout + ), + "authorization_evidence_execution_closeout_contract": ( + authorization_evidence_execution_closeout_contract + ), + "authorization_evidence_execution_closeout_checks": checks, + "source_authorization_evidence_execution_preflight_summary": summary, + "source_authorization_evidence_execution_preflight_contract": ( + preflight_contract + ), + "source_authorization_evidence_execution_preflight": execution_preflight, + "source_database_apply_authorization_verifier_handoff": verifier_handoff, + "safety": { + "read_only_db_apply_authorization_evidence_execution_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout as the final verifier gate input for a future controlled-apply final preflight.", + "Keep signed receipt body, signature material, secret values, endpoint execution, SQL, and DB writes out of this closeout.", + "A later controlled-apply final preflight may bind rollback and post-apply verification; this closeout still does not authorize DB apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_apply_final_preflight( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Bind rollback and verifier requirements before any controlled apply.""" + closeout = build_pchome_auto_policy_db_apply_authorization_evidence_execution_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + final_gate = ( + closeout.get("future_database_apply_authorization_final_verifier_gate") or {} + ) + evidence_closeout = closeout.get("authorization_evidence_execution_closeout") or {} + closeout_contract = ( + closeout.get("authorization_evidence_execution_closeout_contract") or {} + ) + summary = closeout.get("summary") or {} + safety = closeout.get("safety") or {} + preflight_id = _db_apply_controlled_apply_final_preflight_id(closeout) + controlled_apply_fields = [ + "controlled_apply_preflight_id", + "source_final_verifier_gate_id", + "source_authorization_evidence_execution_closeout_id", + "source_final_signable_request_package_id", + "target_migration_file", + "target_migration_sha256", + "rollback_binding_id", + "rollback_verifier_endpoint", + "post_apply_verifier_endpoint", + "same_run_production_truth_reference", + "dry_run_command_shape", + "abort_conditions", + ] + controlled_apply_acceptance_gates = [ + "final_verifier_gate_ready", + "source_chain_ids_match", + "target_migration_hash_locked", + "rollback_plan_bound", + "post_apply_verifier_bound", + "same_run_production_truth_required", + "dry_run_only_no_execution", + "no_secret_signature_or_database_apply", + "database_write_gate_remains_closed", + "exception_only_failure_routing", + ] + rollback_binding_fields = [ + "rollback_binding_id", + "target_migration_file", + "target_migration_sha256", + "rollback_strategy", + "rollback_verifier_endpoint", + "post_apply_verifier_endpoint", + "rollback_requires_same_run_truth", + "rollback_execution_authorized", + ] + post_apply_verifier_binding_fields = [ + "post_apply_verifier_binding_id", + "source_final_verifier_gate_id", + "post_apply_verifier_endpoint", + "expected_migration_sha256", + "same_run_production_truth_required", + "verifier_must_run_after_apply", + "verifier_execution_authorized_in_preview", + "database_apply_authorized", + ] + target_file = evidence_closeout.get("target_file") + expected_sha256 = evidence_closeout.get("expected_sha256") + actual_sha256 = evidence_closeout.get("actual_sha256") + target_hash_locked = ( + target_file == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(expected_sha256) + and bool(actual_sha256) + and expected_sha256 == actual_sha256 + and evidence_closeout.get("hash_matches") is True + ) + rollback_binding_id = f"{preflight_id}-rollback" + post_apply_verifier_binding_id = f"{preflight_id}-post-apply-verifier" + rollback_binding = { + "rollback_binding_id": rollback_binding_id, + "target_migration_file": target_file, + "target_migration_sha256": expected_sha256, + "rollback_strategy": "fail_closed_no_apply_without_post_apply_verifier", + "rollback_verifier_endpoint": evidence_closeout.get("post_apply_verifier_endpoint"), + "post_apply_verifier_endpoint": evidence_closeout.get("post_apply_verifier_endpoint"), + "rollback_requires_same_run_truth": True, + "rollback_execution_authorized": False, + "rollback_executes_sql": False, + "rollback_writes_database": False, + "rollback_reads_secret": False, + "rollback_binding_field_count": len(rollback_binding_fields), + "rollback_binding_fields": rollback_binding_fields, + } + post_apply_verifier_binding = { + "post_apply_verifier_binding_id": post_apply_verifier_binding_id, + "source_final_verifier_gate_id": final_gate.get("final_verifier_gate_id"), + "post_apply_verifier_endpoint": evidence_closeout.get("post_apply_verifier_endpoint"), + "expected_migration_sha256": expected_sha256, + "same_run_production_truth_required": True, + "verifier_must_run_after_apply": True, + "verifier_execution_authorized_in_preview": False, + "database_apply_authorized": False, + "post_apply_verifier_binding_field_count": len( + post_apply_verifier_binding_fields + ), + "post_apply_verifier_binding_fields": post_apply_verifier_binding_fields, + } + side_effect_free = ( + int(summary.get("writes_script_count") or 0) == 0 + and int(summary.get("writes_artifact_count") or 0) == 0 + and int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_migration_count") or 0) == 0 + and int(summary.get("executes_endpoint_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and int(summary.get("signs_database_apply_authorization_count") or 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_script") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("signs_database_apply_authorization") is False + and safety.get("performs_detached_signature_verification") is False + and safety.get("persists_verifier_receipt") is False + and safety.get("executes_authorization_evidence") is False + and safety.get("executes_database_apply") is False + and evidence_closeout.get("detached_signature_verification_performed") is False + and evidence_closeout.get("verifier_receipt_persisted") is False + and evidence_closeout.get("external_signed_authorization_receipt_included") + is False + and evidence_closeout.get("signed_authorization_receipt_included") is False + and evidence_closeout.get("signature_material_included") is False + and evidence_closeout.get("secret_material_included") is False + and evidence_closeout.get("secret_material_required_in_preview") is False + and evidence_closeout.get("accepts_plaintext_secret") is False + and evidence_closeout.get("reads_secret_in_preview") is False + and evidence_closeout.get("executes_shell_in_preview") is False + and evidence_closeout.get("executes_endpoint_in_preview") is False + and evidence_closeout.get("executes_sql_in_preview") is False + and evidence_closeout.get("writes_database_in_preview") is False + and evidence_closeout.get("executes_authorization_evidence") is False + and evidence_closeout.get("executes_database_apply") is False + ) + checks = [ + _controlled_apply_final_preflight_check( + "final_verifier_gate_ready", + closeout.get("result") + == "DB_APPLY_AUTHORIZATION_EVIDENCE_EXECUTION_CLOSEOUT_READY" + and final_gate.get( + "ready_for_future_database_apply_authorization_final_verifier_gate" + ) + is True + and final_gate.get( + "can_enter_future_database_apply_controlled_apply_final_preflight" + ) + is True + and evidence_closeout.get( + "ready_for_future_authorization_evidence_execution_closeout" + ) + is True, + { + "result": closeout.get("result"), + "ready_for_future_database_apply_authorization_final_verifier_gate": ( + final_gate.get( + "ready_for_future_database_apply_authorization_final_verifier_gate" + ) + ), + "can_enter_future_database_apply_controlled_apply_final_preflight": ( + final_gate.get( + "can_enter_future_database_apply_controlled_apply_final_preflight" + ) + ), + }, + "wait_for_database_apply_authorization_final_verifier_gate", + ), + _controlled_apply_final_preflight_check( + "authorization_evidence_execution_closeout_package_complete", + evidence_closeout.get("authorization_material_type") + == "authorization_evidence_execution_closeout" + and bool(evidence_closeout.get("closeout_id")) + and int( + evidence_closeout.get( + "authorization_evidence_execution_closeout_field_count" + ) + or 0 + ) + == 12 + and int( + evidence_closeout.get( + "authorization_evidence_execution_closeout_acceptance_gate_count" + ) + or 0 + ) + == 10 + and evidence_closeout.get("ready_for_database_apply_now") is False + and evidence_closeout.get("database_apply_authorized") is False, + { + "closeout_id": evidence_closeout.get("closeout_id"), + "authorization_evidence_execution_closeout_field_count": ( + evidence_closeout.get( + "authorization_evidence_execution_closeout_field_count" + ) + ), + "authorization_evidence_execution_closeout_acceptance_gate_count": ( + evidence_closeout.get( + "authorization_evidence_execution_closeout_acceptance_gate_count" + ) + ), + }, + "wait_for_authorization_evidence_execution_closeout_package", + ), + _controlled_apply_final_preflight_check( + "source_chain_ids_present", + bool(final_gate.get("final_verifier_gate_id")) + and bool(evidence_closeout.get("closeout_id")) + and bool(evidence_closeout.get("source_authorization_evidence_execution_preflight_id")) + and bool(evidence_closeout.get("source_verifier_receipt_closeout_id")) + and bool(evidence_closeout.get("source_verifier_receipt_evidence_handoff_id")) + and bool(evidence_closeout.get("source_final_signable_request_package_id")), + { + "final_verifier_gate_id": final_gate.get("final_verifier_gate_id"), + "closeout_id": evidence_closeout.get("closeout_id"), + "source_final_signable_request_package_id": evidence_closeout.get( + "source_final_signable_request_package_id" + ), + }, + "wait_for_source_authorization_chain_ids", + ), + _controlled_apply_final_preflight_check( + "controlled_apply_final_preflight_contract_complete", + len(controlled_apply_fields) == 12 + and len(controlled_apply_acceptance_gates) == 10 + and "rollback_binding_id" in controlled_apply_fields + and "post_apply_verifier_bound" in controlled_apply_acceptance_gates, + { + "controlled_apply_final_preflight_field_count": len( + controlled_apply_fields + ), + "controlled_apply_final_preflight_acceptance_gate_count": len( + controlled_apply_acceptance_gates + ), + }, + "wait_for_controlled_apply_final_preflight_contract", + ), + _controlled_apply_final_preflight_check( + "rollback_binding_complete", + len(rollback_binding_fields) == 8 + and bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_requires_same_run_truth") is True + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "rollback_binding_field_count": len(rollback_binding_fields), + "rollback_execution_authorized": rollback_binding.get( + "rollback_execution_authorized" + ), + }, + "wait_for_rollback_binding", + ), + _controlled_apply_final_preflight_check( + "post_apply_verifier_binding_complete", + len(post_apply_verifier_binding_fields) == 8 + and bool(post_apply_verifier_binding.get("post_apply_verifier_binding_id")) + and bool(post_apply_verifier_binding.get("post_apply_verifier_endpoint")) + and post_apply_verifier_binding.get("same_run_production_truth_required") + is True + and post_apply_verifier_binding.get("verifier_must_run_after_apply") + is True + and post_apply_verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and post_apply_verifier_binding.get("database_apply_authorized") is False, + { + "post_apply_verifier_binding_id": post_apply_verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "post_apply_verifier_endpoint": post_apply_verifier_binding.get( + "post_apply_verifier_endpoint" + ), + "post_apply_verifier_binding_field_count": len( + post_apply_verifier_binding_fields + ), + }, + "wait_for_post_apply_verifier_binding", + ), + _controlled_apply_final_preflight_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": target_file, + "expected_sha256_present": bool(expected_sha256), + "actual_sha256_present": bool(actual_sha256), + "hash_matches": evidence_closeout.get("hash_matches"), + }, + "require_target_migration_hash_lock", + ), + _controlled_apply_final_preflight_check( + "same_run_production_truth_and_post_apply_verifier_required", + evidence_closeout.get("requires_fresh_production_truth_in_same_run") is True + and evidence_closeout.get("requires_post_apply_verifier") is True + and bool(evidence_closeout.get("post_apply_verifier_endpoint")) + and int(summary.get("same_run_truth_required_count") or 0) == 1 + and int(summary.get("post_apply_verifier_required_count") or 0) == 1, + { + "requires_fresh_production_truth_in_same_run": evidence_closeout.get( + "requires_fresh_production_truth_in_same_run" + ), + "requires_post_apply_verifier": evidence_closeout.get( + "requires_post_apply_verifier" + ), + "post_apply_verifier_endpoint": evidence_closeout.get( + "post_apply_verifier_endpoint" + ), + }, + "require_same_run_production_truth_and_post_apply_verifier", + ), + _controlled_apply_final_preflight_check( + "secret_signed_material_execution_and_write_boundary_enforced", + evidence_closeout.get("detached_signature_verification_performed") is False + and evidence_closeout.get("verifier_receipt_persisted") is False + and evidence_closeout.get("external_signed_authorization_receipt_included") + is False + and evidence_closeout.get("signed_authorization_receipt_included") is False + and evidence_closeout.get("signature_material_included") is False + and evidence_closeout.get("secret_material_included") is False + and evidence_closeout.get("accepts_plaintext_secret") is False + and evidence_closeout.get("executes_authorization_evidence") is False + and evidence_closeout.get("executes_database_apply") is False + and final_gate.get("database_apply_authorized") is False, + { + "signature_material_included": evidence_closeout.get( + "signature_material_included" + ), + "secret_material_included": evidence_closeout.get( + "secret_material_included" + ), + "executes_database_apply": evidence_closeout.get( + "executes_database_apply" + ), + "database_apply_authorized": final_gate.get( + "database_apply_authorized" + ), + }, + "abort_on_secret_signed_material_execution_or_write_boundary_violation", + ), + _controlled_apply_final_preflight_check( + "final_verifier_contract_blocks_database_apply", + closeout_contract.get( + "permits_future_database_apply_controlled_apply_final_preflight" + ) + is True + and closeout_contract.get("executes_database_apply") is False + and closeout_contract.get("database_apply_authorized") is False + and closeout_contract.get("ready_for_database_apply_now") is False + and closeout_contract.get("signs_database_apply_authorization") is False + and closeout_contract.get("writes_database") is False, + { + "permits_future_database_apply_controlled_apply_final_preflight": ( + closeout_contract.get( + "permits_future_database_apply_controlled_apply_final_preflight" + ) + ), + "database_apply_authorized": closeout_contract.get( + "database_apply_authorized" + ), + "writes_database": closeout_contract.get("writes_database"), + }, + "abort_if_final_verifier_contract_authorizes_database_apply", + ), + _controlled_apply_final_preflight_check( + "preview_has_no_side_effects_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + "executes_database_apply": safety.get("executes_database_apply"), + }, + "abort_on_preview_side_effect_execution_or_signing", + ), + _controlled_apply_final_preflight_check( + "manual_review_regression_absent", + int(summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY) or 0) == 0 + and final_gate.get("manual_review_mode") == "exception_only", + { + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get( + LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0 + ), + "manual_review_mode": final_gate.get("manual_review_mode"), + }, + "route_failed_controlled_apply_preflight_to_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + final_preflight_ready = not waiting_checks + final_preflight_status = ( + "DB_APPLY_CONTROLLED_APPLY_FINAL_PREFLIGHT_READY" + if final_preflight_ready + else "WAITING_FOR_DB_APPLY_AUTHORIZATION_EVIDENCE_EXECUTION_CLOSEOUT" + ) + future_database_apply_controlled_apply_final_preflight = { + "controlled_apply_preflight_id": preflight_id, + "source_final_verifier_gate_id": final_gate.get("final_verifier_gate_id"), + "source_authorization_evidence_execution_closeout_id": ( + evidence_closeout.get("closeout_id") + ), + "source_authorization_evidence_execution_preflight_id": ( + evidence_closeout.get("source_authorization_evidence_execution_preflight_id") + ), + "source_final_signable_request_package_id": evidence_closeout.get( + "source_final_signable_request_package_id" + ), + "status": final_preflight_status, + "ready_for_future_database_apply_controlled_apply_final_preflight": ( + final_preflight_ready + ), + "can_enter_future_database_apply_controlled_dry_run_package": ( + final_preflight_ready + ), + "controlled_apply_final_preflight_ready": final_preflight_ready, + "rollback_binding_ready": final_preflight_ready, + "post_apply_verifier_binding_ready": final_preflight_ready, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "reads_secret_in_preview": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_apply_final_preflight = { + "controlled_apply_preflight_id": preflight_id, + "authorization_material_type": "controlled_apply_final_preflight", + "source_final_verifier_gate_id": final_gate.get("final_verifier_gate_id"), + "source_authorization_evidence_execution_closeout_id": ( + evidence_closeout.get("closeout_id") + ), + "source_authorization_evidence_execution_preflight_id": ( + evidence_closeout.get("source_authorization_evidence_execution_preflight_id") + ), + "source_verifier_receipt_closeout_id": evidence_closeout.get( + "source_verifier_receipt_closeout_id" + ), + "source_verifier_receipt_evidence_handoff_id": evidence_closeout.get( + "source_verifier_receipt_evidence_handoff_id" + ), + "source_final_signable_request_package_id": evidence_closeout.get( + "source_final_signable_request_package_id" + ), + "status": final_preflight_status, + "ready_for_future_database_apply_controlled_apply_final_preflight": ( + final_preflight_ready + ), + "controlled_apply_final_preflight_fields": controlled_apply_fields, + "controlled_apply_final_preflight_field_count": len(controlled_apply_fields), + "controlled_apply_final_preflight_acceptance_gates": ( + controlled_apply_acceptance_gates + ), + "controlled_apply_final_preflight_acceptance_gate_count": len( + controlled_apply_acceptance_gates + ), + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "rollback_binding_field_count": len(rollback_binding_fields), + "post_apply_verifier_binding": post_apply_verifier_binding, + "post_apply_verifier_binding_count": 1, + "post_apply_verifier_binding_field_count": len( + post_apply_verifier_binding_fields + ), + "target_file": target_file, + "expected_sha256": expected_sha256, + "actual_sha256": actual_sha256, + "hash_matches": evidence_closeout.get("hash_matches"), + "dry_run_only": True, + "check_mode_only": True, + "rollback_bound": final_preflight_ready, + "post_apply_verifier_bound": final_preflight_ready, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "post_apply_verifier_endpoint": evidence_closeout.get( + "post_apply_verifier_endpoint" + ), + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "secret_material_required_in_preview": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + } + controlled_apply_final_preflight_contract = { + "mode": "controlled_apply_final_preflight_rollback_and_verifier_binding_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-apply-final-preflight" + ), + "source_authorization_evidence_execution_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-authorization-evidence-execution-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_package": ( + final_preflight_ready + ), + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": 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", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_APPLY_FINAL_PREFLIGHT_POLICY, + "result": final_preflight_status, + "success": bool(closeout.get("success")), + "generated_at": closeout.get("generated_at"), + "source_policy": closeout.get("policy"), + "stats": closeout.get("stats") or {}, + "summary": { + "controlled_apply_final_preflight_ready_count": ( + 1 if final_preflight_ready else 0 + ), + "controlled_apply_final_preflight_check_count": len(checks), + "controlled_apply_final_preflight_pass_count": passed_count, + "controlled_apply_final_preflight_waiting_count": len(waiting_checks), + "authorization_evidence_execution_closeout_ready_count": summary.get( + "authorization_evidence_execution_closeout_ready_count", 0 + ), + "authorization_evidence_execution_closeout_check_count": summary.get( + "authorization_evidence_execution_closeout_check_count", 0 + ), + "authorization_evidence_execution_preflight_ready_count": summary.get( + "authorization_evidence_execution_preflight_ready_count", 0 + ), + "authorization_evidence_execution_preflight_check_count": summary.get( + "authorization_evidence_execution_preflight_check_count", 0 + ), + "authorization_verifier_receipt_closeout_ready_count": summary.get( + "authorization_verifier_receipt_closeout_ready_count", 0 + ), + "verifier_receipt_closeout_check_count": summary.get( + "verifier_receipt_closeout_check_count", 0 + ), + "database_apply_final_verifier_gate_count": summary.get( + "database_apply_final_verifier_gate_count", 0 + ), + "database_apply_authorization_final_verifier_gate_ready_count": summary.get( + "database_apply_authorization_final_verifier_gate_ready_count", 0 + ), + "controlled_apply_final_preflight_count": 1, + "rollback_binding_count": 1, + "rollback_binding_field_count": len(rollback_binding_fields), + "post_apply_verifier_binding_count": 1, + "post_apply_verifier_binding_field_count": len( + post_apply_verifier_binding_fields + ), + "controlled_apply_final_preflight_field_count": len( + controlled_apply_fields + ), + "controlled_apply_final_preflight_acceptance_gate_count": len( + controlled_apply_acceptance_gates + ), + "authorization_evidence_execution_closeout_field_count": summary.get( + "authorization_evidence_execution_closeout_field_count", 0 + ), + "authorization_evidence_execution_closeout_acceptance_gate_count": ( + summary.get( + "authorization_evidence_execution_closeout_acceptance_gate_count", + 0, + ) + ), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + "same_run_truth_required_count": summary.get("same_run_truth_required_count", 0), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + }, + "future_database_apply_controlled_apply_final_preflight": ( + future_database_apply_controlled_apply_final_preflight + ), + "controlled_apply_final_preflight": controlled_apply_final_preflight, + "controlled_apply_final_preflight_contract": ( + controlled_apply_final_preflight_contract + ), + "controlled_apply_final_preflight_checks": checks, + "source_authorization_evidence_execution_closeout_summary": summary, + "source_authorization_evidence_execution_closeout_contract": closeout_contract, + "source_authorization_evidence_execution_closeout": evidence_closeout, + "source_database_apply_authorization_final_verifier_gate": final_gate, + "safety": { + "read_only_db_apply_controlled_apply_final_preflight": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this final preflight to build a future controlled dry-run package.", + "Keep rollback and post-apply verifier binding machine-verifiable before any apply execution.", + "This preflight still does not authorize endpoint execution, SQL, DB writes, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_package( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Package a future controlled dry-run receipt without executing it.""" + final_preflight = build_pchome_auto_policy_db_apply_controlled_apply_final_preflight( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + future_preflight = ( + final_preflight.get("future_database_apply_controlled_apply_final_preflight") + or {} + ) + controlled_preflight = final_preflight.get("controlled_apply_final_preflight") or {} + final_preflight_contract = ( + final_preflight.get("controlled_apply_final_preflight_contract") or {} + ) + summary = final_preflight.get("summary") or {} + safety = final_preflight.get("safety") or {} + package_id = _db_apply_controlled_dry_run_package_id(final_preflight) + receipt_id = f"{package_id}-dry-run-receipt-preview" + rollback_binding = controlled_preflight.get("rollback_binding") or {} + verifier_binding = controlled_preflight.get("post_apply_verifier_binding") or {} + target_file = controlled_preflight.get("target_file") + expected_sha256 = controlled_preflight.get("expected_sha256") + actual_sha256 = controlled_preflight.get("actual_sha256") + target_hash_locked = ( + target_file == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(expected_sha256) + and bool(actual_sha256) + and expected_sha256 == actual_sha256 + and controlled_preflight.get("hash_matches") is True + ) + dry_run_package_fields = [ + "dry_run_package_id", + "source_controlled_apply_preflight_id", + "source_final_verifier_gate_id", + "source_authorization_evidence_execution_closeout_id", + "target_migration_file", + "target_migration_sha256", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "dry_run_command_shape", + "dry_run_result_parser", + "dry_run_execution_receipt_id", + "abort_conditions", + ] + dry_run_acceptance_gates = [ + "controlled_apply_final_preflight_ready", + "source_chain_ids_match", + "rollback_binding_present", + "post_apply_verifier_binding_present", + "target_migration_hash_locked", + "dry_run_command_shape_is_non_executable", + "dry_run_receipt_preview_only", + "no_secret_signature_or_database_apply", + "no_endpoint_sql_or_db_write", + "exception_only_failure_routing", + ] + receipt_fields = [ + "receipt_id", + "source_dry_run_package_id", + "dry_run_status", + "dry_run_command_shape_hash", + "execution_performed", + "stdout_included", + "stderr_included", + "database_apply_authorized", + ] + result_parser_fields = [ + "parser_id", + "source_dry_run_package_id", + "expected_receipt_status", + "required_receipt_fields", + "required_command_shape_hash", + "execution_required", + "stdout_allowed", + "stderr_allowed", + "database_apply_authorized", + "parser_verification_status", + ] + command_shape = { + "command_family": "pchome_db_apply_controlled_dry_run", + "dry_run_only": True, + "check_mode_only": True, + "execution_allowed": False, + "shell_command_included": False, + "sql_included": False, + "endpoint_execution_included": False, + "database_write_included": False, + "requires_fresh_production_truth_in_same_run": True, + "requires_rollback_binding": True, + "requires_post_apply_verifier_binding": True, + "target_file": target_file, + "target_sha256": expected_sha256, + "args_preview": [ + "--dry-run", + "--check", + "--no-execute", + "--require-post-apply-verifier", + ], + } + command_shape_hash = hashlib.sha256( + json.dumps(command_shape, sort_keys=True).encode("utf-8") + ).hexdigest() + dry_run_execution_receipt_preview = { + "receipt_id": receipt_id, + "source_dry_run_package_id": package_id, + "dry_run_status": "preview_only_not_executed", + "dry_run_command_shape_hash": command_shape_hash, + "execution_performed": False, + "stdout_included": False, + "stderr_included": False, + "database_apply_authorized": False, + "executes_shell": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "reads_secret": False, + "receipt_field_count": len(receipt_fields), + "receipt_fields": receipt_fields, + } + dry_run_result_parser = { + "parser_id": f"{package_id}-result-parser", + "source_dry_run_package_id": package_id, + "expected_receipt_status": "preview_only_not_executed", + "required_receipt_fields": receipt_fields, + "required_command_shape_hash": command_shape_hash, + "execution_required": False, + "stdout_allowed": False, + "stderr_allowed": False, + "database_apply_authorized": False, + "parser_verification_status": "schema_preview_ready", + "parser_field_count": len(result_parser_fields), + "parser_fields": result_parser_fields, + } + command_shape_preview_only = ( + command_shape.get("dry_run_only") is True + and command_shape.get("check_mode_only") is True + and command_shape.get("execution_allowed") is False + and command_shape.get("shell_command_included") is False + and command_shape.get("sql_included") is False + and command_shape.get("endpoint_execution_included") is False + and command_shape.get("database_write_included") is False + ) + receipt_preview_only = ( + dry_run_execution_receipt_preview.get("execution_performed") is False + and dry_run_execution_receipt_preview.get("stdout_included") is False + and dry_run_execution_receipt_preview.get("stderr_included") is False + and dry_run_execution_receipt_preview.get("database_apply_authorized") is False + and dry_run_execution_receipt_preview.get("executes_shell") is False + and dry_run_execution_receipt_preview.get("executes_endpoint") is False + and dry_run_execution_receipt_preview.get("executes_sql") is False + and dry_run_execution_receipt_preview.get("writes_database") is False + and dry_run_execution_receipt_preview.get("reads_secret") is False + ) + side_effect_free = ( + int(summary.get("writes_script_count") or 0) == 0 + and int(summary.get("writes_artifact_count") or 0) == 0 + and int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_migration_count") or 0) == 0 + and int(summary.get("executes_endpoint_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and int(summary.get("signs_database_apply_authorization_count") or 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_script") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("signs_database_apply_authorization") is False + and safety.get("executes_authorization_evidence") is False + and safety.get("executes_database_apply") is False + and controlled_preflight.get("accepts_plaintext_secret") is False + and controlled_preflight.get("reads_secret_in_preview") is False + and controlled_preflight.get("signature_material_included") is False + and controlled_preflight.get("secret_material_included") is False + and controlled_preflight.get("secret_material_required_in_preview") is False + and controlled_preflight.get("signs_database_apply_authorization") is False + and controlled_preflight.get("executes_authorization_evidence") is False + and controlled_preflight.get("executes_database_apply") is False + and controlled_preflight.get("executes_endpoint_in_preview") is False + and controlled_preflight.get("executes_sql_in_preview") is False + and controlled_preflight.get("writes_database_in_preview") is False + and command_shape_preview_only + and receipt_preview_only + ) + checks = [ + _controlled_dry_run_package_check( + "controlled_apply_final_preflight_ready", + final_preflight.get("result") + == "DB_APPLY_CONTROLLED_APPLY_FINAL_PREFLIGHT_READY" + and future_preflight.get( + "ready_for_future_database_apply_controlled_apply_final_preflight" + ) + is True + and future_preflight.get( + "can_enter_future_database_apply_controlled_dry_run_package" + ) + is True + and controlled_preflight.get( + "ready_for_future_database_apply_controlled_apply_final_preflight" + ) + is True, + { + "result": final_preflight.get("result"), + "ready_for_future_database_apply_controlled_apply_final_preflight": ( + future_preflight.get( + "ready_for_future_database_apply_controlled_apply_final_preflight" + ) + ), + "can_enter_future_database_apply_controlled_dry_run_package": ( + future_preflight.get( + "can_enter_future_database_apply_controlled_dry_run_package" + ) + ), + }, + "wait_for_controlled_apply_final_preflight", + ), + _controlled_dry_run_package_check( + "source_chain_ids_present", + bool(future_preflight.get("controlled_apply_preflight_id")) + and bool(controlled_preflight.get("source_final_verifier_gate_id")) + and bool( + controlled_preflight.get( + "source_authorization_evidence_execution_closeout_id" + ) + ) + and bool( + controlled_preflight.get("source_final_signable_request_package_id") + ), + { + "controlled_apply_preflight_id": future_preflight.get( + "controlled_apply_preflight_id" + ), + "source_final_verifier_gate_id": controlled_preflight.get( + "source_final_verifier_gate_id" + ), + "source_authorization_evidence_execution_closeout_id": ( + controlled_preflight.get( + "source_authorization_evidence_execution_closeout_id" + ) + ), + }, + "wait_for_source_controlled_apply_chain_ids", + ), + _controlled_dry_run_package_check( + "controlled_dry_run_package_contract_complete", + len(dry_run_package_fields) == 12 + and len(dry_run_acceptance_gates) == 10 + and len(receipt_fields) == 8 + and "dry_run_execution_receipt_id" in dry_run_package_fields + and "dry_run_receipt_preview_only" in dry_run_acceptance_gates, + { + "controlled_dry_run_package_field_count": len( + dry_run_package_fields + ), + "controlled_dry_run_acceptance_gate_count": len( + dry_run_acceptance_gates + ), + "dry_run_execution_receipt_field_count": len(receipt_fields), + }, + "wait_for_controlled_dry_run_package_contract", + ), + _controlled_dry_run_package_check( + "rollback_binding_carried_forward", + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_requires_same_run_truth") is True + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "rollback_execution_authorized": rollback_binding.get( + "rollback_execution_authorized" + ), + "rollback_executes_sql": rollback_binding.get("rollback_executes_sql"), + }, + "wait_for_rollback_binding", + ), + _controlled_dry_run_package_check( + "post_apply_verifier_binding_carried_forward", + bool(verifier_binding.get("post_apply_verifier_binding_id")) + and bool(verifier_binding.get("post_apply_verifier_endpoint")) + and verifier_binding.get("same_run_production_truth_required") is True + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False, + { + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "post_apply_verifier_endpoint": verifier_binding.get( + "post_apply_verifier_endpoint" + ), + "database_apply_authorized": verifier_binding.get( + "database_apply_authorized" + ), + }, + "wait_for_post_apply_verifier_binding", + ), + _controlled_dry_run_package_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": target_file, + "expected_sha256_present": bool(expected_sha256), + "actual_sha256_present": bool(actual_sha256), + "hash_matches": controlled_preflight.get("hash_matches"), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_package_check( + "dry_run_command_shape_preview_only", + command_shape_preview_only + and command_shape.get("requires_fresh_production_truth_in_same_run") + is True + and command_shape.get("requires_rollback_binding") is True + and command_shape.get("requires_post_apply_verifier_binding") is True + and bool(command_shape_hash), + { + "dry_run_only": command_shape.get("dry_run_only"), + "check_mode_only": command_shape.get("check_mode_only"), + "execution_allowed": command_shape.get("execution_allowed"), + "dry_run_command_shape_hash_present": bool(command_shape_hash), + }, + "abort_if_dry_run_command_shape_is_executable", + ), + _controlled_dry_run_package_check( + "dry_run_execution_receipt_preview_only", + receipt_preview_only + and dry_run_execution_receipt_preview.get("receipt_field_count") == 8 + and bool(dry_run_execution_receipt_preview.get("receipt_id")) + and bool( + dry_run_execution_receipt_preview.get( + "dry_run_command_shape_hash" + ) + ), + { + "receipt_id": dry_run_execution_receipt_preview.get("receipt_id"), + "dry_run_status": dry_run_execution_receipt_preview.get( + "dry_run_status" + ), + "execution_performed": dry_run_execution_receipt_preview.get( + "execution_performed" + ), + }, + "abort_if_dry_run_receipt_indicates_execution", + ), + _controlled_dry_run_package_check( + "same_run_production_truth_and_post_apply_verifier_required", + controlled_preflight.get("requires_fresh_production_truth_in_same_run") + is True + and controlled_preflight.get("requires_post_apply_verifier") is True + and bool(controlled_preflight.get("post_apply_verifier_endpoint")) + and int(summary.get("same_run_truth_required_count") or 0) == 1 + and int(summary.get("post_apply_verifier_required_count") or 0) == 1, + { + "requires_fresh_production_truth_in_same_run": ( + controlled_preflight.get( + "requires_fresh_production_truth_in_same_run" + ) + ), + "requires_post_apply_verifier": controlled_preflight.get( + "requires_post_apply_verifier" + ), + "post_apply_verifier_endpoint": controlled_preflight.get( + "post_apply_verifier_endpoint" + ), + }, + "require_same_run_production_truth_and_post_apply_verifier", + ), + _controlled_dry_run_package_check( + "final_preflight_contract_blocks_database_apply", + final_preflight_contract.get( + "permits_future_database_apply_controlled_dry_run_package" + ) + is True + and final_preflight_contract.get("executes_database_apply") is False + and final_preflight_contract.get("database_apply_authorized") is False + and final_preflight_contract.get("ready_for_database_apply_now") is False + and final_preflight_contract.get("signs_database_apply_authorization") + is False + and final_preflight_contract.get("writes_database") is False, + { + "permits_future_database_apply_controlled_dry_run_package": ( + final_preflight_contract.get( + "permits_future_database_apply_controlled_dry_run_package" + ) + ), + "database_apply_authorized": final_preflight_contract.get( + "database_apply_authorized" + ), + "writes_database": final_preflight_contract.get("writes_database"), + }, + "abort_if_final_preflight_contract_authorizes_database_apply", + ), + _controlled_dry_run_package_check( + "preview_has_no_side_effects_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + "command_shape_preview_only": command_shape_preview_only, + "receipt_preview_only": receipt_preview_only, + }, + "abort_on_preview_side_effect_execution_or_signing", + ), + _controlled_dry_run_package_check( + "manual_review_regression_absent", + int(summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY) or 0) == 0 + and future_preflight.get("manual_review_mode") == "exception_only", + { + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get( + LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0 + ), + "manual_review_mode": future_preflight.get("manual_review_mode"), + }, + "route_failed_dry_run_package_to_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + package_ready = not waiting_checks + package_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_PACKAGE_READY" + if package_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_APPLY_FINAL_PREFLIGHT" + ) + future_database_apply_controlled_dry_run_execution_receipt = { + "dry_run_package_id": package_id, + "dry_run_execution_receipt_id": receipt_id, + "source_controlled_apply_preflight_id": future_preflight.get( + "controlled_apply_preflight_id" + ), + "source_final_verifier_gate_id": controlled_preflight.get( + "source_final_verifier_gate_id" + ), + "status": package_status, + "ready_for_future_database_apply_controlled_dry_run_execution_receipt": ( + package_ready + ), + "can_enter_future_database_apply_controlled_dry_run_receipt_closeout": ( + package_ready + ), + "controlled_dry_run_package_ready": package_ready, + "dry_run_execution_performed": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "reads_secret_in_preview": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_package = { + "dry_run_package_id": package_id, + "authorization_material_type": "controlled_dry_run_package", + "source_controlled_apply_preflight_id": future_preflight.get( + "controlled_apply_preflight_id" + ), + "source_final_verifier_gate_id": controlled_preflight.get( + "source_final_verifier_gate_id" + ), + "source_authorization_evidence_execution_closeout_id": ( + controlled_preflight.get( + "source_authorization_evidence_execution_closeout_id" + ) + ), + "source_final_signable_request_package_id": controlled_preflight.get( + "source_final_signable_request_package_id" + ), + "status": package_status, + "ready_for_future_database_apply_controlled_dry_run_package": ( + package_ready + ), + "controlled_dry_run_package_fields": dry_run_package_fields, + "controlled_dry_run_package_field_count": len(dry_run_package_fields), + "controlled_dry_run_acceptance_gates": dry_run_acceptance_gates, + "controlled_dry_run_acceptance_gate_count": len( + dry_run_acceptance_gates + ), + "dry_run_command_shape": command_shape, + "dry_run_command_shape_hash": command_shape_hash, + "dry_run_result_parser": dry_run_result_parser, + "dry_run_result_parser_count": 1, + "dry_run_result_parser_field_count": len(result_parser_fields), + "dry_run_execution_receipt_preview": dry_run_execution_receipt_preview, + "dry_run_execution_receipt_preview_count": 1, + "dry_run_execution_receipt_field_count": len(receipt_fields), + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "target_file": target_file, + "expected_sha256": expected_sha256, + "actual_sha256": actual_sha256, + "hash_matches": controlled_preflight.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "dry_run_only": True, + "check_mode_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + } + controlled_dry_run_package_contract = { + "mode": "controlled_dry_run_package_and_receipt_preview_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-package" + ), + "source_final_preflight_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-apply-final-preflight" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_execution_receipt": ( + package_ready + ), + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": 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", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_PACKAGE_POLICY, + "result": package_status, + "success": bool(final_preflight.get("success")), + "generated_at": final_preflight.get("generated_at"), + "source_policy": final_preflight.get("policy"), + "stats": final_preflight.get("stats") or {}, + "summary": { + "controlled_dry_run_package_ready_count": 1 if package_ready else 0, + "controlled_dry_run_package_check_count": len(checks), + "controlled_dry_run_package_pass_count": passed_count, + "controlled_dry_run_package_waiting_count": len(waiting_checks), + "controlled_apply_final_preflight_ready_count": summary.get( + "controlled_apply_final_preflight_ready_count", 0 + ), + "controlled_apply_final_preflight_check_count": summary.get( + "controlled_apply_final_preflight_check_count", 0 + ), + "authorization_evidence_execution_closeout_ready_count": summary.get( + "authorization_evidence_execution_closeout_ready_count", 0 + ), + "authorization_evidence_execution_closeout_check_count": summary.get( + "authorization_evidence_execution_closeout_check_count", 0 + ), + "authorization_evidence_execution_preflight_ready_count": summary.get( + "authorization_evidence_execution_preflight_ready_count", 0 + ), + "authorization_evidence_execution_preflight_check_count": summary.get( + "authorization_evidence_execution_preflight_check_count", 0 + ), + "authorization_verifier_receipt_closeout_ready_count": summary.get( + "authorization_verifier_receipt_closeout_ready_count", 0 + ), + "verifier_receipt_closeout_check_count": summary.get( + "verifier_receipt_closeout_check_count", 0 + ), + "database_apply_final_verifier_gate_count": summary.get( + "database_apply_final_verifier_gate_count", 0 + ), + "database_apply_authorization_final_verifier_gate_ready_count": ( + summary.get( + "database_apply_authorization_final_verifier_gate_ready_count", + 0, + ) + ), + "controlled_dry_run_package_count": 1, + "controlled_dry_run_package_field_count": len(dry_run_package_fields), + "controlled_dry_run_acceptance_gate_count": len( + dry_run_acceptance_gates + ), + "dry_run_execution_receipt_preview_count": 1, + "dry_run_execution_receipt_field_count": len(receipt_fields), + "dry_run_result_parser_count": 1, + "dry_run_result_parser_field_count": len(result_parser_fields), + "controlled_apply_final_preflight_count": summary.get( + "controlled_apply_final_preflight_count", 0 + ), + "controlled_apply_final_preflight_field_count": summary.get( + "controlled_apply_final_preflight_field_count", 0 + ), + "controlled_apply_final_preflight_acceptance_gate_count": summary.get( + "controlled_apply_final_preflight_acceptance_gate_count", 0 + ), + "rollback_binding_count": summary.get("rollback_binding_count", 0), + "rollback_binding_field_count": summary.get( + "rollback_binding_field_count", 0 + ), + "post_apply_verifier_binding_count": summary.get( + "post_apply_verifier_binding_count", 0 + ), + "post_apply_verifier_binding_field_count": summary.get( + "post_apply_verifier_binding_field_count", 0 + ), + "authorization_evidence_execution_closeout_field_count": summary.get( + "authorization_evidence_execution_closeout_field_count", 0 + ), + "authorization_evidence_execution_closeout_acceptance_gate_count": ( + summary.get( + "authorization_evidence_execution_closeout_acceptance_gate_count", + 0, + ) + ), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + "same_run_truth_required_count": summary.get( + "same_run_truth_required_count", 0 + ), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + }, + "future_database_apply_controlled_dry_run_execution_receipt": ( + future_database_apply_controlled_dry_run_execution_receipt + ), + "controlled_dry_run_package": controlled_dry_run_package, + "controlled_dry_run_package_contract": controlled_dry_run_package_contract, + "controlled_dry_run_package_checks": checks, + "source_controlled_apply_final_preflight_summary": summary, + "source_controlled_apply_final_preflight_contract": final_preflight_contract, + "source_controlled_apply_final_preflight": controlled_preflight, + "source_database_apply_controlled_apply_final_preflight": future_preflight, + "safety": { + "read_only_db_apply_controlled_dry_run_package": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this package to build a future controlled dry-run execution receipt closeout.", + "Keep the dry-run command shape non-executable until a dedicated execution receipt lane is explicit.", + "This package still does not authorize endpoint execution, SQL, DB writes, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_receipt_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out the dry-run receipt preview and parser without execution.""" + package_result = build_pchome_auto_policy_db_apply_controlled_dry_run_package( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + future_receipt = ( + package_result.get("future_database_apply_controlled_dry_run_execution_receipt") + or {} + ) + package = package_result.get("controlled_dry_run_package") or {} + package_contract = package_result.get("controlled_dry_run_package_contract") or {} + summary = package_result.get("summary") or {} + safety = package_result.get("safety") or {} + receipt_preview = package.get("dry_run_execution_receipt_preview") or {} + command_shape = package.get("dry_run_command_shape") or {} + result_parser = package.get("dry_run_result_parser") or {} + rollback_binding = package.get("rollback_binding") or {} + verifier_binding = package.get("post_apply_verifier_binding") or {} + closeout_id = _db_apply_controlled_dry_run_receipt_closeout_id(package_result) + receipt_closeout_fields = [ + "receipt_closeout_id", + "source_dry_run_package_id", + "source_controlled_apply_preflight_id", + "source_dry_run_execution_receipt_id", + "dry_run_command_shape_hash", + "dry_run_result_parser_id", + "receipt_validation_status", + "target_migration_file", + "target_migration_sha256", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "abort_conditions", + ] + receipt_closeout_acceptance_gates = [ + "controlled_dry_run_package_ready", + "source_chain_ids_match", + "result_parser_schema_complete", + "receipt_preview_schema_match", + "command_shape_hash_match", + "receipt_preview_not_executed", + "target_migration_hash_locked", + "no_secret_signature_or_database_apply", + "no_endpoint_sql_or_db_write", + "exception_only_failure_routing", + ] + receipt_validation_fields = [ + "receipt_id", + "source_dry_run_package_id", + "dry_run_status", + "dry_run_command_shape_hash", + "execution_performed", + "stdout_included", + "stderr_included", + "database_apply_authorized", + ] + target_hash_locked = ( + package.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(package.get("expected_sha256")) + and bool(package.get("actual_sha256")) + and package.get("expected_sha256") == package.get("actual_sha256") + and package.get("hash_matches") is True + and package.get("target_migration_hash_locked") is True + ) + parser_schema_complete = ( + bool(result_parser.get("parser_id")) + and result_parser.get("source_dry_run_package_id") + == package.get("dry_run_package_id") + and result_parser.get("expected_receipt_status") + == "preview_only_not_executed" + and result_parser.get("required_command_shape_hash") + == package.get("dry_run_command_shape_hash") + and result_parser.get("execution_required") is False + and result_parser.get("stdout_allowed") is False + and result_parser.get("stderr_allowed") is False + and result_parser.get("database_apply_authorized") is False + and int(result_parser.get("parser_field_count") or 0) == 10 + ) + required_receipt_fields = result_parser.get("required_receipt_fields") or [] + receipt_schema_matches_parser = ( + parser_schema_complete + and receipt_preview.get("source_dry_run_package_id") + == package.get("dry_run_package_id") + and receipt_preview.get("dry_run_status") + == result_parser.get("expected_receipt_status") + and receipt_preview.get("dry_run_command_shape_hash") + == result_parser.get("required_command_shape_hash") + and int(receipt_preview.get("receipt_field_count") or 0) == 8 + and all(field in receipt_preview for field in required_receipt_fields) + ) + command_hash_matches_receipt = ( + bool(package.get("dry_run_command_shape_hash")) + and package.get("dry_run_command_shape_hash") + == receipt_preview.get("dry_run_command_shape_hash") + == result_parser.get("required_command_shape_hash") + ) + command_shape_preview_only = ( + command_shape.get("dry_run_only") is True + and command_shape.get("check_mode_only") is True + and command_shape.get("execution_allowed") is False + and command_shape.get("shell_command_included") is False + and command_shape.get("sql_included") is False + and command_shape.get("endpoint_execution_included") is False + and command_shape.get("database_write_included") is False + ) + receipt_preview_only = ( + receipt_preview.get("execution_performed") is False + and receipt_preview.get("stdout_included") is False + and receipt_preview.get("stderr_included") is False + and receipt_preview.get("database_apply_authorized") is False + and receipt_preview.get("executes_shell") is False + and receipt_preview.get("executes_endpoint") is False + and receipt_preview.get("executes_sql") is False + and receipt_preview.get("writes_database") is False + and receipt_preview.get("reads_secret") is False + ) + receipt_validation_report = { + "receipt_id": receipt_preview.get("receipt_id"), + "source_dry_run_package_id": package.get("dry_run_package_id"), + "dry_run_status": receipt_preview.get("dry_run_status"), + "dry_run_command_shape_hash": receipt_preview.get( + "dry_run_command_shape_hash" + ), + "execution_performed": False, + "stdout_included": False, + "stderr_included": False, + "database_apply_authorized": False, + "receipt_validation_status": ( + "preview_validated_not_executed" + if receipt_schema_matches_parser and command_hash_matches_receipt + else "waiting_for_receipt_parser_schema_match" + ), + "receipt_validation_field_count": len(receipt_validation_fields), + "receipt_validation_fields": receipt_validation_fields, + "executes_shell": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "reads_secret": False, + } + side_effect_free = ( + int(summary.get("writes_script_count") or 0) == 0 + and int(summary.get("writes_artifact_count") or 0) == 0 + and int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_migration_count") or 0) == 0 + and int(summary.get("executes_endpoint_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and int(summary.get("signs_database_apply_authorization_count") or 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_script") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("signs_database_apply_authorization") is False + and safety.get("executes_authorization_evidence") is False + and safety.get("executes_database_apply") is False + and package.get("accepts_plaintext_secret") is False + and package.get("reads_secret_in_preview") is False + and package.get("signature_material_included") is False + and package.get("secret_material_included") is False + and package.get("signs_database_apply_authorization") is False + and package.get("executes_authorization_evidence") is False + and package.get("executes_database_apply") is False + and package.get("executes_endpoint_in_preview") is False + and package.get("executes_sql_in_preview") is False + and package.get("writes_database_in_preview") is False + and command_shape_preview_only + and receipt_preview_only + ) + checks = [ + _controlled_dry_run_receipt_closeout_check( + "controlled_dry_run_package_ready", + package_result.get("result") == "DB_APPLY_CONTROLLED_DRY_RUN_PACKAGE_READY" + and future_receipt.get( + "ready_for_future_database_apply_controlled_dry_run_execution_receipt" + ) + is True + and future_receipt.get( + "can_enter_future_database_apply_controlled_dry_run_receipt_closeout" + ) + is True + and package.get("ready_for_future_database_apply_controlled_dry_run_package") + is True, + { + "result": package_result.get("result"), + "ready_for_future_database_apply_controlled_dry_run_execution_receipt": ( + future_receipt.get( + "ready_for_future_database_apply_controlled_dry_run_execution_receipt" + ) + ), + "can_enter_future_database_apply_controlled_dry_run_receipt_closeout": ( + future_receipt.get( + "can_enter_future_database_apply_controlled_dry_run_receipt_closeout" + ) + ), + }, + "wait_for_controlled_dry_run_package", + ), + _controlled_dry_run_receipt_closeout_check( + "source_chain_ids_present", + bool(package.get("dry_run_package_id")) + and bool(future_receipt.get("dry_run_execution_receipt_id")) + and bool(package.get("source_controlled_apply_preflight_id")) + and bool(package.get("source_final_verifier_gate_id")) + and bool(package.get("source_authorization_evidence_execution_closeout_id")), + { + "dry_run_package_id": package.get("dry_run_package_id"), + "dry_run_execution_receipt_id": future_receipt.get( + "dry_run_execution_receipt_id" + ), + "source_controlled_apply_preflight_id": package.get( + "source_controlled_apply_preflight_id" + ), + }, + "wait_for_controlled_dry_run_source_chain_ids", + ), + _controlled_dry_run_receipt_closeout_check( + "controlled_dry_run_receipt_closeout_contract_complete", + len(receipt_closeout_fields) == 12 + and len(receipt_closeout_acceptance_gates) == 10 + and len(receipt_validation_fields) == 8 + and "dry_run_result_parser_id" in receipt_closeout_fields + and "receipt_preview_schema_match" in receipt_closeout_acceptance_gates, + { + "receipt_closeout_field_count": len(receipt_closeout_fields), + "receipt_closeout_acceptance_gate_count": len( + receipt_closeout_acceptance_gates + ), + "receipt_validation_field_count": len(receipt_validation_fields), + }, + "wait_for_receipt_closeout_contract", + ), + _controlled_dry_run_receipt_closeout_check( + "dry_run_result_parser_schema_complete", + parser_schema_complete, + { + "parser_id": result_parser.get("parser_id"), + "parser_field_count": result_parser.get("parser_field_count"), + "execution_required": result_parser.get("execution_required"), + "database_apply_authorized": result_parser.get( + "database_apply_authorized" + ), + }, + "wait_for_dry_run_result_parser_schema", + ), + _controlled_dry_run_receipt_closeout_check( + "receipt_preview_schema_matches_parser", + receipt_schema_matches_parser, + { + "receipt_id": receipt_preview.get("receipt_id"), + "dry_run_status": receipt_preview.get("dry_run_status"), + "required_receipt_field_count": len(required_receipt_fields), + "receipt_field_count": receipt_preview.get("receipt_field_count"), + }, + "wait_for_receipt_preview_parser_match", + ), + _controlled_dry_run_receipt_closeout_check( + "command_shape_hash_matches_receipt", + command_hash_matches_receipt, + { + "dry_run_command_shape_hash_present": bool( + package.get("dry_run_command_shape_hash") + ), + "receipt_hash_matches": ( + package.get("dry_run_command_shape_hash") + == receipt_preview.get("dry_run_command_shape_hash") + ), + }, + "abort_on_dry_run_command_shape_hash_mismatch", + ), + _controlled_dry_run_receipt_closeout_check( + "receipt_preview_only_not_executed", + receipt_preview_only and command_shape_preview_only, + { + "execution_performed": receipt_preview.get("execution_performed"), + "stdout_included": receipt_preview.get("stdout_included"), + "stderr_included": receipt_preview.get("stderr_included"), + "execution_allowed": command_shape.get("execution_allowed"), + }, + "abort_if_receipt_or_command_shape_indicates_execution", + ), + _controlled_dry_run_receipt_closeout_check( + "rollback_and_post_apply_verifier_bindings_carried_forward", + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "database_apply_authorized": verifier_binding.get( + "database_apply_authorized" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_receipt_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": package.get("target_file"), + "expected_sha256_present": bool(package.get("expected_sha256")), + "actual_sha256_present": bool(package.get("actual_sha256")), + "hash_matches": package.get("hash_matches"), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_receipt_closeout_check( + "package_contract_blocks_database_apply", + package_contract.get( + "permits_future_database_apply_controlled_dry_run_execution_receipt" + ) + is True + and package_contract.get("executes_database_apply") is False + and package_contract.get("database_apply_authorized") is False + and package_contract.get("ready_for_database_apply_now") is False + and package_contract.get("signs_database_apply_authorization") is False + and package_contract.get("writes_database") is False, + { + "permits_future_database_apply_controlled_dry_run_execution_receipt": ( + package_contract.get( + "permits_future_database_apply_controlled_dry_run_execution_receipt" + ) + ), + "database_apply_authorized": package_contract.get( + "database_apply_authorized" + ), + "writes_database": package_contract.get("writes_database"), + }, + "abort_if_dry_run_package_contract_authorizes_database_apply", + ), + _controlled_dry_run_receipt_closeout_check( + "preview_has_no_side_effects_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + "command_shape_preview_only": command_shape_preview_only, + "receipt_preview_only": receipt_preview_only, + }, + "abort_on_preview_side_effect_execution_or_signing", + ), + _controlled_dry_run_receipt_closeout_check( + "manual_review_regression_absent", + int(summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY) or 0) == 0 + and future_receipt.get("manual_review_mode") == "exception_only", + { + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get( + LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0 + ), + "manual_review_mode": future_receipt.get("manual_review_mode"), + }, + "route_failed_receipt_closeout_to_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_RECEIPT_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_PACKAGE" + ) + future_database_apply_controlled_dry_run_result_parser_verification = { + "receipt_closeout_id": closeout_id, + "source_dry_run_package_id": package.get("dry_run_package_id"), + "source_dry_run_execution_receipt_id": future_receipt.get( + "dry_run_execution_receipt_id" + ), + "dry_run_result_parser_id": result_parser.get("parser_id"), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_result_parser_verification": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_runner_readiness": ( + closeout_ready + ), + "controlled_dry_run_receipt_closeout_ready": closeout_ready, + "receipt_validation_status": receipt_validation_report.get( + "receipt_validation_status" + ), + "dry_run_execution_performed": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "reads_secret_in_preview": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_receipt_closeout = { + "receipt_closeout_id": closeout_id, + "authorization_material_type": "controlled_dry_run_receipt_closeout", + "source_dry_run_package_id": package.get("dry_run_package_id"), + "source_controlled_apply_preflight_id": package.get( + "source_controlled_apply_preflight_id" + ), + "source_dry_run_execution_receipt_id": future_receipt.get( + "dry_run_execution_receipt_id" + ), + "dry_run_result_parser_id": result_parser.get("parser_id"), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_receipt_closeout": ( + closeout_ready + ), + "controlled_dry_run_receipt_closeout_fields": receipt_closeout_fields, + "controlled_dry_run_receipt_closeout_field_count": len( + receipt_closeout_fields + ), + "controlled_dry_run_receipt_closeout_acceptance_gates": ( + receipt_closeout_acceptance_gates + ), + "controlled_dry_run_receipt_closeout_acceptance_gate_count": len( + receipt_closeout_acceptance_gates + ), + "dry_run_result_parser": result_parser, + "dry_run_result_parser_count": 1, + "dry_run_result_parser_field_count": int( + result_parser.get("parser_field_count") or 0 + ), + "receipt_validation_report": receipt_validation_report, + "receipt_validation_report_count": 1, + "receipt_validation_field_count": len(receipt_validation_fields), + "dry_run_command_shape_hash": package.get("dry_run_command_shape_hash"), + "target_file": package.get("target_file"), + "expected_sha256": package.get("expected_sha256"), + "actual_sha256": package.get("actual_sha256"), + "hash_matches": package.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "dry_run_only": True, + "check_mode_only": True, + "receipt_preview_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + } + controlled_dry_run_receipt_closeout_contract = { + "mode": "controlled_dry_run_receipt_closeout_and_result_parser_verification_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-receipt-closeout" + ), + "source_dry_run_package_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-package" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_runner_readiness": ( + closeout_ready + ), + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": 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", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_RECEIPT_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(package_result.get("success")), + "generated_at": package_result.get("generated_at"), + "source_policy": package_result.get("policy"), + "stats": package_result.get("stats") or {}, + "summary": { + "controlled_dry_run_receipt_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_receipt_closeout_check_count": len(checks), + "controlled_dry_run_receipt_closeout_pass_count": passed_count, + "controlled_dry_run_receipt_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_package_ready_count": summary.get( + "controlled_dry_run_package_ready_count", 0 + ), + "controlled_dry_run_package_check_count": summary.get( + "controlled_dry_run_package_check_count", 0 + ), + "controlled_apply_final_preflight_ready_count": summary.get( + "controlled_apply_final_preflight_ready_count", 0 + ), + "controlled_apply_final_preflight_check_count": summary.get( + "controlled_apply_final_preflight_check_count", 0 + ), + "authorization_evidence_execution_closeout_ready_count": summary.get( + "authorization_evidence_execution_closeout_ready_count", 0 + ), + "authorization_evidence_execution_closeout_check_count": summary.get( + "authorization_evidence_execution_closeout_check_count", 0 + ), + "authorization_evidence_execution_preflight_ready_count": summary.get( + "authorization_evidence_execution_preflight_ready_count", 0 + ), + "authorization_evidence_execution_preflight_check_count": summary.get( + "authorization_evidence_execution_preflight_check_count", 0 + ), + "authorization_verifier_receipt_closeout_ready_count": summary.get( + "authorization_verifier_receipt_closeout_ready_count", 0 + ), + "verifier_receipt_closeout_check_count": summary.get( + "verifier_receipt_closeout_check_count", 0 + ), + "database_apply_final_verifier_gate_count": summary.get( + "database_apply_final_verifier_gate_count", 0 + ), + "database_apply_authorization_final_verifier_gate_ready_count": ( + summary.get( + "database_apply_authorization_final_verifier_gate_ready_count", + 0, + ) + ), + "controlled_dry_run_receipt_closeout_count": 1, + "controlled_dry_run_receipt_closeout_field_count": len( + receipt_closeout_fields + ), + "controlled_dry_run_receipt_closeout_acceptance_gate_count": len( + receipt_closeout_acceptance_gates + ), + "dry_run_result_parser_count": 1, + "dry_run_result_parser_field_count": int( + result_parser.get("parser_field_count") or 0 + ), + "receipt_validation_report_count": 1, + "receipt_validation_field_count": len(receipt_validation_fields), + "dry_run_execution_receipt_preview_count": summary.get( + "dry_run_execution_receipt_preview_count", 0 + ), + "dry_run_execution_receipt_field_count": summary.get( + "dry_run_execution_receipt_field_count", 0 + ), + "controlled_dry_run_package_count": summary.get( + "controlled_dry_run_package_count", 0 + ), + "controlled_dry_run_package_field_count": summary.get( + "controlled_dry_run_package_field_count", 0 + ), + "controlled_dry_run_acceptance_gate_count": summary.get( + "controlled_dry_run_acceptance_gate_count", 0 + ), + "rollback_binding_count": summary.get("rollback_binding_count", 0), + "post_apply_verifier_binding_count": summary.get( + "post_apply_verifier_binding_count", 0 + ), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + "same_run_truth_required_count": summary.get( + "same_run_truth_required_count", 0 + ), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + }, + "future_database_apply_controlled_dry_run_result_parser_verification": ( + future_database_apply_controlled_dry_run_result_parser_verification + ), + "controlled_dry_run_receipt_closeout": controlled_dry_run_receipt_closeout, + "controlled_dry_run_receipt_closeout_contract": ( + controlled_dry_run_receipt_closeout_contract + ), + "controlled_dry_run_receipt_closeout_checks": checks, + "source_controlled_dry_run_package_summary": summary, + "source_controlled_dry_run_package_contract": package_contract, + "source_controlled_dry_run_package": package, + "source_database_apply_controlled_dry_run_execution_receipt": ( + future_receipt + ), + "safety": { + "read_only_db_apply_controlled_dry_run_receipt_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future controlled dry-run runner readiness package.", + "Keep result parsing bound to receipt preview fields and command-shape hash before any execution lane.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_runner_readiness( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Bind a future dry-run runner execution plan without authorizing it.""" + receipt_closeout_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_receipt_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_verification = ( + receipt_closeout_result.get( + "future_database_apply_controlled_dry_run_result_parser_verification" + ) + or {} + ) + receipt_closeout = ( + receipt_closeout_result.get("controlled_dry_run_receipt_closeout") or {} + ) + closeout_contract = ( + receipt_closeout_result.get("controlled_dry_run_receipt_closeout_contract") + or {} + ) + summary = receipt_closeout_result.get("summary") or {} + safety = receipt_closeout_result.get("safety") or {} + result_parser = receipt_closeout.get("dry_run_result_parser") or {} + validation = receipt_closeout.get("receipt_validation_report") or {} + rollback_binding = receipt_closeout.get("rollback_binding") or {} + verifier_binding = receipt_closeout.get("post_apply_verifier_binding") or {} + runner_readiness_id = _db_apply_controlled_dry_run_runner_readiness_id( + receipt_closeout_result + ) + execution_plan_binding_id = f"{runner_readiness_id}-execution-plan-binding" + runner_readiness_fields = [ + "runner_readiness_id", + "source_receipt_closeout_id", + "source_dry_run_package_id", + "source_controlled_apply_preflight_id", + "source_dry_run_execution_receipt_id", + "dry_run_result_parser_id", + "receipt_validation_status", + "dry_run_command_shape_hash", + "execution_plan_binding_id", + "target_migration_file", + "rollback_binding_id", + "post_apply_verifier_binding_id", + ] + runner_readiness_acceptance_gates = [ + "receipt_closeout_ready", + "source_chain_ids_match", + "result_parser_verified", + "receipt_validation_report_ready", + "command_shape_hash_bound", + "execution_plan_binding_preview_only", + "runner_execution_gate_closed", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "closeout_contract_blocks_database_apply", + ] + execution_plan_binding_fields = [ + "execution_plan_binding_id", + "source_receipt_closeout_id", + "source_dry_run_package_id", + "dry_run_command_shape_hash", + "runner_mode", + "plan_status", + "dry_run_only", + "check_mode_only", + "shell_execution_included", + "endpoint_execution_included", + "sql_execution_included", + "database_write_included", + ] + target_hash_locked = ( + receipt_closeout.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(receipt_closeout.get("expected_sha256")) + and bool(receipt_closeout.get("actual_sha256")) + and receipt_closeout.get("expected_sha256") + == receipt_closeout.get("actual_sha256") + and receipt_closeout.get("hash_matches") is True + and receipt_closeout.get("target_migration_hash_locked") is True + ) + result_parser_verified = ( + bool(result_parser.get("parser_id")) + and result_parser.get("parser_id") + == receipt_closeout.get("dry_run_result_parser_id") + and result_parser.get("expected_receipt_status") + == "preview_only_not_executed" + and result_parser.get("required_command_shape_hash") + == receipt_closeout.get("dry_run_command_shape_hash") + and result_parser.get("execution_required") is False + and result_parser.get("stdout_allowed") is False + and result_parser.get("stderr_allowed") is False + and result_parser.get("database_apply_authorized") is False + and int(result_parser.get("parser_field_count") or 0) == 10 + ) + receipt_validation_ready = ( + validation.get("receipt_validation_status") + == "preview_validated_not_executed" + and validation.get("dry_run_command_shape_hash") + == receipt_closeout.get("dry_run_command_shape_hash") + and validation.get("execution_performed") is False + and validation.get("stdout_included") is False + and validation.get("stderr_included") is False + and validation.get("database_apply_authorized") is False + and validation.get("executes_shell") is False + and validation.get("executes_endpoint") is False + and validation.get("executes_sql") is False + and validation.get("writes_database") is False + and validation.get("reads_secret") is False + and int(validation.get("receipt_validation_field_count") or 0) == 8 + ) + command_shape_hash_bound = ( + bool(receipt_closeout.get("dry_run_command_shape_hash")) + and receipt_closeout.get("dry_run_command_shape_hash") + == validation.get("dry_run_command_shape_hash") + == result_parser.get("required_command_shape_hash") + ) + execution_plan_binding = { + "execution_plan_binding_id": execution_plan_binding_id, + "source_runner_readiness_id": runner_readiness_id, + "source_receipt_closeout_id": receipt_closeout.get("receipt_closeout_id"), + "source_dry_run_package_id": receipt_closeout.get("source_dry_run_package_id"), + "dry_run_command_shape_hash": receipt_closeout.get( + "dry_run_command_shape_hash" + ), + "runner_mode": "future_controlled_dry_run_runner_readiness_only", + "plan_status": "plan_binding_preview_not_executable", + "dry_run_only": True, + "check_mode_only": True, + "execution_authorized": False, + "dry_run_execution_authorized": False, + "runner_execution_authorized": False, + "shell_execution_included": False, + "endpoint_execution_included": False, + "sql_execution_included": False, + "database_write_included": False, + "stdout_capture_allowed": False, + "stderr_capture_allowed": False, + "database_apply_authorized": False, + "ready_for_database_apply_now": False, + "execution_plan_binding_field_count": len(execution_plan_binding_fields), + "execution_plan_binding_fields": execution_plan_binding_fields, + } + execution_plan_preview_only = ( + execution_plan_binding.get("dry_run_only") is True + and execution_plan_binding.get("check_mode_only") is True + and execution_plan_binding.get("execution_authorized") is False + and execution_plan_binding.get("dry_run_execution_authorized") is False + and execution_plan_binding.get("runner_execution_authorized") is False + and execution_plan_binding.get("shell_execution_included") is False + and execution_plan_binding.get("endpoint_execution_included") is False + and execution_plan_binding.get("sql_execution_included") is False + and execution_plan_binding.get("database_write_included") is False + and execution_plan_binding.get("stdout_capture_allowed") is False + and execution_plan_binding.get("stderr_capture_allowed") is False + and execution_plan_binding.get("database_apply_authorized") is False + ) + runner_execution_gate_closed = ( + future_verification.get( + "ready_for_future_database_apply_controlled_dry_run_result_parser_verification" + ) + is True + and future_verification.get( + "can_enter_future_database_apply_controlled_dry_run_runner_readiness" + ) + is True + and future_verification.get("dry_run_execution_performed") is False + and future_verification.get("database_apply_authorized") is False + and future_verification.get("executes_database_apply") is False + and future_verification.get("executes_endpoint") is False + and future_verification.get("executes_sql") is False + and future_verification.get("writes_database") is False + and execution_plan_preview_only + ) + side_effect_free = ( + int(summary.get("writes_script_count") or 0) == 0 + and int(summary.get("writes_artifact_count") or 0) == 0 + and int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_migration_count") or 0) == 0 + and int(summary.get("executes_endpoint_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and int(summary.get("signs_database_apply_authorization_count") or 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_script") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("signs_database_apply_authorization") is False + and safety.get("executes_authorization_evidence") is False + and safety.get("executes_database_apply") is False + and receipt_closeout.get("accepts_plaintext_secret") is False + and receipt_closeout.get("reads_secret_in_preview") is False + and receipt_closeout.get("signature_material_included") is False + and receipt_closeout.get("secret_material_included") is False + and receipt_closeout.get("signs_database_apply_authorization") is False + and receipt_closeout.get("executes_authorization_evidence") is False + and receipt_closeout.get("executes_database_apply") is False + and receipt_closeout.get("executes_endpoint_in_preview") is False + and receipt_closeout.get("executes_sql_in_preview") is False + and receipt_closeout.get("writes_database_in_preview") is False + and receipt_validation_ready + and execution_plan_preview_only + ) + checks = [ + _controlled_dry_run_runner_readiness_check( + "receipt_closeout_ready", + receipt_closeout_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_RECEIPT_CLOSEOUT_READY" + and future_verification.get( + "ready_for_future_database_apply_controlled_dry_run_result_parser_verification" + ) + is True + and future_verification.get( + "can_enter_future_database_apply_controlled_dry_run_runner_readiness" + ) + is True + and receipt_closeout.get( + "ready_for_future_database_apply_controlled_dry_run_receipt_closeout" + ) + is True, + { + "result": receipt_closeout_result.get("result"), + "ready_for_future_database_apply_controlled_dry_run_result_parser_verification": ( + future_verification.get( + "ready_for_future_database_apply_controlled_dry_run_result_parser_verification" + ) + ), + "can_enter_future_database_apply_controlled_dry_run_runner_readiness": ( + future_verification.get( + "can_enter_future_database_apply_controlled_dry_run_runner_readiness" + ) + ), + }, + "wait_for_controlled_dry_run_receipt_closeout", + ), + _controlled_dry_run_runner_readiness_check( + "source_chain_ids_present", + bool(receipt_closeout.get("receipt_closeout_id")) + and bool(receipt_closeout.get("source_dry_run_package_id")) + and bool(receipt_closeout.get("source_controlled_apply_preflight_id")) + and bool(receipt_closeout.get("source_dry_run_execution_receipt_id")) + and bool(receipt_closeout.get("dry_run_result_parser_id")), + { + "receipt_closeout_id": receipt_closeout.get("receipt_closeout_id"), + "source_dry_run_package_id": receipt_closeout.get( + "source_dry_run_package_id" + ), + "dry_run_result_parser_id": receipt_closeout.get( + "dry_run_result_parser_id" + ), + }, + "wait_for_runner_readiness_source_chain_ids", + ), + _controlled_dry_run_runner_readiness_check( + "controlled_dry_run_runner_readiness_contract_complete", + len(runner_readiness_fields) == 12 + and len(runner_readiness_acceptance_gates) == 10 + and len(execution_plan_binding_fields) == 12 + and "execution_plan_binding_id" in runner_readiness_fields + and "execution_plan_binding_preview_only" + in runner_readiness_acceptance_gates, + { + "runner_readiness_field_count": len(runner_readiness_fields), + "runner_readiness_acceptance_gate_count": len( + runner_readiness_acceptance_gates + ), + "execution_plan_binding_field_count": len( + execution_plan_binding_fields + ), + }, + "wait_for_runner_readiness_contract", + ), + _controlled_dry_run_runner_readiness_check( + "dry_run_result_parser_verified", + result_parser_verified, + { + "parser_id": result_parser.get("parser_id"), + "parser_field_count": result_parser.get("parser_field_count"), + "execution_required": result_parser.get("execution_required"), + "database_apply_authorized": result_parser.get( + "database_apply_authorized" + ), + }, + "wait_for_dry_run_result_parser_verification", + ), + _controlled_dry_run_runner_readiness_check( + "receipt_validation_report_ready", + receipt_validation_ready, + { + "receipt_validation_status": validation.get( + "receipt_validation_status" + ), + "receipt_validation_field_count": validation.get( + "receipt_validation_field_count" + ), + "execution_performed": validation.get("execution_performed"), + }, + "wait_for_receipt_validation_report", + ), + _controlled_dry_run_runner_readiness_check( + "command_shape_hash_bound", + command_shape_hash_bound, + { + "dry_run_command_shape_hash_present": bool( + receipt_closeout.get("dry_run_command_shape_hash") + ), + "parser_hash_matches": ( + receipt_closeout.get("dry_run_command_shape_hash") + == result_parser.get("required_command_shape_hash") + ), + "validation_hash_matches": ( + receipt_closeout.get("dry_run_command_shape_hash") + == validation.get("dry_run_command_shape_hash") + ), + }, + "abort_on_runner_command_shape_hash_mismatch", + ), + _controlled_dry_run_runner_readiness_check( + "execution_plan_binding_preview_only", + execution_plan_preview_only, + { + "execution_authorized": execution_plan_binding.get( + "execution_authorized" + ), + "runner_execution_authorized": execution_plan_binding.get( + "runner_execution_authorized" + ), + "database_write_included": execution_plan_binding.get( + "database_write_included" + ), + }, + "abort_if_execution_plan_binding_is_executable", + ), + _controlled_dry_run_runner_readiness_check( + "runner_execution_gate_closed", + runner_execution_gate_closed, + { + "dry_run_execution_performed": future_verification.get( + "dry_run_execution_performed" + ), + "database_apply_authorized": future_verification.get( + "database_apply_authorized" + ), + "writes_database": future_verification.get("writes_database"), + }, + "abort_if_runner_execution_gate_opens", + ), + _controlled_dry_run_runner_readiness_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": receipt_closeout.get("target_file"), + "expected_sha256_present": bool(receipt_closeout.get("expected_sha256")), + "actual_sha256_present": bool(receipt_closeout.get("actual_sha256")), + "hash_matches": receipt_closeout.get("hash_matches"), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_runner_readiness_check( + "rollback_and_post_apply_verifier_bindings_carried_forward", + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "database_apply_authorized": verifier_binding.get( + "database_apply_authorized" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_runner_readiness_check( + "closeout_contract_blocks_database_apply", + closeout_contract.get( + "permits_future_database_apply_controlled_dry_run_runner_readiness" + ) + is True + and closeout_contract.get("executes_database_apply") is False + and closeout_contract.get("database_apply_authorized") is False + and closeout_contract.get("ready_for_database_apply_now") is False + and closeout_contract.get("signs_database_apply_authorization") is False + and closeout_contract.get("writes_database") is False, + { + "permits_future_database_apply_controlled_dry_run_runner_readiness": ( + closeout_contract.get( + "permits_future_database_apply_controlled_dry_run_runner_readiness" + ) + ), + "database_apply_authorized": closeout_contract.get( + "database_apply_authorized" + ), + "writes_database": closeout_contract.get("writes_database"), + }, + "abort_if_receipt_closeout_contract_authorizes_database_apply", + ), + _controlled_dry_run_runner_readiness_check( + "preview_has_no_side_effects_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + "execution_plan_preview_only": execution_plan_preview_only, + }, + "abort_on_preview_side_effect_execution_or_signing", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + runner_ready = not waiting_checks + runner_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_RUNNER_READINESS_READY" + if runner_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_RECEIPT_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_execution_plan_binding = { + "runner_readiness_id": runner_readiness_id, + "execution_plan_binding_id": execution_plan_binding_id, + "source_receipt_closeout_id": receipt_closeout.get("receipt_closeout_id"), + "source_dry_run_package_id": receipt_closeout.get("source_dry_run_package_id"), + "source_dry_run_execution_receipt_id": receipt_closeout.get( + "source_dry_run_execution_receipt_id" + ), + "status": runner_status, + "ready_for_future_database_apply_controlled_dry_run_execution_plan_binding": ( + runner_ready + ), + "can_enter_future_database_apply_controlled_dry_run_execution_plan_closeout": ( + runner_ready + ), + "controlled_dry_run_runner_readiness_ready": runner_ready, + "execution_plan_bound": runner_ready, + "dry_run_execution_performed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "reads_secret_in_preview": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_runner_readiness = { + "runner_readiness_id": runner_readiness_id, + "authorization_material_type": "controlled_dry_run_runner_readiness", + "source_receipt_closeout_id": receipt_closeout.get("receipt_closeout_id"), + "source_dry_run_package_id": receipt_closeout.get("source_dry_run_package_id"), + "source_controlled_apply_preflight_id": receipt_closeout.get( + "source_controlled_apply_preflight_id" + ), + "source_dry_run_execution_receipt_id": receipt_closeout.get( + "source_dry_run_execution_receipt_id" + ), + "dry_run_result_parser_id": receipt_closeout.get( + "dry_run_result_parser_id" + ), + "status": runner_status, + "ready_for_future_database_apply_controlled_dry_run_runner_readiness": ( + runner_ready + ), + "controlled_dry_run_runner_readiness_fields": runner_readiness_fields, + "controlled_dry_run_runner_readiness_field_count": len( + runner_readiness_fields + ), + "controlled_dry_run_runner_readiness_acceptance_gates": ( + runner_readiness_acceptance_gates + ), + "controlled_dry_run_runner_readiness_acceptance_gate_count": len( + runner_readiness_acceptance_gates + ), + "execution_plan_binding": execution_plan_binding, + "execution_plan_binding_count": 1, + "execution_plan_binding_field_count": len(execution_plan_binding_fields), + "dry_run_result_parser": result_parser, + "dry_run_result_parser_count": 1, + "receipt_validation_report": validation, + "receipt_validation_report_count": 1, + "dry_run_command_shape_hash": receipt_closeout.get( + "dry_run_command_shape_hash" + ), + "target_file": receipt_closeout.get("target_file"), + "expected_sha256": receipt_closeout.get("expected_sha256"), + "actual_sha256": receipt_closeout.get("actual_sha256"), + "hash_matches": receipt_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "dry_run_only": True, + "check_mode_only": True, + "runner_readiness_only": True, + "execution_plan_preview_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + } + controlled_dry_run_runner_readiness_contract = { + "mode": "controlled_dry_run_runner_readiness_and_execution_plan_binding_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-runner-readiness" + ), + "source_receipt_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-receipt-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_execution_plan_binding": ( + runner_ready + ), + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": 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", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_RUNNER_READINESS_POLICY, + "result": runner_status, + "success": bool(receipt_closeout_result.get("success")), + "generated_at": receipt_closeout_result.get("generated_at"), + "source_policy": receipt_closeout_result.get("policy"), + "stats": receipt_closeout_result.get("stats") or {}, + "summary": { + "controlled_dry_run_runner_readiness_ready_count": ( + 1 if runner_ready else 0 + ), + "controlled_dry_run_runner_readiness_check_count": len(checks), + "controlled_dry_run_runner_readiness_pass_count": passed_count, + "controlled_dry_run_runner_readiness_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_receipt_closeout_ready_count": summary.get( + "controlled_dry_run_receipt_closeout_ready_count", 0 + ), + "controlled_dry_run_receipt_closeout_check_count": summary.get( + "controlled_dry_run_receipt_closeout_check_count", 0 + ), + "controlled_dry_run_package_ready_count": summary.get( + "controlled_dry_run_package_ready_count", 0 + ), + "controlled_dry_run_package_check_count": summary.get( + "controlled_dry_run_package_check_count", 0 + ), + "controlled_apply_final_preflight_ready_count": summary.get( + "controlled_apply_final_preflight_ready_count", 0 + ), + "controlled_apply_final_preflight_check_count": summary.get( + "controlled_apply_final_preflight_check_count", 0 + ), + "authorization_evidence_execution_closeout_ready_count": summary.get( + "authorization_evidence_execution_closeout_ready_count", 0 + ), + "authorization_evidence_execution_closeout_check_count": summary.get( + "authorization_evidence_execution_closeout_check_count", 0 + ), + "authorization_evidence_execution_preflight_ready_count": summary.get( + "authorization_evidence_execution_preflight_ready_count", 0 + ), + "authorization_evidence_execution_preflight_check_count": summary.get( + "authorization_evidence_execution_preflight_check_count", 0 + ), + "database_apply_final_verifier_gate_count": summary.get( + "database_apply_final_verifier_gate_count", 0 + ), + "database_apply_authorization_final_verifier_gate_ready_count": ( + summary.get( + "database_apply_authorization_final_verifier_gate_ready_count", + 0, + ) + ), + "controlled_dry_run_runner_readiness_count": 1, + "controlled_dry_run_runner_readiness_field_count": len( + runner_readiness_fields + ), + "controlled_dry_run_runner_readiness_acceptance_gate_count": len( + runner_readiness_acceptance_gates + ), + "execution_plan_binding_count": 1, + "execution_plan_binding_field_count": len(execution_plan_binding_fields), + "dry_run_result_parser_count": summary.get( + "dry_run_result_parser_count", 0 + ), + "dry_run_result_parser_field_count": summary.get( + "dry_run_result_parser_field_count", 0 + ), + "receipt_validation_report_count": summary.get( + "receipt_validation_report_count", 0 + ), + "receipt_validation_field_count": summary.get( + "receipt_validation_field_count", 0 + ), + "controlled_dry_run_receipt_closeout_count": summary.get( + "controlled_dry_run_receipt_closeout_count", 0 + ), + "controlled_dry_run_receipt_closeout_field_count": summary.get( + "controlled_dry_run_receipt_closeout_field_count", 0 + ), + "controlled_dry_run_receipt_closeout_acceptance_gate_count": summary.get( + "controlled_dry_run_receipt_closeout_acceptance_gate_count", 0 + ), + "rollback_binding_count": summary.get("rollback_binding_count", 0), + "post_apply_verifier_binding_count": summary.get( + "post_apply_verifier_binding_count", 0 + ), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + "same_run_truth_required_count": summary.get( + "same_run_truth_required_count", 0 + ), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + }, + "future_database_apply_controlled_dry_run_execution_plan_binding": ( + future_database_apply_controlled_dry_run_execution_plan_binding + ), + "controlled_dry_run_runner_readiness": controlled_dry_run_runner_readiness, + "controlled_dry_run_runner_readiness_contract": ( + controlled_dry_run_runner_readiness_contract + ), + "controlled_dry_run_runner_readiness_checks": checks, + "source_controlled_dry_run_receipt_closeout_summary": summary, + "source_controlled_dry_run_receipt_closeout_contract": closeout_contract, + "source_controlled_dry_run_receipt_closeout": receipt_closeout, + "source_database_apply_controlled_dry_run_result_parser_verification": ( + future_verification + ), + "safety": { + "read_only_db_apply_controlled_dry_run_runner_readiness": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this readiness package to build a future controlled dry-run execution plan closeout.", + "Keep runner execution authorization closed until a dedicated execution receipt lane is explicit.", + "This readiness package still does not authorize endpoint execution, SQL, DB writes, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_execution_plan_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out a future dry-run execution plan as a non-executable artifact.""" + runner_readiness_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_runner_readiness( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_plan = ( + runner_readiness_result.get( + "future_database_apply_controlled_dry_run_execution_plan_binding" + ) + or {} + ) + runner = ( + runner_readiness_result.get("controlled_dry_run_runner_readiness") or {} + ) + runner_contract = ( + runner_readiness_result.get("controlled_dry_run_runner_readiness_contract") + or {} + ) + summary = runner_readiness_result.get("summary") or {} + safety = runner_readiness_result.get("safety") or {} + execution_plan_binding = runner.get("execution_plan_binding") or {} + validation = runner.get("receipt_validation_report") or {} + result_parser = runner.get("dry_run_result_parser") or {} + rollback_binding = runner.get("rollback_binding") or {} + verifier_binding = runner.get("post_apply_verifier_binding") or {} + closeout_id = _db_apply_controlled_dry_run_execution_plan_closeout_id( + runner_readiness_result + ) + artifact_id = f"{closeout_id}-non-executable-command-artifact" + execution_plan_closeout_fields = [ + "execution_plan_closeout_id", + "source_runner_readiness_id", + "source_execution_plan_binding_id", + "source_receipt_closeout_id", + "source_dry_run_package_id", + "dry_run_command_shape_hash", + "non_executable_command_artifact_id", + "non_executable_command_artifact_sha256", + "target_migration_file", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "abort_conditions", + ] + execution_plan_closeout_acceptance_gates = [ + "runner_readiness_ready", + "source_chain_ids_match", + "execution_plan_binding_preview_only", + "non_executable_command_artifact_bound", + "command_artifact_hash_locked", + "runner_execution_gate_closed", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "runner_contract_blocks_database_apply", + "no_secret_signature_or_database_apply", + ] + non_executable_command_artifact_fields = [ + "artifact_id", + "source_execution_plan_binding_id", + "dry_run_command_shape_hash", + "artifact_type", + "shell_command_included", + "endpoint_execution_included", + "sql_execution_included", + "database_write_included", + "execution_authorized", + "database_apply_authorized", + ] + abort_conditions = [ + "abort_if_runner_readiness_not_ready", + "abort_if_execution_plan_becomes_executable", + "abort_if_command_artifact_contains_shell_command_or_argv", + "abort_if_command_artifact_hash_missing", + "abort_if_target_migration_hash_changes", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_runner_contract_authorizes_database_apply", + "abort_if_any_endpoint_sql_database_write_or_signature_is_requested", + ] + non_executable_command_artifact = { + "artifact_id": artifact_id, + "source_runner_readiness_id": runner.get("runner_readiness_id"), + "source_execution_plan_binding_id": execution_plan_binding.get( + "execution_plan_binding_id" + ), + "source_receipt_closeout_id": runner.get("source_receipt_closeout_id"), + "source_dry_run_package_id": runner.get("source_dry_run_package_id"), + "dry_run_command_shape_hash": runner.get("dry_run_command_shape_hash"), + "artifact_type": "non_executable_command_artifact_reference", + "command_text_included": False, + "argv_included": False, + "shell_command_included": False, + "endpoint_execution_included": False, + "sql_execution_included": False, + "database_write_included": False, + "stdout_capture_allowed": False, + "stderr_capture_allowed": False, + "execution_authorized": False, + "dry_run_execution_authorized": False, + "runner_execution_authorized": False, + "database_apply_authorized": False, + "ready_for_database_apply_now": False, + "non_executable_command_artifact_field_count": len( + non_executable_command_artifact_fields + ), + "non_executable_command_artifact_fields": ( + non_executable_command_artifact_fields + ), + } + artifact_hash_payload = { + key: value + for key, value in non_executable_command_artifact.items() + if key != "non_executable_command_artifact_sha256" + } + artifact_sha256 = hashlib.sha256( + json.dumps(artifact_hash_payload, sort_keys=True).encode("utf-8") + ).hexdigest() + non_executable_command_artifact[ + "non_executable_command_artifact_sha256" + ] = artifact_sha256 + execution_plan_preview_only = ( + execution_plan_binding.get("dry_run_only") is True + and execution_plan_binding.get("check_mode_only") is True + and execution_plan_binding.get("execution_authorized") is False + and execution_plan_binding.get("dry_run_execution_authorized") is False + and execution_plan_binding.get("runner_execution_authorized") is False + and execution_plan_binding.get("shell_execution_included") is False + and execution_plan_binding.get("endpoint_execution_included") is False + and execution_plan_binding.get("sql_execution_included") is False + and execution_plan_binding.get("database_write_included") is False + and execution_plan_binding.get("stdout_capture_allowed") is False + and execution_plan_binding.get("stderr_capture_allowed") is False + and execution_plan_binding.get("database_apply_authorized") is False + and execution_plan_binding.get("ready_for_database_apply_now") is False + ) + non_executable_command_artifact_bound = ( + non_executable_command_artifact.get("artifact_type") + == "non_executable_command_artifact_reference" + and bool(non_executable_command_artifact.get("artifact_id")) + and bool( + non_executable_command_artifact.get("source_execution_plan_binding_id") + ) + and bool(non_executable_command_artifact.get("dry_run_command_shape_hash")) + and non_executable_command_artifact.get("command_text_included") is False + and non_executable_command_artifact.get("argv_included") is False + and non_executable_command_artifact.get("shell_command_included") is False + and non_executable_command_artifact.get("endpoint_execution_included") + is False + and non_executable_command_artifact.get("sql_execution_included") is False + and non_executable_command_artifact.get("database_write_included") is False + and non_executable_command_artifact.get("execution_authorized") is False + and non_executable_command_artifact.get("database_apply_authorized") + is False + and int( + non_executable_command_artifact.get( + "non_executable_command_artifact_field_count" + ) + or 0 + ) + == 10 + ) + command_artifact_hash_locked = ( + bool(artifact_sha256) + and len(artifact_sha256) == 64 + and non_executable_command_artifact.get( + "non_executable_command_artifact_sha256" + ) + == artifact_sha256 + ) + source_chain_ids_match = ( + bool(runner.get("runner_readiness_id")) + and runner.get("runner_readiness_id") == future_plan.get("runner_readiness_id") + and runner.get("runner_readiness_id") + == execution_plan_binding.get("source_runner_readiness_id") + and execution_plan_binding.get("execution_plan_binding_id") + == future_plan.get("execution_plan_binding_id") + and execution_plan_binding.get("execution_plan_binding_id") + == non_executable_command_artifact.get("source_execution_plan_binding_id") + and runner.get("source_receipt_closeout_id") + == execution_plan_binding.get("source_receipt_closeout_id") + and runner.get("source_dry_run_package_id") + == execution_plan_binding.get("source_dry_run_package_id") + ) + receipt_validation_and_parser_carried_forward = ( + result_parser.get("required_command_shape_hash") + == runner.get("dry_run_command_shape_hash") + and result_parser.get("execution_required") is False + and result_parser.get("stdout_allowed") is False + and result_parser.get("stderr_allowed") is False + and result_parser.get("database_apply_authorized") is False + and validation.get("dry_run_command_shape_hash") + == runner.get("dry_run_command_shape_hash") + and validation.get("execution_performed") is False + and validation.get("stdout_included") is False + and validation.get("stderr_included") is False + and validation.get("database_apply_authorized") is False + and validation.get("executes_endpoint") is False + and validation.get("executes_sql") is False + and validation.get("writes_database") is False + ) + runner_execution_gate_closed = ( + future_plan.get("dry_run_execution_performed") is False + and future_plan.get("runner_execution_authorized") is False + and future_plan.get("dry_run_execution_authorized") is False + and future_plan.get("database_apply_authorized") is False + and future_plan.get("executes_database_apply") is False + and future_plan.get("executes_endpoint") is False + and future_plan.get("executes_sql") is False + and future_plan.get("writes_database") is False + and runner.get("runner_execution_authorized") is False + and runner.get("dry_run_execution_authorized") is False + and runner.get("database_apply_authorized") is False + and execution_plan_preview_only + and non_executable_command_artifact_bound + ) + target_hash_locked = ( + runner.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(runner.get("expected_sha256")) + and bool(runner.get("actual_sha256")) + and runner.get("expected_sha256") == runner.get("actual_sha256") + and runner.get("hash_matches") is True + and runner.get("target_migration_hash_locked") is True + ) + rollback_and_verifier_bound = ( + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False + ) + runner_contract_blocks_database_apply = ( + runner_contract.get( + "permits_future_database_apply_controlled_dry_run_execution_plan_binding" + ) + is True + and runner_contract.get("executes_database_apply") is False + and runner_contract.get("database_apply_authorized") is False + and runner_contract.get("ready_for_database_apply_now") is False + and runner_contract.get("signs_database_apply_authorization") is False + and runner_contract.get("writes_database") is False + ) + side_effect_free = ( + int(summary.get("writes_script_count") or 0) == 0 + and int(summary.get("writes_artifact_count") or 0) == 0 + and int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_migration_count") or 0) == 0 + and int(summary.get("executes_endpoint_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and int(summary.get("signs_database_apply_authorization_count") or 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_script") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("signs_database_apply_authorization") is False + and safety.get("executes_authorization_evidence") is False + and safety.get("executes_database_apply") is False + and runner.get("accepts_plaintext_secret") is False + and runner.get("reads_secret_in_preview") is False + and runner.get("signature_material_included") is False + and runner.get("secret_material_included") is False + and runner.get("signs_database_apply_authorization") is False + and runner.get("executes_authorization_evidence") is False + and runner.get("executes_database_apply") is False + and runner.get("executes_endpoint_in_preview") is False + and runner.get("executes_sql_in_preview") is False + and runner.get("writes_database_in_preview") is False + and receipt_validation_and_parser_carried_forward + and execution_plan_preview_only + and non_executable_command_artifact_bound + ) + checks = [ + _controlled_dry_run_execution_plan_closeout_check( + "runner_readiness_ready", + runner_readiness_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_RUNNER_READINESS_READY" + and future_plan.get( + "ready_for_future_database_apply_controlled_dry_run_execution_plan_binding" + ) + is True + and future_plan.get( + "can_enter_future_database_apply_controlled_dry_run_execution_plan_closeout" + ) + is True + and runner.get( + "ready_for_future_database_apply_controlled_dry_run_runner_readiness" + ) + is True, + { + "result": runner_readiness_result.get("result"), + "ready_for_future_database_apply_controlled_dry_run_execution_plan_binding": ( + future_plan.get( + "ready_for_future_database_apply_controlled_dry_run_execution_plan_binding" + ) + ), + "can_enter_future_database_apply_controlled_dry_run_execution_plan_closeout": ( + future_plan.get( + "can_enter_future_database_apply_controlled_dry_run_execution_plan_closeout" + ) + ), + }, + "wait_for_controlled_dry_run_runner_readiness", + ), + _controlled_dry_run_execution_plan_closeout_check( + "source_chain_ids_match", + source_chain_ids_match, + { + "runner_readiness_id": runner.get("runner_readiness_id"), + "execution_plan_binding_id": execution_plan_binding.get( + "execution_plan_binding_id" + ), + "artifact_source_execution_plan_binding_id": ( + non_executable_command_artifact.get( + "source_execution_plan_binding_id" + ) + ), + }, + "wait_for_execution_plan_source_chain_ids", + ), + _controlled_dry_run_execution_plan_closeout_check( + "execution_plan_closeout_contract_complete", + len(execution_plan_closeout_fields) == 12 + and len(execution_plan_closeout_acceptance_gates) == 10 + and len(non_executable_command_artifact_fields) == 10 + and "non_executable_command_artifact_id" + in execution_plan_closeout_fields + and "non_executable_command_artifact_bound" + in execution_plan_closeout_acceptance_gates, + { + "execution_plan_closeout_field_count": len( + execution_plan_closeout_fields + ), + "execution_plan_closeout_acceptance_gate_count": len( + execution_plan_closeout_acceptance_gates + ), + "non_executable_command_artifact_field_count": len( + non_executable_command_artifact_fields + ), + }, + "wait_for_execution_plan_closeout_contract", + ), + _controlled_dry_run_execution_plan_closeout_check( + "execution_plan_binding_preview_only", + execution_plan_preview_only, + { + "execution_authorized": execution_plan_binding.get( + "execution_authorized" + ), + "runner_execution_authorized": execution_plan_binding.get( + "runner_execution_authorized" + ), + "database_write_included": execution_plan_binding.get( + "database_write_included" + ), + }, + "abort_if_execution_plan_binding_is_executable", + ), + _controlled_dry_run_execution_plan_closeout_check( + "non_executable_command_artifact_bound", + non_executable_command_artifact_bound, + { + "artifact_id": non_executable_command_artifact.get("artifact_id"), + "command_text_included": non_executable_command_artifact.get( + "command_text_included" + ), + "argv_included": non_executable_command_artifact.get( + "argv_included" + ), + "database_write_included": non_executable_command_artifact.get( + "database_write_included" + ), + }, + "abort_if_command_artifact_contains_executable_material", + ), + _controlled_dry_run_execution_plan_closeout_check( + "command_artifact_hash_locked", + command_artifact_hash_locked, + { + "artifact_sha256_present": bool(artifact_sha256), + "artifact_sha256_length": len(artifact_sha256), + "artifact_id": artifact_id, + }, + "abort_if_command_artifact_hash_is_not_locked", + ), + _controlled_dry_run_execution_plan_closeout_check( + "receipt_validation_and_parser_carried_forward", + receipt_validation_and_parser_carried_forward, + { + "parser_id": result_parser.get("parser_id"), + "receipt_validation_status": validation.get( + "receipt_validation_status" + ), + "dry_run_command_shape_hash": runner.get( + "dry_run_command_shape_hash" + ), + }, + "wait_for_receipt_validation_and_parser_carry_forward", + ), + _controlled_dry_run_execution_plan_closeout_check( + "runner_execution_gate_closed", + runner_execution_gate_closed, + { + "dry_run_execution_performed": future_plan.get( + "dry_run_execution_performed" + ), + "runner_execution_authorized": future_plan.get( + "runner_execution_authorized" + ), + "writes_database": future_plan.get("writes_database"), + }, + "abort_if_runner_execution_gate_opens", + ), + _controlled_dry_run_execution_plan_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": runner.get("target_file"), + "expected_sha256_present": bool(runner.get("expected_sha256")), + "actual_sha256_present": bool(runner.get("actual_sha256")), + "hash_matches": runner.get("hash_matches"), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_execution_plan_closeout_check( + "rollback_and_post_apply_verifier_bindings_carried_forward", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "database_apply_authorized": verifier_binding.get( + "database_apply_authorized" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_execution_plan_closeout_check( + "runner_contract_blocks_database_apply", + runner_contract_blocks_database_apply, + { + "permits_future_database_apply_controlled_dry_run_execution_plan_binding": ( + runner_contract.get( + "permits_future_database_apply_controlled_dry_run_execution_plan_binding" + ) + ), + "database_apply_authorized": runner_contract.get( + "database_apply_authorized" + ), + "writes_database": runner_contract.get("writes_database"), + }, + "abort_if_runner_contract_authorizes_database_apply", + ), + _controlled_dry_run_execution_plan_closeout_check( + "preview_has_no_side_effects_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + "non_executable_command_artifact_bound": ( + non_executable_command_artifact_bound + ), + }, + "abort_on_preview_side_effect_execution_or_signing", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_EXECUTION_PLAN_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_RUNNER_READINESS" + ) + future_database_apply_controlled_dry_run_command_artifact_verification = { + "execution_plan_closeout_id": closeout_id, + "non_executable_command_artifact_id": artifact_id, + "non_executable_command_artifact_sha256": artifact_sha256, + "source_runner_readiness_id": runner.get("runner_readiness_id"), + "source_execution_plan_binding_id": execution_plan_binding.get( + "execution_plan_binding_id" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_command_artifact_verification": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_command_artifact_closeout": ( + closeout_ready + ), + "execution_plan_closeout_ready": closeout_ready, + "non_executable_command_artifact_verified": closeout_ready, + "dry_run_execution_performed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "reads_secret_in_preview": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_execution_plan_closeout = { + "execution_plan_closeout_id": closeout_id, + "authorization_material_type": "controlled_dry_run_execution_plan_closeout", + "source_runner_readiness_id": runner.get("runner_readiness_id"), + "source_execution_plan_binding_id": execution_plan_binding.get( + "execution_plan_binding_id" + ), + "source_receipt_closeout_id": runner.get("source_receipt_closeout_id"), + "source_dry_run_package_id": runner.get("source_dry_run_package_id"), + "source_controlled_apply_preflight_id": runner.get( + "source_controlled_apply_preflight_id" + ), + "source_dry_run_execution_receipt_id": runner.get( + "source_dry_run_execution_receipt_id" + ), + "dry_run_result_parser_id": runner.get("dry_run_result_parser_id"), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_execution_plan_closeout": ( + closeout_ready + ), + "execution_plan_closeout_fields": execution_plan_closeout_fields, + "execution_plan_closeout_field_count": len( + execution_plan_closeout_fields + ), + "execution_plan_closeout_acceptance_gates": ( + execution_plan_closeout_acceptance_gates + ), + "execution_plan_closeout_acceptance_gate_count": len( + execution_plan_closeout_acceptance_gates + ), + "non_executable_command_artifact": non_executable_command_artifact, + "non_executable_command_artifact_count": 1, + "non_executable_command_artifact_field_count": len( + non_executable_command_artifact_fields + ), + "non_executable_command_artifact_sha256": artifact_sha256, + "execution_plan_binding": execution_plan_binding, + "execution_plan_binding_count": 1, + "execution_plan_binding_field_count": runner.get( + "execution_plan_binding_field_count", 0 + ), + "dry_run_result_parser": result_parser, + "dry_run_result_parser_count": 1, + "receipt_validation_report": validation, + "receipt_validation_report_count": 1, + "dry_run_command_shape_hash": runner.get("dry_run_command_shape_hash"), + "target_file": runner.get("target_file"), + "expected_sha256": runner.get("expected_sha256"), + "actual_sha256": runner.get("actual_sha256"), + "hash_matches": runner.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "execution_plan_closeout_only": True, + "non_executable_command_artifact_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + } + controlled_dry_run_execution_plan_closeout_contract = { + "mode": "controlled_dry_run_execution_plan_closeout_and_non_executable_command_artifact_verification_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-execution-plan-closeout" + ), + "source_runner_readiness_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-runner-readiness" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_command_artifact_verification": ( + closeout_ready + ), + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": 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", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_EXECUTION_PLAN_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(runner_readiness_result.get("success")), + "generated_at": runner_readiness_result.get("generated_at"), + "source_policy": runner_readiness_result.get("policy"), + "stats": runner_readiness_result.get("stats") or {}, + "summary": { + "controlled_dry_run_execution_plan_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_execution_plan_closeout_check_count": len(checks), + "controlled_dry_run_execution_plan_closeout_pass_count": passed_count, + "controlled_dry_run_execution_plan_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_runner_readiness_ready_count": summary.get( + "controlled_dry_run_runner_readiness_ready_count", 0 + ), + "controlled_dry_run_runner_readiness_check_count": summary.get( + "controlled_dry_run_runner_readiness_check_count", 0 + ), + "controlled_dry_run_receipt_closeout_ready_count": summary.get( + "controlled_dry_run_receipt_closeout_ready_count", 0 + ), + "controlled_dry_run_receipt_closeout_check_count": summary.get( + "controlled_dry_run_receipt_closeout_check_count", 0 + ), + "controlled_dry_run_package_ready_count": summary.get( + "controlled_dry_run_package_ready_count", 0 + ), + "controlled_dry_run_package_check_count": summary.get( + "controlled_dry_run_package_check_count", 0 + ), + "controlled_apply_final_preflight_ready_count": summary.get( + "controlled_apply_final_preflight_ready_count", 0 + ), + "controlled_apply_final_preflight_check_count": summary.get( + "controlled_apply_final_preflight_check_count", 0 + ), + "authorization_evidence_execution_closeout_ready_count": summary.get( + "authorization_evidence_execution_closeout_ready_count", 0 + ), + "authorization_evidence_execution_closeout_check_count": summary.get( + "authorization_evidence_execution_closeout_check_count", 0 + ), + "authorization_evidence_execution_preflight_ready_count": summary.get( + "authorization_evidence_execution_preflight_ready_count", 0 + ), + "authorization_evidence_execution_preflight_check_count": summary.get( + "authorization_evidence_execution_preflight_check_count", 0 + ), + "database_apply_final_verifier_gate_count": summary.get( + "database_apply_final_verifier_gate_count", 0 + ), + "database_apply_authorization_final_verifier_gate_ready_count": ( + summary.get( + "database_apply_authorization_final_verifier_gate_ready_count", + 0, + ) + ), + "controlled_dry_run_execution_plan_closeout_count": 1, + "controlled_dry_run_execution_plan_closeout_field_count": len( + execution_plan_closeout_fields + ), + "controlled_dry_run_execution_plan_closeout_acceptance_gate_count": len( + execution_plan_closeout_acceptance_gates + ), + "non_executable_command_artifact_count": 1, + "non_executable_command_artifact_field_count": len( + non_executable_command_artifact_fields + ), + "execution_plan_binding_count": summary.get( + "execution_plan_binding_count", 0 + ), + "execution_plan_binding_field_count": summary.get( + "execution_plan_binding_field_count", 0 + ), + "controlled_dry_run_runner_readiness_count": summary.get( + "controlled_dry_run_runner_readiness_count", 0 + ), + "controlled_dry_run_runner_readiness_field_count": summary.get( + "controlled_dry_run_runner_readiness_field_count", 0 + ), + "controlled_dry_run_runner_readiness_acceptance_gate_count": ( + summary.get( + "controlled_dry_run_runner_readiness_acceptance_gate_count", 0 + ) + ), + "dry_run_result_parser_count": summary.get( + "dry_run_result_parser_count", 0 + ), + "dry_run_result_parser_field_count": summary.get( + "dry_run_result_parser_field_count", 0 + ), + "receipt_validation_report_count": summary.get( + "receipt_validation_report_count", 0 + ), + "receipt_validation_field_count": summary.get( + "receipt_validation_field_count", 0 + ), + "controlled_dry_run_receipt_closeout_count": summary.get( + "controlled_dry_run_receipt_closeout_count", 0 + ), + "controlled_dry_run_receipt_closeout_field_count": summary.get( + "controlled_dry_run_receipt_closeout_field_count", 0 + ), + "controlled_dry_run_receipt_closeout_acceptance_gate_count": summary.get( + "controlled_dry_run_receipt_closeout_acceptance_gate_count", 0 + ), + "rollback_binding_count": summary.get("rollback_binding_count", 0), + "post_apply_verifier_binding_count": summary.get( + "post_apply_verifier_binding_count", 0 + ), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + "same_run_truth_required_count": summary.get( + "same_run_truth_required_count", 0 + ), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + }, + "future_database_apply_controlled_dry_run_command_artifact_verification": ( + future_database_apply_controlled_dry_run_command_artifact_verification + ), + "controlled_dry_run_execution_plan_closeout": ( + controlled_dry_run_execution_plan_closeout + ), + "controlled_dry_run_execution_plan_closeout_contract": ( + controlled_dry_run_execution_plan_closeout_contract + ), + "controlled_dry_run_execution_plan_closeout_checks": checks, + "source_controlled_dry_run_runner_readiness_summary": summary, + "source_controlled_dry_run_runner_readiness_contract": runner_contract, + "source_controlled_dry_run_runner_readiness": runner, + "source_database_apply_controlled_dry_run_execution_plan_binding": future_plan, + "safety": { + "read_only_db_apply_controlled_dry_run_execution_plan_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future controlled dry-run command artifact closeout.", + "Keep command artifacts hash-locked and non-executable before any runner execution receipt lane.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_command_artifact_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out a non-executable command artifact into a receipt preflight.""" + execution_plan_closeout_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_execution_plan_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_artifact = ( + execution_plan_closeout_result.get( + "future_database_apply_controlled_dry_run_command_artifact_verification" + ) + or {} + ) + plan_closeout = ( + execution_plan_closeout_result.get( + "controlled_dry_run_execution_plan_closeout" + ) + or {} + ) + plan_closeout_contract = ( + execution_plan_closeout_result.get( + "controlled_dry_run_execution_plan_closeout_contract" + ) + or {} + ) + summary = execution_plan_closeout_result.get("summary") or {} + safety = execution_plan_closeout_result.get("safety") or {} + artifact = plan_closeout.get("non_executable_command_artifact") or {} + execution_plan_binding = plan_closeout.get("execution_plan_binding") or {} + validation = plan_closeout.get("receipt_validation_report") or {} + result_parser = plan_closeout.get("dry_run_result_parser") or {} + rollback_binding = plan_closeout.get("rollback_binding") or {} + verifier_binding = plan_closeout.get("post_apply_verifier_binding") or {} + closeout_id = _db_apply_controlled_dry_run_command_artifact_closeout_id( + execution_plan_closeout_result + ) + receipt_preflight_id = f"{closeout_id}-runner-execution-receipt-preflight" + command_artifact_closeout_fields = [ + "command_artifact_closeout_id", + "source_execution_plan_closeout_id", + "source_non_executable_command_artifact_id", + "source_execution_plan_binding_id", + "source_runner_readiness_id", + "source_receipt_closeout_id", + "dry_run_command_shape_hash", + "non_executable_command_artifact_sha256", + "runner_execution_receipt_preflight_id", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "abort_conditions", + ] + command_artifact_closeout_acceptance_gates = [ + "execution_plan_closeout_ready", + "source_chain_ids_match", + "non_executable_command_artifact_hash_verified", + "non_executable_artifact_has_no_command_text_or_argv", + "runner_execution_receipt_preflight_bound", + "runner_execution_receipt_preflight_no_execute", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "execution_plan_closeout_contract_blocks_database_apply", + "no_secret_signature_or_database_apply", + ] + runner_execution_receipt_preflight_fields = [ + "preflight_id", + "source_command_artifact_closeout_id", + "source_non_executable_command_artifact_id", + "dry_run_command_shape_hash", + "preflight_status", + "execution_required", + "stdout_capture_allowed", + "stderr_capture_allowed", + "database_apply_authorized", + "writes_database", + ] + abort_conditions = [ + "abort_if_execution_plan_closeout_not_ready", + "abort_if_non_executable_command_artifact_hash_changes", + "abort_if_command_artifact_contains_command_text_or_argv", + "abort_if_runner_execution_receipt_preflight_requests_execution", + "abort_if_stdout_or_stderr_capture_is_requested_before_execution_lane", + "abort_if_target_migration_hash_changes", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_any_endpoint_sql_database_write_or_signature_is_requested", + ] + command_artifact_hash_verified = ( + bool(artifact.get("non_executable_command_artifact_sha256")) + and artifact.get("non_executable_command_artifact_sha256") + == plan_closeout.get("non_executable_command_artifact_sha256") + == future_artifact.get("non_executable_command_artifact_sha256") + and len(str(artifact.get("non_executable_command_artifact_sha256") or "")) + == 64 + ) + non_executable_artifact_has_no_command_text_or_argv = ( + artifact.get("artifact_type") == "non_executable_command_artifact_reference" + and artifact.get("command_text_included") is False + and artifact.get("argv_included") is False + and artifact.get("command_text") is None + and artifact.get("argv") is None + and artifact.get("shell_command_included") is False + and artifact.get("endpoint_execution_included") is False + and artifact.get("sql_execution_included") is False + and artifact.get("database_write_included") is False + and artifact.get("stdout_capture_allowed") is False + and artifact.get("stderr_capture_allowed") is False + and artifact.get("execution_authorized") is False + and artifact.get("dry_run_execution_authorized") is False + and artifact.get("runner_execution_authorized") is False + and artifact.get("database_apply_authorized") is False + ) + runner_execution_receipt_preflight = { + "preflight_id": receipt_preflight_id, + "source_command_artifact_closeout_id": closeout_id, + "source_execution_plan_closeout_id": plan_closeout.get( + "execution_plan_closeout_id" + ), + "source_non_executable_command_artifact_id": artifact.get("artifact_id"), + "source_execution_plan_binding_id": artifact.get( + "source_execution_plan_binding_id" + ), + "source_runner_readiness_id": artifact.get("source_runner_readiness_id"), + "source_receipt_closeout_id": artifact.get("source_receipt_closeout_id"), + "source_dry_run_package_id": artifact.get("source_dry_run_package_id"), + "dry_run_command_shape_hash": artifact.get("dry_run_command_shape_hash"), + "non_executable_command_artifact_sha256": artifact.get( + "non_executable_command_artifact_sha256" + ), + "preflight_status": "preflight_only_not_executed", + "execution_required": False, + "execution_authorized": False, + "dry_run_execution_authorized": False, + "runner_execution_authorized": False, + "shell_execution_included": False, + "endpoint_execution_included": False, + "sql_execution_included": False, + "database_write_included": False, + "stdout_capture_allowed": False, + "stderr_capture_allowed": False, + "execution_performed": False, + "stdout_included": False, + "stderr_included": False, + "database_apply_authorized": False, + "ready_for_database_apply_now": False, + "writes_database": False, + "runner_execution_receipt_preflight_field_count": len( + runner_execution_receipt_preflight_fields + ), + "runner_execution_receipt_preflight_fields": ( + runner_execution_receipt_preflight_fields + ), + } + runner_execution_receipt_preflight_bound = ( + bool(runner_execution_receipt_preflight.get("preflight_id")) + and runner_execution_receipt_preflight.get( + "source_command_artifact_closeout_id" + ) + == closeout_id + and runner_execution_receipt_preflight.get( + "source_non_executable_command_artifact_id" + ) + == artifact.get("artifact_id") + and runner_execution_receipt_preflight.get( + "source_execution_plan_binding_id" + ) + == execution_plan_binding.get("execution_plan_binding_id") + and runner_execution_receipt_preflight.get("dry_run_command_shape_hash") + == plan_closeout.get("dry_run_command_shape_hash") + and int( + runner_execution_receipt_preflight.get( + "runner_execution_receipt_preflight_field_count" + ) + or 0 + ) + == 10 + ) + runner_execution_receipt_preflight_no_execute = ( + runner_execution_receipt_preflight.get("preflight_status") + == "preflight_only_not_executed" + and runner_execution_receipt_preflight.get("execution_required") is False + and runner_execution_receipt_preflight.get("execution_authorized") is False + and runner_execution_receipt_preflight.get("dry_run_execution_authorized") + is False + and runner_execution_receipt_preflight.get("runner_execution_authorized") + is False + and runner_execution_receipt_preflight.get("shell_execution_included") + is False + and runner_execution_receipt_preflight.get("endpoint_execution_included") + is False + and runner_execution_receipt_preflight.get("sql_execution_included") is False + and runner_execution_receipt_preflight.get("database_write_included") + is False + and runner_execution_receipt_preflight.get("stdout_capture_allowed") is False + and runner_execution_receipt_preflight.get("stderr_capture_allowed") is False + and runner_execution_receipt_preflight.get("execution_performed") is False + and runner_execution_receipt_preflight.get("stdout_included") is False + and runner_execution_receipt_preflight.get("stderr_included") is False + and runner_execution_receipt_preflight.get("database_apply_authorized") + is False + and runner_execution_receipt_preflight.get("writes_database") is False + ) + source_chain_ids_match = ( + bool(plan_closeout.get("execution_plan_closeout_id")) + and plan_closeout.get("execution_plan_closeout_id") + == future_artifact.get("execution_plan_closeout_id") + and plan_closeout.get("execution_plan_closeout_id") + == runner_execution_receipt_preflight.get( + "source_execution_plan_closeout_id" + ) + and artifact.get("artifact_id") + == future_artifact.get("non_executable_command_artifact_id") + == runner_execution_receipt_preflight.get( + "source_non_executable_command_artifact_id" + ) + and artifact.get("source_execution_plan_binding_id") + == plan_closeout.get("source_execution_plan_binding_id") + == execution_plan_binding.get("execution_plan_binding_id") + and artifact.get("source_runner_readiness_id") + == plan_closeout.get("source_runner_readiness_id") + ) + result_parser_and_validation_carried_forward = ( + result_parser.get("required_command_shape_hash") + == plan_closeout.get("dry_run_command_shape_hash") + and result_parser.get("execution_required") is False + and result_parser.get("stdout_allowed") is False + and result_parser.get("stderr_allowed") is False + and result_parser.get("database_apply_authorized") is False + and validation.get("dry_run_command_shape_hash") + == plan_closeout.get("dry_run_command_shape_hash") + and validation.get("execution_performed") is False + and validation.get("stdout_included") is False + and validation.get("stderr_included") is False + and validation.get("database_apply_authorized") is False + and validation.get("executes_endpoint") is False + and validation.get("executes_sql") is False + and validation.get("writes_database") is False + ) + target_hash_locked = ( + plan_closeout.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(plan_closeout.get("expected_sha256")) + and bool(plan_closeout.get("actual_sha256")) + and plan_closeout.get("expected_sha256") + == plan_closeout.get("actual_sha256") + and plan_closeout.get("hash_matches") is True + and plan_closeout.get("target_migration_hash_locked") is True + ) + rollback_and_verifier_bound = ( + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False + ) + plan_closeout_contract_blocks_database_apply = ( + plan_closeout_contract.get( + "permits_future_database_apply_controlled_dry_run_command_artifact_verification" + ) + is True + and plan_closeout_contract.get("executes_database_apply") is False + and plan_closeout_contract.get("database_apply_authorized") is False + and plan_closeout_contract.get("ready_for_database_apply_now") is False + and plan_closeout_contract.get("signs_database_apply_authorization") is False + and plan_closeout_contract.get("writes_database") is False + ) + side_effect_free = ( + int(summary.get("writes_script_count") or 0) == 0 + and int(summary.get("writes_artifact_count") or 0) == 0 + and int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_migration_count") or 0) == 0 + and int(summary.get("executes_endpoint_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and int(summary.get("signs_database_apply_authorization_count") or 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_script") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("signs_database_apply_authorization") is False + and safety.get("executes_authorization_evidence") is False + and safety.get("executes_database_apply") is False + and plan_closeout.get("accepts_plaintext_secret") is False + and plan_closeout.get("reads_secret_in_preview") is False + and plan_closeout.get("signature_material_included") is False + and plan_closeout.get("secret_material_included") is False + and plan_closeout.get("signs_database_apply_authorization") is False + and plan_closeout.get("executes_authorization_evidence") is False + and plan_closeout.get("executes_database_apply") is False + and plan_closeout.get("executes_endpoint_in_preview") is False + and plan_closeout.get("executes_sql_in_preview") is False + and plan_closeout.get("writes_database_in_preview") is False + and non_executable_artifact_has_no_command_text_or_argv + and runner_execution_receipt_preflight_no_execute + and result_parser_and_validation_carried_forward + ) + checks = [ + _controlled_dry_run_command_artifact_closeout_check( + "execution_plan_closeout_ready", + execution_plan_closeout_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_EXECUTION_PLAN_CLOSEOUT_READY" + and future_artifact.get( + "ready_for_future_database_apply_controlled_dry_run_command_artifact_verification" + ) + is True + and future_artifact.get( + "can_enter_future_database_apply_controlled_dry_run_command_artifact_closeout" + ) + is True + and plan_closeout.get( + "ready_for_future_database_apply_controlled_dry_run_execution_plan_closeout" + ) + is True, + { + "result": execution_plan_closeout_result.get("result"), + "ready_for_future_database_apply_controlled_dry_run_command_artifact_verification": ( + future_artifact.get( + "ready_for_future_database_apply_controlled_dry_run_command_artifact_verification" + ) + ), + "can_enter_future_database_apply_controlled_dry_run_command_artifact_closeout": ( + future_artifact.get( + "can_enter_future_database_apply_controlled_dry_run_command_artifact_closeout" + ) + ), + }, + "wait_for_controlled_dry_run_execution_plan_closeout", + ), + _controlled_dry_run_command_artifact_closeout_check( + "source_chain_ids_match", + source_chain_ids_match, + { + "execution_plan_closeout_id": plan_closeout.get( + "execution_plan_closeout_id" + ), + "artifact_id": artifact.get("artifact_id"), + "runner_execution_receipt_preflight_id": receipt_preflight_id, + }, + "wait_for_command_artifact_source_chain_ids", + ), + _controlled_dry_run_command_artifact_closeout_check( + "command_artifact_closeout_contract_complete", + len(command_artifact_closeout_fields) == 12 + and len(command_artifact_closeout_acceptance_gates) == 10 + and len(runner_execution_receipt_preflight_fields) == 10 + and "runner_execution_receipt_preflight_id" + in command_artifact_closeout_fields + and "runner_execution_receipt_preflight_no_execute" + in command_artifact_closeout_acceptance_gates, + { + "command_artifact_closeout_field_count": len( + command_artifact_closeout_fields + ), + "command_artifact_closeout_acceptance_gate_count": len( + command_artifact_closeout_acceptance_gates + ), + "runner_execution_receipt_preflight_field_count": len( + runner_execution_receipt_preflight_fields + ), + }, + "wait_for_command_artifact_closeout_contract", + ), + _controlled_dry_run_command_artifact_closeout_check( + "non_executable_command_artifact_hash_verified", + command_artifact_hash_verified, + { + "artifact_sha256_present": bool( + artifact.get("non_executable_command_artifact_sha256") + ), + "artifact_sha256_length": len( + str(artifact.get("non_executable_command_artifact_sha256") or "") + ), + }, + "abort_if_command_artifact_hash_changes", + ), + _controlled_dry_run_command_artifact_closeout_check( + "non_executable_artifact_has_no_command_text_or_argv", + non_executable_artifact_has_no_command_text_or_argv, + { + "command_text_included": artifact.get("command_text_included"), + "argv_included": artifact.get("argv_included"), + "shell_command_included": artifact.get("shell_command_included"), + "database_write_included": artifact.get("database_write_included"), + }, + "abort_if_command_artifact_contains_executable_material", + ), + _controlled_dry_run_command_artifact_closeout_check( + "runner_execution_receipt_preflight_bound", + runner_execution_receipt_preflight_bound, + { + "preflight_id": runner_execution_receipt_preflight.get( + "preflight_id" + ), + "source_non_executable_command_artifact_id": ( + runner_execution_receipt_preflight.get( + "source_non_executable_command_artifact_id" + ) + ), + "runner_execution_receipt_preflight_field_count": ( + runner_execution_receipt_preflight.get( + "runner_execution_receipt_preflight_field_count" + ) + ), + }, + "wait_for_runner_execution_receipt_preflight_binding", + ), + _controlled_dry_run_command_artifact_closeout_check( + "runner_execution_receipt_preflight_no_execute", + runner_execution_receipt_preflight_no_execute, + { + "execution_required": runner_execution_receipt_preflight.get( + "execution_required" + ), + "execution_authorized": runner_execution_receipt_preflight.get( + "execution_authorized" + ), + "stdout_capture_allowed": runner_execution_receipt_preflight.get( + "stdout_capture_allowed" + ), + "writes_database": runner_execution_receipt_preflight.get( + "writes_database" + ), + }, + "abort_if_runner_execution_receipt_preflight_executes", + ), + _controlled_dry_run_command_artifact_closeout_check( + "result_parser_and_receipt_validation_carried_forward", + result_parser_and_validation_carried_forward, + { + "parser_id": result_parser.get("parser_id"), + "receipt_validation_status": validation.get( + "receipt_validation_status" + ), + "dry_run_command_shape_hash": plan_closeout.get( + "dry_run_command_shape_hash" + ), + }, + "wait_for_result_parser_and_receipt_validation_carry_forward", + ), + _controlled_dry_run_command_artifact_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": plan_closeout.get("target_file"), + "expected_sha256_present": bool(plan_closeout.get("expected_sha256")), + "actual_sha256_present": bool(plan_closeout.get("actual_sha256")), + "hash_matches": plan_closeout.get("hash_matches"), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_command_artifact_closeout_check( + "rollback_and_post_apply_verifier_bindings_carried_forward", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "database_apply_authorized": verifier_binding.get( + "database_apply_authorized" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_command_artifact_closeout_check( + "execution_plan_closeout_contract_blocks_database_apply", + plan_closeout_contract_blocks_database_apply, + { + "permits_future_database_apply_controlled_dry_run_command_artifact_verification": ( + plan_closeout_contract.get( + "permits_future_database_apply_controlled_dry_run_command_artifact_verification" + ) + ), + "database_apply_authorized": plan_closeout_contract.get( + "database_apply_authorized" + ), + "writes_database": plan_closeout_contract.get("writes_database"), + }, + "abort_if_execution_plan_closeout_contract_authorizes_database_apply", + ), + _controlled_dry_run_command_artifact_closeout_check( + "preview_has_no_side_effects_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + "runner_execution_receipt_preflight_no_execute": ( + runner_execution_receipt_preflight_no_execute + ), + }, + "abort_on_preview_side_effect_execution_or_signing", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_COMMAND_ARTIFACT_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_EXECUTION_PLAN_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_runner_execution_receipt_preflight = { + "command_artifact_closeout_id": closeout_id, + "runner_execution_receipt_preflight_id": receipt_preflight_id, + "source_execution_plan_closeout_id": plan_closeout.get( + "execution_plan_closeout_id" + ), + "source_non_executable_command_artifact_id": artifact.get("artifact_id"), + "source_execution_plan_binding_id": artifact.get( + "source_execution_plan_binding_id" + ), + "non_executable_command_artifact_sha256": artifact.get( + "non_executable_command_artifact_sha256" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_runner_execution_receipt_preflight": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_runner_execution_receipt_closeout": ( + closeout_ready + ), + "command_artifact_closeout_ready": closeout_ready, + "runner_execution_receipt_preflight_bound": closeout_ready, + "dry_run_execution_performed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "stdout_capture_allowed": False, + "stderr_capture_allowed": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "reads_secret_in_preview": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_command_artifact_closeout = { + "command_artifact_closeout_id": closeout_id, + "authorization_material_type": "controlled_dry_run_command_artifact_closeout", + "source_execution_plan_closeout_id": plan_closeout.get( + "execution_plan_closeout_id" + ), + "source_non_executable_command_artifact_id": artifact.get("artifact_id"), + "source_execution_plan_binding_id": artifact.get( + "source_execution_plan_binding_id" + ), + "source_runner_readiness_id": artifact.get("source_runner_readiness_id"), + "source_receipt_closeout_id": artifact.get("source_receipt_closeout_id"), + "source_dry_run_package_id": artifact.get("source_dry_run_package_id"), + "dry_run_command_shape_hash": artifact.get("dry_run_command_shape_hash"), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_command_artifact_closeout": ( + closeout_ready + ), + "command_artifact_closeout_fields": command_artifact_closeout_fields, + "command_artifact_closeout_field_count": len( + command_artifact_closeout_fields + ), + "command_artifact_closeout_acceptance_gates": ( + command_artifact_closeout_acceptance_gates + ), + "command_artifact_closeout_acceptance_gate_count": len( + command_artifact_closeout_acceptance_gates + ), + "runner_execution_receipt_preflight": ( + runner_execution_receipt_preflight + ), + "runner_execution_receipt_preflight_count": 1, + "runner_execution_receipt_preflight_field_count": len( + runner_execution_receipt_preflight_fields + ), + "non_executable_command_artifact": artifact, + "non_executable_command_artifact_count": 1, + "non_executable_command_artifact_field_count": plan_closeout.get( + "non_executable_command_artifact_field_count", 0 + ), + "non_executable_command_artifact_sha256": artifact.get( + "non_executable_command_artifact_sha256" + ), + "execution_plan_binding": execution_plan_binding, + "execution_plan_binding_count": 1, + "execution_plan_binding_field_count": plan_closeout.get( + "execution_plan_binding_field_count", 0 + ), + "dry_run_result_parser": result_parser, + "dry_run_result_parser_count": 1, + "receipt_validation_report": validation, + "receipt_validation_report_count": 1, + "target_file": plan_closeout.get("target_file"), + "expected_sha256": plan_closeout.get("expected_sha256"), + "actual_sha256": plan_closeout.get("actual_sha256"), + "hash_matches": plan_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "command_artifact_closeout_only": True, + "runner_execution_receipt_preflight_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + } + controlled_dry_run_command_artifact_closeout_contract = { + "mode": "controlled_dry_run_command_artifact_closeout_and_runner_execution_receipt_preflight_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-command-artifact-closeout" + ), + "source_execution_plan_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-execution-plan-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_runner_execution_receipt_preflight": ( + closeout_ready + ), + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": 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", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_COMMAND_ARTIFACT_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(execution_plan_closeout_result.get("success")), + "generated_at": execution_plan_closeout_result.get("generated_at"), + "source_policy": execution_plan_closeout_result.get("policy"), + "stats": execution_plan_closeout_result.get("stats") or {}, + "summary": { + "controlled_dry_run_command_artifact_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_command_artifact_closeout_check_count": len( + checks + ), + "controlled_dry_run_command_artifact_closeout_pass_count": passed_count, + "controlled_dry_run_command_artifact_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_execution_plan_closeout_ready_count": summary.get( + "controlled_dry_run_execution_plan_closeout_ready_count", 0 + ), + "controlled_dry_run_execution_plan_closeout_check_count": summary.get( + "controlled_dry_run_execution_plan_closeout_check_count", 0 + ), + "controlled_dry_run_runner_readiness_ready_count": summary.get( + "controlled_dry_run_runner_readiness_ready_count", 0 + ), + "controlled_dry_run_runner_readiness_check_count": summary.get( + "controlled_dry_run_runner_readiness_check_count", 0 + ), + "controlled_dry_run_receipt_closeout_ready_count": summary.get( + "controlled_dry_run_receipt_closeout_ready_count", 0 + ), + "controlled_dry_run_receipt_closeout_check_count": summary.get( + "controlled_dry_run_receipt_closeout_check_count", 0 + ), + "controlled_dry_run_package_ready_count": summary.get( + "controlled_dry_run_package_ready_count", 0 + ), + "controlled_dry_run_package_check_count": summary.get( + "controlled_dry_run_package_check_count", 0 + ), + "controlled_apply_final_preflight_ready_count": summary.get( + "controlled_apply_final_preflight_ready_count", 0 + ), + "controlled_apply_final_preflight_check_count": summary.get( + "controlled_apply_final_preflight_check_count", 0 + ), + "authorization_evidence_execution_closeout_ready_count": summary.get( + "authorization_evidence_execution_closeout_ready_count", 0 + ), + "authorization_evidence_execution_closeout_check_count": summary.get( + "authorization_evidence_execution_closeout_check_count", 0 + ), + "authorization_evidence_execution_preflight_ready_count": summary.get( + "authorization_evidence_execution_preflight_ready_count", 0 + ), + "authorization_evidence_execution_preflight_check_count": summary.get( + "authorization_evidence_execution_preflight_check_count", 0 + ), + "database_apply_final_verifier_gate_count": summary.get( + "database_apply_final_verifier_gate_count", 0 + ), + "database_apply_authorization_final_verifier_gate_ready_count": ( + summary.get( + "database_apply_authorization_final_verifier_gate_ready_count", + 0, + ) + ), + "controlled_dry_run_command_artifact_closeout_count": 1, + "controlled_dry_run_command_artifact_closeout_field_count": len( + command_artifact_closeout_fields + ), + "controlled_dry_run_command_artifact_closeout_acceptance_gate_count": len( + command_artifact_closeout_acceptance_gates + ), + "runner_execution_receipt_preflight_count": 1, + "runner_execution_receipt_preflight_field_count": len( + runner_execution_receipt_preflight_fields + ), + "controlled_dry_run_execution_plan_closeout_count": summary.get( + "controlled_dry_run_execution_plan_closeout_count", 0 + ), + "controlled_dry_run_execution_plan_closeout_field_count": summary.get( + "controlled_dry_run_execution_plan_closeout_field_count", 0 + ), + "controlled_dry_run_execution_plan_closeout_acceptance_gate_count": ( + summary.get( + "controlled_dry_run_execution_plan_closeout_acceptance_gate_count", + 0, + ) + ), + "non_executable_command_artifact_count": summary.get( + "non_executable_command_artifact_count", 0 + ), + "non_executable_command_artifact_field_count": summary.get( + "non_executable_command_artifact_field_count", 0 + ), + "execution_plan_binding_count": summary.get( + "execution_plan_binding_count", 0 + ), + "execution_plan_binding_field_count": summary.get( + "execution_plan_binding_field_count", 0 + ), + "dry_run_result_parser_count": summary.get( + "dry_run_result_parser_count", 0 + ), + "dry_run_result_parser_field_count": summary.get( + "dry_run_result_parser_field_count", 0 + ), + "receipt_validation_report_count": summary.get( + "receipt_validation_report_count", 0 + ), + "receipt_validation_field_count": summary.get( + "receipt_validation_field_count", 0 + ), + "rollback_binding_count": summary.get("rollback_binding_count", 0), + "post_apply_verifier_binding_count": summary.get( + "post_apply_verifier_binding_count", 0 + ), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + "same_run_truth_required_count": summary.get( + "same_run_truth_required_count", 0 + ), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + }, + "future_database_apply_controlled_dry_run_runner_execution_receipt_preflight": ( + future_database_apply_controlled_dry_run_runner_execution_receipt_preflight + ), + "controlled_dry_run_command_artifact_closeout": ( + controlled_dry_run_command_artifact_closeout + ), + "controlled_dry_run_command_artifact_closeout_contract": ( + controlled_dry_run_command_artifact_closeout_contract + ), + "controlled_dry_run_command_artifact_closeout_checks": checks, + "source_controlled_dry_run_execution_plan_closeout_summary": summary, + "source_controlled_dry_run_execution_plan_closeout_contract": ( + plan_closeout_contract + ), + "source_controlled_dry_run_execution_plan_closeout": plan_closeout, + "source_database_apply_controlled_dry_run_command_artifact_verification": ( + future_artifact + ), + "safety": { + "read_only_db_apply_controlled_dry_run_command_artifact_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future controlled dry-run runner execution receipt closeout.", + "Keep runner execution receipt preflight non-executing until the dedicated receipt lane is explicit.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_runner_execution_receipt_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out the runner receipt preflight without running the runner.""" + command_artifact_closeout_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_command_artifact_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_preflight = ( + command_artifact_closeout_result.get( + "future_database_apply_controlled_dry_run_runner_execution_receipt_preflight" + ) + or {} + ) + command_closeout = ( + command_artifact_closeout_result.get( + "controlled_dry_run_command_artifact_closeout" + ) + or {} + ) + command_closeout_contract = ( + command_artifact_closeout_result.get( + "controlled_dry_run_command_artifact_closeout_contract" + ) + or {} + ) + summary = command_artifact_closeout_result.get("summary") or {} + safety = command_artifact_closeout_result.get("safety") or {} + receipt_preflight = command_closeout.get("runner_execution_receipt_preflight") or {} + artifact = command_closeout.get("non_executable_command_artifact") or {} + execution_plan_binding = command_closeout.get("execution_plan_binding") or {} + validation = command_closeout.get("receipt_validation_report") or {} + result_parser = command_closeout.get("dry_run_result_parser") or {} + rollback_binding = command_closeout.get("rollback_binding") or {} + verifier_binding = command_closeout.get("post_apply_verifier_binding") or {} + closeout_id = _db_apply_controlled_dry_run_runner_execution_receipt_closeout_id( + command_artifact_closeout_result + ) + parser_verification_id = f"{closeout_id}-post-receipt-parser-verification" + runner_execution_receipt_closeout_fields = [ + "runner_execution_receipt_closeout_id", + "source_command_artifact_closeout_id", + "source_runner_execution_receipt_preflight_id", + "source_non_executable_command_artifact_id", + "source_execution_plan_closeout_id", + "dry_run_command_shape_hash", + "non_executable_command_artifact_sha256", + "post_receipt_parser_verification_id", + "target_migration_file", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "abort_conditions", + ] + runner_execution_receipt_closeout_acceptance_gates = [ + "command_artifact_closeout_ready", + "source_chain_ids_match", + "runner_execution_receipt_preflight_no_execute", + "post_receipt_parser_verification_bound", + "post_receipt_parser_blocks_execution", + "receipt_closeout_preview_only", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "command_artifact_closeout_contract_blocks_database_apply", + "no_secret_signature_or_database_apply", + ] + post_receipt_parser_verification_fields = [ + "verification_id", + "source_runner_execution_receipt_preflight_id", + "source_command_artifact_closeout_id", + "expected_preflight_status", + "expected_execution_performed", + "expected_stdout_included", + "expected_stderr_included", + "required_command_shape_hash", + "execution_required", + "database_apply_authorized", + ] + abort_conditions = [ + "abort_if_command_artifact_closeout_not_ready", + "abort_if_runner_execution_receipt_preflight_requests_execution", + "abort_if_receipt_closeout_includes_stdout_or_stderr", + "abort_if_post_receipt_parser_requires_execution", + "abort_if_command_shape_hash_mismatch", + "abort_if_target_migration_hash_changes", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_any_endpoint_sql_database_write_or_signature_is_requested", + ] + receipt_closeout_preview = { + "runner_execution_receipt_closeout_id": closeout_id, + "source_command_artifact_closeout_id": command_closeout.get( + "command_artifact_closeout_id" + ), + "source_runner_execution_receipt_preflight_id": receipt_preflight.get( + "preflight_id" + ), + "source_non_executable_command_artifact_id": artifact.get("artifact_id"), + "source_execution_plan_closeout_id": command_closeout.get( + "source_execution_plan_closeout_id" + ), + "dry_run_command_shape_hash": command_closeout.get( + "dry_run_command_shape_hash" + ), + "non_executable_command_artifact_sha256": artifact.get( + "non_executable_command_artifact_sha256" + ), + "receipt_status": "receipt_closeout_preview_not_executed", + "execution_required": False, + "execution_authorized": False, + "dry_run_execution_authorized": False, + "runner_execution_authorized": False, + "execution_performed": False, + "stdout_included": False, + "stderr_included": False, + "stdout_capture_allowed": False, + "stderr_capture_allowed": False, + "database_apply_authorized": False, + "ready_for_database_apply_now": False, + "writes_database": False, + "executes_endpoint": False, + "executes_sql": False, + "receipt_closeout_preview_field_count": 10, + } + post_receipt_parser_verification = { + "verification_id": parser_verification_id, + "source_runner_execution_receipt_closeout_id": closeout_id, + "source_runner_execution_receipt_preflight_id": receipt_preflight.get( + "preflight_id" + ), + "source_command_artifact_closeout_id": command_closeout.get( + "command_artifact_closeout_id" + ), + "source_non_executable_command_artifact_id": artifact.get("artifact_id"), + "expected_preflight_status": "preflight_only_not_executed", + "expected_receipt_status": "receipt_closeout_preview_not_executed", + "expected_execution_performed": False, + "expected_stdout_included": False, + "expected_stderr_included": False, + "required_command_shape_hash": command_closeout.get( + "dry_run_command_shape_hash" + ), + "execution_required": False, + "stdout_allowed": False, + "stderr_allowed": False, + "database_apply_authorized": False, + "parser_verification_status": "post_receipt_parser_preview_ready", + "post_receipt_parser_verification_field_count": len( + post_receipt_parser_verification_fields + ), + "post_receipt_parser_verification_fields": ( + post_receipt_parser_verification_fields + ), + } + runner_execution_receipt_preflight_no_execute = ( + receipt_preflight.get("preflight_status") == "preflight_only_not_executed" + and receipt_preflight.get("execution_required") is False + and receipt_preflight.get("execution_authorized") is False + and receipt_preflight.get("dry_run_execution_authorized") is False + and receipt_preflight.get("runner_execution_authorized") is False + and receipt_preflight.get("shell_execution_included") is False + and receipt_preflight.get("endpoint_execution_included") is False + and receipt_preflight.get("sql_execution_included") is False + and receipt_preflight.get("database_write_included") is False + and receipt_preflight.get("stdout_capture_allowed") is False + and receipt_preflight.get("stderr_capture_allowed") is False + and receipt_preflight.get("execution_performed") is False + and receipt_preflight.get("stdout_included") is False + and receipt_preflight.get("stderr_included") is False + and receipt_preflight.get("database_apply_authorized") is False + and receipt_preflight.get("writes_database") is False + ) + post_receipt_parser_verification_bound = ( + bool(post_receipt_parser_verification.get("verification_id")) + and post_receipt_parser_verification.get( + "source_runner_execution_receipt_preflight_id" + ) + == receipt_preflight.get("preflight_id") + and post_receipt_parser_verification.get( + "source_command_artifact_closeout_id" + ) + == command_closeout.get("command_artifact_closeout_id") + and post_receipt_parser_verification.get("required_command_shape_hash") + == command_closeout.get("dry_run_command_shape_hash") + and int( + post_receipt_parser_verification.get( + "post_receipt_parser_verification_field_count" + ) + or 0 + ) + == 10 + ) + post_receipt_parser_blocks_execution = ( + post_receipt_parser_verification.get("expected_preflight_status") + == "preflight_only_not_executed" + and post_receipt_parser_verification.get("expected_receipt_status") + == "receipt_closeout_preview_not_executed" + and post_receipt_parser_verification.get("expected_execution_performed") + is False + and post_receipt_parser_verification.get("expected_stdout_included") + is False + and post_receipt_parser_verification.get("expected_stderr_included") + is False + and post_receipt_parser_verification.get("execution_required") is False + and post_receipt_parser_verification.get("stdout_allowed") is False + and post_receipt_parser_verification.get("stderr_allowed") is False + and post_receipt_parser_verification.get("database_apply_authorized") + is False + ) + receipt_closeout_preview_only = ( + receipt_closeout_preview.get("receipt_status") + == "receipt_closeout_preview_not_executed" + and receipt_closeout_preview.get("execution_required") is False + and receipt_closeout_preview.get("execution_authorized") is False + and receipt_closeout_preview.get("dry_run_execution_authorized") is False + and receipt_closeout_preview.get("runner_execution_authorized") is False + and receipt_closeout_preview.get("execution_performed") is False + and receipt_closeout_preview.get("stdout_included") is False + and receipt_closeout_preview.get("stderr_included") is False + and receipt_closeout_preview.get("stdout_capture_allowed") is False + and receipt_closeout_preview.get("stderr_capture_allowed") is False + and receipt_closeout_preview.get("database_apply_authorized") is False + and receipt_closeout_preview.get("writes_database") is False + and receipt_closeout_preview.get("executes_endpoint") is False + and receipt_closeout_preview.get("executes_sql") is False + ) + source_chain_ids_match = ( + bool(command_closeout.get("command_artifact_closeout_id")) + and command_closeout.get("command_artifact_closeout_id") + == future_preflight.get("command_artifact_closeout_id") + == receipt_preflight.get("source_command_artifact_closeout_id") + == receipt_closeout_preview.get("source_command_artifact_closeout_id") + and receipt_preflight.get("preflight_id") + == future_preflight.get("runner_execution_receipt_preflight_id") + == receipt_closeout_preview.get( + "source_runner_execution_receipt_preflight_id" + ) + == post_receipt_parser_verification.get( + "source_runner_execution_receipt_preflight_id" + ) + and artifact.get("artifact_id") + == command_closeout.get("source_non_executable_command_artifact_id") + == receipt_closeout_preview.get("source_non_executable_command_artifact_id") + ) + result_parser_and_validation_carried_forward = ( + result_parser.get("required_command_shape_hash") + == command_closeout.get("dry_run_command_shape_hash") + and result_parser.get("execution_required") is False + and result_parser.get("stdout_allowed") is False + and result_parser.get("stderr_allowed") is False + and result_parser.get("database_apply_authorized") is False + and validation.get("dry_run_command_shape_hash") + == command_closeout.get("dry_run_command_shape_hash") + and validation.get("execution_performed") is False + and validation.get("stdout_included") is False + and validation.get("stderr_included") is False + and validation.get("database_apply_authorized") is False + and validation.get("executes_endpoint") is False + and validation.get("executes_sql") is False + and validation.get("writes_database") is False + ) + target_hash_locked = ( + command_closeout.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(command_closeout.get("expected_sha256")) + and bool(command_closeout.get("actual_sha256")) + and command_closeout.get("expected_sha256") + == command_closeout.get("actual_sha256") + and command_closeout.get("hash_matches") is True + and command_closeout.get("target_migration_hash_locked") is True + ) + rollback_and_verifier_bound = ( + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False + ) + command_closeout_contract_blocks_database_apply = ( + command_closeout_contract.get( + "permits_future_database_apply_controlled_dry_run_runner_execution_receipt_preflight" + ) + is True + and command_closeout_contract.get("executes_database_apply") is False + and command_closeout_contract.get("database_apply_authorized") is False + and command_closeout_contract.get("ready_for_database_apply_now") is False + and command_closeout_contract.get("signs_database_apply_authorization") + is False + and command_closeout_contract.get("writes_database") is False + ) + side_effect_free = ( + int(summary.get("writes_script_count") or 0) == 0 + and int(summary.get("writes_artifact_count") or 0) == 0 + and int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_migration_count") or 0) == 0 + and int(summary.get("executes_endpoint_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and int(summary.get("signs_database_apply_authorization_count") or 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_script") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("signs_database_apply_authorization") is False + and safety.get("executes_authorization_evidence") is False + and safety.get("executes_database_apply") is False + and command_closeout.get("accepts_plaintext_secret") is False + and command_closeout.get("reads_secret_in_preview") is False + and command_closeout.get("signature_material_included") is False + and command_closeout.get("secret_material_included") is False + and command_closeout.get("signs_database_apply_authorization") is False + and command_closeout.get("executes_authorization_evidence") is False + and command_closeout.get("executes_database_apply") is False + and command_closeout.get("executes_endpoint_in_preview") is False + and command_closeout.get("executes_sql_in_preview") is False + and command_closeout.get("writes_database_in_preview") is False + and runner_execution_receipt_preflight_no_execute + and post_receipt_parser_blocks_execution + and receipt_closeout_preview_only + and result_parser_and_validation_carried_forward + ) + checks = [ + _controlled_dry_run_runner_execution_receipt_closeout_check( + "command_artifact_closeout_ready", + command_artifact_closeout_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_COMMAND_ARTIFACT_CLOSEOUT_READY" + and future_preflight.get( + "ready_for_future_database_apply_controlled_dry_run_runner_execution_receipt_preflight" + ) + is True + and future_preflight.get( + "can_enter_future_database_apply_controlled_dry_run_runner_execution_receipt_closeout" + ) + is True + and command_closeout.get( + "ready_for_future_database_apply_controlled_dry_run_command_artifact_closeout" + ) + is True, + { + "result": command_artifact_closeout_result.get("result"), + "ready_for_future_database_apply_controlled_dry_run_runner_execution_receipt_preflight": ( + future_preflight.get( + "ready_for_future_database_apply_controlled_dry_run_runner_execution_receipt_preflight" + ) + ), + "can_enter_future_database_apply_controlled_dry_run_runner_execution_receipt_closeout": ( + future_preflight.get( + "can_enter_future_database_apply_controlled_dry_run_runner_execution_receipt_closeout" + ) + ), + }, + "wait_for_controlled_dry_run_command_artifact_closeout", + ), + _controlled_dry_run_runner_execution_receipt_closeout_check( + "source_chain_ids_match", + source_chain_ids_match, + { + "command_artifact_closeout_id": command_closeout.get( + "command_artifact_closeout_id" + ), + "runner_execution_receipt_preflight_id": receipt_preflight.get( + "preflight_id" + ), + "artifact_id": artifact.get("artifact_id"), + }, + "wait_for_runner_execution_receipt_source_chain_ids", + ), + _controlled_dry_run_runner_execution_receipt_closeout_check( + "runner_execution_receipt_closeout_contract_complete", + len(runner_execution_receipt_closeout_fields) == 12 + and len(runner_execution_receipt_closeout_acceptance_gates) == 10 + and len(post_receipt_parser_verification_fields) == 10 + and "post_receipt_parser_verification_id" + in runner_execution_receipt_closeout_fields + and "post_receipt_parser_verification_bound" + in runner_execution_receipt_closeout_acceptance_gates, + { + "runner_execution_receipt_closeout_field_count": len( + runner_execution_receipt_closeout_fields + ), + "runner_execution_receipt_closeout_acceptance_gate_count": len( + runner_execution_receipt_closeout_acceptance_gates + ), + "post_receipt_parser_verification_field_count": len( + post_receipt_parser_verification_fields + ), + }, + "wait_for_runner_execution_receipt_closeout_contract", + ), + _controlled_dry_run_runner_execution_receipt_closeout_check( + "runner_execution_receipt_preflight_no_execute", + runner_execution_receipt_preflight_no_execute, + { + "execution_required": receipt_preflight.get("execution_required"), + "execution_authorized": receipt_preflight.get( + "execution_authorized" + ), + "stdout_capture_allowed": receipt_preflight.get( + "stdout_capture_allowed" + ), + "writes_database": receipt_preflight.get("writes_database"), + }, + "abort_if_runner_execution_receipt_preflight_executes", + ), + _controlled_dry_run_runner_execution_receipt_closeout_check( + "post_receipt_parser_verification_bound", + post_receipt_parser_verification_bound, + { + "verification_id": post_receipt_parser_verification.get( + "verification_id" + ), + "source_runner_execution_receipt_preflight_id": ( + post_receipt_parser_verification.get( + "source_runner_execution_receipt_preflight_id" + ) + ), + "post_receipt_parser_verification_field_count": ( + post_receipt_parser_verification.get( + "post_receipt_parser_verification_field_count" + ) + ), + }, + "wait_for_post_receipt_parser_verification_binding", + ), + _controlled_dry_run_runner_execution_receipt_closeout_check( + "post_receipt_parser_blocks_execution", + post_receipt_parser_blocks_execution, + { + "expected_execution_performed": ( + post_receipt_parser_verification.get( + "expected_execution_performed" + ) + ), + "execution_required": post_receipt_parser_verification.get( + "execution_required" + ), + "database_apply_authorized": post_receipt_parser_verification.get( + "database_apply_authorized" + ), + }, + "abort_if_post_receipt_parser_requires_execution", + ), + _controlled_dry_run_runner_execution_receipt_closeout_check( + "receipt_closeout_preview_only", + receipt_closeout_preview_only, + { + "receipt_status": receipt_closeout_preview.get("receipt_status"), + "execution_performed": receipt_closeout_preview.get( + "execution_performed" + ), + "stdout_included": receipt_closeout_preview.get("stdout_included"), + "writes_database": receipt_closeout_preview.get("writes_database"), + }, + "abort_if_receipt_closeout_preview_contains_execution", + ), + _controlled_dry_run_runner_execution_receipt_closeout_check( + "result_parser_and_receipt_validation_carried_forward", + result_parser_and_validation_carried_forward, + { + "parser_id": result_parser.get("parser_id"), + "receipt_validation_status": validation.get( + "receipt_validation_status" + ), + "dry_run_command_shape_hash": command_closeout.get( + "dry_run_command_shape_hash" + ), + }, + "wait_for_result_parser_and_receipt_validation_carry_forward", + ), + _controlled_dry_run_runner_execution_receipt_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": command_closeout.get("target_file"), + "expected_sha256_present": bool(command_closeout.get("expected_sha256")), + "actual_sha256_present": bool(command_closeout.get("actual_sha256")), + "hash_matches": command_closeout.get("hash_matches"), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_runner_execution_receipt_closeout_check( + "rollback_and_post_apply_verifier_bindings_carried_forward", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "database_apply_authorized": verifier_binding.get( + "database_apply_authorized" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_runner_execution_receipt_closeout_check( + "command_artifact_closeout_contract_blocks_database_apply", + command_closeout_contract_blocks_database_apply, + { + "permits_future_database_apply_controlled_dry_run_runner_execution_receipt_preflight": ( + command_closeout_contract.get( + "permits_future_database_apply_controlled_dry_run_runner_execution_receipt_preflight" + ) + ), + "database_apply_authorized": command_closeout_contract.get( + "database_apply_authorized" + ), + "writes_database": command_closeout_contract.get("writes_database"), + }, + "abort_if_command_artifact_closeout_contract_authorizes_database_apply", + ), + _controlled_dry_run_runner_execution_receipt_closeout_check( + "preview_has_no_side_effects_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + "receipt_closeout_preview_only": receipt_closeout_preview_only, + }, + "abort_on_preview_side_effect_execution_or_signing", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_RUNNER_EXECUTION_RECEIPT_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_COMMAND_ARTIFACT_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_post_receipt_parser_verification = { + "runner_execution_receipt_closeout_id": closeout_id, + "post_receipt_parser_verification_id": parser_verification_id, + "source_command_artifact_closeout_id": command_closeout.get( + "command_artifact_closeout_id" + ), + "source_runner_execution_receipt_preflight_id": receipt_preflight.get( + "preflight_id" + ), + "source_non_executable_command_artifact_id": artifact.get("artifact_id"), + "non_executable_command_artifact_sha256": artifact.get( + "non_executable_command_artifact_sha256" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_post_receipt_parser_verification": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_post_receipt_parser_closeout": ( + closeout_ready + ), + "runner_execution_receipt_closeout_ready": closeout_ready, + "post_receipt_parser_verification_bound": closeout_ready, + "dry_run_execution_performed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "stdout_capture_allowed": False, + "stderr_capture_allowed": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "reads_secret_in_preview": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_runner_execution_receipt_closeout = { + "runner_execution_receipt_closeout_id": closeout_id, + "authorization_material_type": ( + "controlled_dry_run_runner_execution_receipt_closeout" + ), + "source_command_artifact_closeout_id": command_closeout.get( + "command_artifact_closeout_id" + ), + "source_runner_execution_receipt_preflight_id": receipt_preflight.get( + "preflight_id" + ), + "source_non_executable_command_artifact_id": artifact.get("artifact_id"), + "source_execution_plan_closeout_id": command_closeout.get( + "source_execution_plan_closeout_id" + ), + "source_execution_plan_binding_id": command_closeout.get( + "source_execution_plan_binding_id" + ), + "source_runner_readiness_id": command_closeout.get( + "source_runner_readiness_id" + ), + "source_receipt_closeout_id": command_closeout.get( + "source_receipt_closeout_id" + ), + "source_dry_run_package_id": command_closeout.get("source_dry_run_package_id"), + "dry_run_command_shape_hash": command_closeout.get( + "dry_run_command_shape_hash" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_runner_execution_receipt_closeout": ( + closeout_ready + ), + "runner_execution_receipt_closeout_fields": ( + runner_execution_receipt_closeout_fields + ), + "runner_execution_receipt_closeout_field_count": len( + runner_execution_receipt_closeout_fields + ), + "runner_execution_receipt_closeout_acceptance_gates": ( + runner_execution_receipt_closeout_acceptance_gates + ), + "runner_execution_receipt_closeout_acceptance_gate_count": len( + runner_execution_receipt_closeout_acceptance_gates + ), + "receipt_closeout_preview": receipt_closeout_preview, + "receipt_closeout_preview_count": 1, + "post_receipt_parser_verification": post_receipt_parser_verification, + "post_receipt_parser_verification_count": 1, + "post_receipt_parser_verification_field_count": len( + post_receipt_parser_verification_fields + ), + "runner_execution_receipt_preflight": receipt_preflight, + "runner_execution_receipt_preflight_count": 1, + "non_executable_command_artifact": artifact, + "non_executable_command_artifact_count": 1, + "non_executable_command_artifact_sha256": artifact.get( + "non_executable_command_artifact_sha256" + ), + "execution_plan_binding": execution_plan_binding, + "execution_plan_binding_count": 1, + "dry_run_result_parser": result_parser, + "dry_run_result_parser_count": 1, + "receipt_validation_report": validation, + "receipt_validation_report_count": 1, + "target_file": command_closeout.get("target_file"), + "expected_sha256": command_closeout.get("expected_sha256"), + "actual_sha256": command_closeout.get("actual_sha256"), + "hash_matches": command_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "runner_execution_receipt_closeout_only": True, + "post_receipt_parser_verification_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + } + controlled_dry_run_runner_execution_receipt_closeout_contract = { + "mode": "controlled_dry_run_runner_execution_receipt_closeout_and_post_receipt_parser_verification_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-runner-execution-receipt-closeout" + ), + "source_command_artifact_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-command-artifact-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_post_receipt_parser_verification": ( + closeout_ready + ), + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": 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", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_RUNNER_EXECUTION_RECEIPT_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(command_artifact_closeout_result.get("success")), + "generated_at": command_artifact_closeout_result.get("generated_at"), + "source_policy": command_artifact_closeout_result.get("policy"), + "stats": command_artifact_closeout_result.get("stats") or {}, + "summary": { + "controlled_dry_run_runner_execution_receipt_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_runner_execution_receipt_closeout_check_count": len( + checks + ), + "controlled_dry_run_runner_execution_receipt_closeout_pass_count": ( + passed_count + ), + "controlled_dry_run_runner_execution_receipt_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_command_artifact_closeout_ready_count": summary.get( + "controlled_dry_run_command_artifact_closeout_ready_count", 0 + ), + "controlled_dry_run_command_artifact_closeout_check_count": summary.get( + "controlled_dry_run_command_artifact_closeout_check_count", 0 + ), + "controlled_dry_run_execution_plan_closeout_ready_count": summary.get( + "controlled_dry_run_execution_plan_closeout_ready_count", 0 + ), + "controlled_dry_run_execution_plan_closeout_check_count": summary.get( + "controlled_dry_run_execution_plan_closeout_check_count", 0 + ), + "controlled_dry_run_runner_readiness_ready_count": summary.get( + "controlled_dry_run_runner_readiness_ready_count", 0 + ), + "controlled_dry_run_runner_readiness_check_count": summary.get( + "controlled_dry_run_runner_readiness_check_count", 0 + ), + "controlled_dry_run_receipt_closeout_ready_count": summary.get( + "controlled_dry_run_receipt_closeout_ready_count", 0 + ), + "controlled_dry_run_receipt_closeout_check_count": summary.get( + "controlled_dry_run_receipt_closeout_check_count", 0 + ), + "controlled_dry_run_package_ready_count": summary.get( + "controlled_dry_run_package_ready_count", 0 + ), + "controlled_dry_run_package_check_count": summary.get( + "controlled_dry_run_package_check_count", 0 + ), + "controlled_apply_final_preflight_ready_count": summary.get( + "controlled_apply_final_preflight_ready_count", 0 + ), + "controlled_apply_final_preflight_check_count": summary.get( + "controlled_apply_final_preflight_check_count", 0 + ), + "authorization_evidence_execution_closeout_ready_count": summary.get( + "authorization_evidence_execution_closeout_ready_count", 0 + ), + "authorization_evidence_execution_closeout_check_count": summary.get( + "authorization_evidence_execution_closeout_check_count", 0 + ), + "authorization_evidence_execution_preflight_ready_count": summary.get( + "authorization_evidence_execution_preflight_ready_count", 0 + ), + "authorization_evidence_execution_preflight_check_count": summary.get( + "authorization_evidence_execution_preflight_check_count", 0 + ), + "database_apply_final_verifier_gate_count": summary.get( + "database_apply_final_verifier_gate_count", 0 + ), + "database_apply_authorization_final_verifier_gate_ready_count": ( + summary.get( + "database_apply_authorization_final_verifier_gate_ready_count", + 0, + ) + ), + "controlled_dry_run_runner_execution_receipt_closeout_count": 1, + "controlled_dry_run_runner_execution_receipt_closeout_field_count": len( + runner_execution_receipt_closeout_fields + ), + "controlled_dry_run_runner_execution_receipt_closeout_acceptance_gate_count": len( + runner_execution_receipt_closeout_acceptance_gates + ), + "post_receipt_parser_verification_count": 1, + "post_receipt_parser_verification_field_count": len( + post_receipt_parser_verification_fields + ), + "receipt_closeout_preview_count": 1, + "controlled_dry_run_command_artifact_closeout_count": summary.get( + "controlled_dry_run_command_artifact_closeout_count", 0 + ), + "controlled_dry_run_command_artifact_closeout_field_count": summary.get( + "controlled_dry_run_command_artifact_closeout_field_count", 0 + ), + "controlled_dry_run_command_artifact_closeout_acceptance_gate_count": ( + summary.get( + "controlled_dry_run_command_artifact_closeout_acceptance_gate_count", + 0, + ) + ), + "runner_execution_receipt_preflight_count": summary.get( + "runner_execution_receipt_preflight_count", 0 + ), + "runner_execution_receipt_preflight_field_count": summary.get( + "runner_execution_receipt_preflight_field_count", 0 + ), + "non_executable_command_artifact_count": summary.get( + "non_executable_command_artifact_count", 0 + ), + "non_executable_command_artifact_field_count": summary.get( + "non_executable_command_artifact_field_count", 0 + ), + "execution_plan_binding_count": summary.get( + "execution_plan_binding_count", 0 + ), + "execution_plan_binding_field_count": summary.get( + "execution_plan_binding_field_count", 0 + ), + "dry_run_result_parser_count": summary.get( + "dry_run_result_parser_count", 0 + ), + "dry_run_result_parser_field_count": summary.get( + "dry_run_result_parser_field_count", 0 + ), + "receipt_validation_report_count": summary.get( + "receipt_validation_report_count", 0 + ), + "receipt_validation_field_count": summary.get( + "receipt_validation_field_count", 0 + ), + "rollback_binding_count": summary.get("rollback_binding_count", 0), + "post_apply_verifier_binding_count": summary.get( + "post_apply_verifier_binding_count", 0 + ), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + "same_run_truth_required_count": summary.get( + "same_run_truth_required_count", 0 + ), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + }, + "future_database_apply_controlled_dry_run_post_receipt_parser_verification": ( + future_database_apply_controlled_dry_run_post_receipt_parser_verification + ), + "controlled_dry_run_runner_execution_receipt_closeout": ( + controlled_dry_run_runner_execution_receipt_closeout + ), + "controlled_dry_run_runner_execution_receipt_closeout_contract": ( + controlled_dry_run_runner_execution_receipt_closeout_contract + ), + "controlled_dry_run_runner_execution_receipt_closeout_checks": checks, + "source_controlled_dry_run_command_artifact_closeout_summary": summary, + "source_controlled_dry_run_command_artifact_closeout_contract": ( + command_closeout_contract + ), + "source_controlled_dry_run_command_artifact_closeout": command_closeout, + "source_database_apply_controlled_dry_run_runner_execution_receipt_preflight": ( + future_preflight + ), + "safety": { + "read_only_db_apply_controlled_dry_run_runner_execution_receipt_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future controlled dry-run post-receipt parser closeout.", + "Keep parser verification preview-only until a dedicated execution lane is explicit.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_post_receipt_parser_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out the post-receipt parser and bind no-apply enforcement.""" + runner_closeout_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_runner_execution_receipt_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_parser = ( + runner_closeout_result.get( + "future_database_apply_controlled_dry_run_post_receipt_parser_verification" + ) + or {} + ) + runner_closeout = ( + runner_closeout_result.get( + "controlled_dry_run_runner_execution_receipt_closeout" + ) + or {} + ) + runner_contract = ( + runner_closeout_result.get( + "controlled_dry_run_runner_execution_receipt_closeout_contract" + ) + or {} + ) + summary = runner_closeout_result.get("summary") or {} + safety = runner_closeout_result.get("safety") or {} + parser = runner_closeout.get("post_receipt_parser_verification") or {} + preview = runner_closeout.get("receipt_closeout_preview") or {} + preflight = runner_closeout.get("runner_execution_receipt_preflight") or {} + artifact = runner_closeout.get("non_executable_command_artifact") or {} + result_parser = runner_closeout.get("dry_run_result_parser") or {} + validation = runner_closeout.get("receipt_validation_report") or {} + rollback_binding = runner_closeout.get("rollback_binding") or {} + verifier_binding = runner_closeout.get("post_apply_verifier_binding") or {} + closeout_id = _db_apply_controlled_dry_run_post_receipt_parser_closeout_id( + runner_closeout_result + ) + no_apply_enforcement_verification_id = ( + f"{closeout_id}-no-apply-enforcement-verification" + ) + post_receipt_parser_closeout_fields = [ + "post_receipt_parser_closeout_id", + "source_runner_execution_receipt_closeout_id", + "source_post_receipt_parser_verification_id", + "source_command_artifact_closeout_id", + "source_non_executable_command_artifact_id", + "source_receipt_closeout_preview_status", + "dry_run_command_shape_hash", + "no_apply_enforcement_verification_id", + "target_migration_file", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "abort_conditions", + ] + post_receipt_parser_closeout_acceptance_gates = [ + "runner_execution_receipt_closeout_ready", + "source_chain_ids_match", + "post_receipt_parser_verification_ready", + "post_receipt_parser_blocks_execution", + "receipt_closeout_preview_not_executed", + "no_apply_enforcement_verification_bound", + "no_apply_enforcement_blocks_endpoint_sql_db_write", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "no_secret_signature_or_database_apply", + ] + no_apply_enforcement_verification_fields = [ + "verification_id", + "source_post_receipt_parser_verification_id", + "source_runner_execution_receipt_closeout_id", + "required_parser_status", + "required_receipt_status", + "required_command_shape_hash", + "expected_execution_performed", + "endpoint_execution_allowed", + "sql_execution_allowed", + "database_write_allowed", + "database_apply_authorized", + "enforcement_status", + ] + abort_conditions = [ + "abort_if_runner_execution_receipt_closeout_not_ready", + "abort_if_post_receipt_parser_verification_missing", + "abort_if_parser_verification_requires_execution", + "abort_if_receipt_closeout_was_executed", + "abort_if_receipt_closeout_includes_stdout_or_stderr", + "abort_if_no_apply_enforcement_allows_endpoint_or_sql", + "abort_if_no_apply_enforcement_allows_database_write_or_apply", + "abort_if_command_shape_hash_mismatch", + "abort_if_target_migration_hash_changes", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_any_secret_signature_or_apply_material_is_present", + ] + no_apply_enforcement_verification = { + "verification_id": no_apply_enforcement_verification_id, + "source_post_receipt_parser_closeout_id": closeout_id, + "source_post_receipt_parser_verification_id": parser.get("verification_id"), + "source_runner_execution_receipt_closeout_id": runner_closeout.get( + "runner_execution_receipt_closeout_id" + ), + "source_command_artifact_closeout_id": runner_closeout.get( + "source_command_artifact_closeout_id" + ), + "required_parser_status": "post_receipt_parser_preview_ready", + "required_receipt_status": "receipt_closeout_preview_not_executed", + "required_command_shape_hash": parser.get("required_command_shape_hash"), + "expected_execution_performed": False, + "expected_stdout_included": False, + "expected_stderr_included": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "database_apply_authorized": False, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "enforcement_status": "no_apply_enforcement_preview_ready", + "no_apply_enforcement_verification_field_count": len( + no_apply_enforcement_verification_fields + ), + "no_apply_enforcement_verification_fields": ( + no_apply_enforcement_verification_fields + ), + } + runner_closeout_ready = ( + runner_closeout_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_RUNNER_EXECUTION_RECEIPT_CLOSEOUT_READY" + and summary.get("controlled_dry_run_runner_execution_receipt_closeout_ready_count") + == 1 + and summary.get("controlled_dry_run_runner_execution_receipt_closeout_pass_count") + == summary.get("controlled_dry_run_runner_execution_receipt_closeout_check_count") + ) + source_chain_ids_match = ( + bool(runner_closeout.get("runner_execution_receipt_closeout_id")) + and runner_closeout.get("runner_execution_receipt_closeout_id") + == future_parser.get("runner_execution_receipt_closeout_id") + == no_apply_enforcement_verification.get( + "source_runner_execution_receipt_closeout_id" + ) + and parser.get("verification_id") + == future_parser.get("post_receipt_parser_verification_id") + == no_apply_enforcement_verification.get( + "source_post_receipt_parser_verification_id" + ) + and artifact.get("artifact_id") + == runner_closeout.get("source_non_executable_command_artifact_id") + ) + post_receipt_parser_verification_ready = ( + parser.get("parser_verification_status") == "post_receipt_parser_preview_ready" + and parser.get("expected_preflight_status") == "preflight_only_not_executed" + and parser.get("expected_receipt_status") + == "receipt_closeout_preview_not_executed" + and parser.get("verification_id") + == future_parser.get("post_receipt_parser_verification_id") + and int(parser.get("post_receipt_parser_verification_field_count") or 0) + == 10 + ) + post_receipt_parser_blocks_execution = ( + parser.get("expected_execution_performed") is False + and parser.get("expected_stdout_included") is False + and parser.get("expected_stderr_included") is False + and parser.get("execution_required") is False + and parser.get("stdout_allowed") is False + and parser.get("stderr_allowed") is False + and parser.get("database_apply_authorized") is False + and no_apply_enforcement_verification.get("expected_execution_performed") + is False + ) + receipt_closeout_preview_not_executed = ( + preview.get("receipt_status") == "receipt_closeout_preview_not_executed" + and preview.get("execution_required") is False + and preview.get("execution_performed") is False + and preview.get("stdout_included") is False + and preview.get("stderr_included") is False + and preview.get("stdout_capture_allowed") is False + and preview.get("stderr_capture_allowed") is False + and preview.get("database_apply_authorized") is False + and preview.get("writes_database") is False + and preview.get("executes_endpoint") is False + and preview.get("executes_sql") is False + ) + no_apply_enforcement_verification_bound = ( + bool(no_apply_enforcement_verification.get("verification_id")) + and no_apply_enforcement_verification.get( + "source_post_receipt_parser_closeout_id" + ) + == closeout_id + and no_apply_enforcement_verification.get( + "source_post_receipt_parser_verification_id" + ) + == parser.get("verification_id") + and no_apply_enforcement_verification.get("required_command_shape_hash") + == parser.get("required_command_shape_hash") + and int( + no_apply_enforcement_verification.get( + "no_apply_enforcement_verification_field_count" + ) + or 0 + ) + == len(no_apply_enforcement_verification_fields) + ) + no_apply_enforcement_blocks_endpoint_sql_db_write = ( + no_apply_enforcement_verification.get("endpoint_execution_allowed") is False + and no_apply_enforcement_verification.get("sql_execution_allowed") is False + and no_apply_enforcement_verification.get("database_write_allowed") is False + and no_apply_enforcement_verification.get("database_apply_authorized") + is False + and no_apply_enforcement_verification.get("executes_endpoint") is False + and no_apply_enforcement_verification.get("executes_sql") is False + and no_apply_enforcement_verification.get("writes_database") is False + and no_apply_enforcement_verification.get("executes_database_apply") + is False + ) + result_parser_and_validation_carried_forward = ( + result_parser.get("required_command_shape_hash") + == parser.get("required_command_shape_hash") + and result_parser.get("execution_required") is False + and result_parser.get("stdout_allowed") is False + and result_parser.get("stderr_allowed") is False + and result_parser.get("database_apply_authorized") is False + and validation.get("receipt_validation_status") + == "preview_validated_not_executed" + and validation.get("execution_performed") is False + and validation.get("stdout_included") is False + and validation.get("stderr_included") is False + and validation.get("database_apply_authorized") is False + and validation.get("executes_endpoint") is False + and validation.get("executes_sql") is False + and validation.get("writes_database") is False + ) + target_hash_locked = ( + runner_closeout.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(runner_closeout.get("expected_sha256")) + and bool(runner_closeout.get("actual_sha256")) + and runner_closeout.get("expected_sha256") + == runner_closeout.get("actual_sha256") + and runner_closeout.get("hash_matches") is True + and runner_closeout.get("target_migration_hash_locked") is True + ) + rollback_and_verifier_bound = ( + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False + ) + runner_contract_blocks_database_apply = ( + runner_contract.get("executes_database_apply") is False + and runner_contract.get("executes_endpoint") is False + and runner_contract.get("executes_sql") is False + and runner_contract.get("database_apply_authorized") is False + and runner_contract.get("ready_for_database_apply_now") is False + and runner_contract.get("signs_database_apply_authorization") is False + and runner_contract.get("writes_database") is False + and runner_contract.get("executes_in_preview") is False + ) + side_effect_free = ( + summary.get("reads_secret_count", 0) == 0 + and summary.get("executes_script_count", 0) == 0 + and summary.get("executes_migration_count", 0) == 0 + and summary.get("executes_endpoint_count", 0) == 0 + and summary.get("executes_sql_count", 0) == 0 + and summary.get("writes_database_count", 0) == 0 + and summary.get("signs_database_apply_authorization_count", 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("executes_database_apply") is False + and no_apply_enforcement_blocks_endpoint_sql_db_write + ) + checks = [ + _controlled_dry_run_post_receipt_parser_closeout_check( + "runner_execution_receipt_closeout_ready", + runner_closeout_ready, + { + "result": runner_closeout_result.get("result"), + "ready_count": summary.get( + "controlled_dry_run_runner_execution_receipt_closeout_ready_count" + ), + "pass_count": summary.get( + "controlled_dry_run_runner_execution_receipt_closeout_pass_count" + ), + "check_count": summary.get( + "controlled_dry_run_runner_execution_receipt_closeout_check_count" + ), + }, + "wait_for_runner_execution_receipt_closeout_ready", + ), + _controlled_dry_run_post_receipt_parser_closeout_check( + "source_chain_ids_match", + source_chain_ids_match, + { + "runner_execution_receipt_closeout_id": runner_closeout.get( + "runner_execution_receipt_closeout_id" + ), + "post_receipt_parser_verification_id": parser.get( + "verification_id" + ), + "artifact_id": artifact.get("artifact_id"), + }, + "wait_for_source_chain_ids_match", + ), + _controlled_dry_run_post_receipt_parser_closeout_check( + "post_receipt_parser_verification_ready", + post_receipt_parser_verification_ready, + { + "parser_verification_status": parser.get( + "parser_verification_status" + ), + "verification_id": parser.get("verification_id"), + "field_count": parser.get( + "post_receipt_parser_verification_field_count" + ), + }, + "wait_for_post_receipt_parser_verification_ready", + ), + _controlled_dry_run_post_receipt_parser_closeout_check( + "post_receipt_parser_blocks_execution", + post_receipt_parser_blocks_execution, + { + "expected_execution_performed": parser.get( + "expected_execution_performed" + ), + "execution_required": parser.get("execution_required"), + "database_apply_authorized": parser.get( + "database_apply_authorized" + ), + }, + "abort_if_post_receipt_parser_allows_execution", + ), + _controlled_dry_run_post_receipt_parser_closeout_check( + "receipt_closeout_preview_not_executed", + receipt_closeout_preview_not_executed, + { + "receipt_status": preview.get("receipt_status"), + "execution_performed": preview.get("execution_performed"), + "stdout_included": preview.get("stdout_included"), + "stderr_included": preview.get("stderr_included"), + }, + "abort_if_receipt_closeout_was_executed", + ), + _controlled_dry_run_post_receipt_parser_closeout_check( + "no_apply_enforcement_verification_bound", + no_apply_enforcement_verification_bound, + { + "verification_id": no_apply_enforcement_verification.get( + "verification_id" + ), + "source_post_receipt_parser_verification_id": ( + no_apply_enforcement_verification.get( + "source_post_receipt_parser_verification_id" + ) + ), + "field_count": no_apply_enforcement_verification.get( + "no_apply_enforcement_verification_field_count" + ), + }, + "wait_for_no_apply_enforcement_verification_binding", + ), + _controlled_dry_run_post_receipt_parser_closeout_check( + "no_apply_enforcement_blocks_endpoint_sql_db_write", + no_apply_enforcement_blocks_endpoint_sql_db_write, + { + "endpoint_execution_allowed": no_apply_enforcement_verification.get( + "endpoint_execution_allowed" + ), + "sql_execution_allowed": no_apply_enforcement_verification.get( + "sql_execution_allowed" + ), + "database_write_allowed": no_apply_enforcement_verification.get( + "database_write_allowed" + ), + "database_apply_authorized": no_apply_enforcement_verification.get( + "database_apply_authorized" + ), + }, + "abort_if_no_apply_enforcement_allows_endpoint_sql_or_db_write", + ), + _controlled_dry_run_post_receipt_parser_closeout_check( + "result_parser_and_receipt_validation_carried_forward", + result_parser_and_validation_carried_forward, + { + "parser_id": result_parser.get("parser_id"), + "receipt_validation_status": validation.get( + "receipt_validation_status" + ), + "dry_run_command_shape_hash": parser.get( + "required_command_shape_hash" + ), + }, + "wait_for_result_parser_and_receipt_validation_carry_forward", + ), + _controlled_dry_run_post_receipt_parser_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": runner_closeout.get("target_file"), + "hash_matches": runner_closeout.get("hash_matches"), + "expected_sha256_present": bool(runner_closeout.get("expected_sha256")), + "actual_sha256_present": bool(runner_closeout.get("actual_sha256")), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_post_receipt_parser_closeout_check( + "rollback_and_post_apply_verifier_bindings_carried_forward", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "database_apply_authorized": verifier_binding.get( + "database_apply_authorized" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_post_receipt_parser_closeout_check( + "runner_execution_receipt_contract_blocks_database_apply", + runner_contract_blocks_database_apply, + { + "permits_future_database_apply_controlled_dry_run_post_receipt_parser_verification": ( + runner_contract.get( + "permits_future_database_apply_controlled_dry_run_post_receipt_parser_verification" + ) + ), + "database_apply_authorized": runner_contract.get( + "database_apply_authorized" + ), + "writes_database": runner_contract.get("writes_database"), + }, + "abort_if_runner_execution_receipt_contract_authorizes_database_apply", + ), + _controlled_dry_run_post_receipt_parser_closeout_check( + "preview_has_no_side_effects_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + }, + "abort_on_preview_side_effect_execution_or_signing", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_POST_RECEIPT_PARSER_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_RUNNER_EXECUTION_RECEIPT_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_no_apply_enforcement_verification = { + "post_receipt_parser_closeout_id": closeout_id, + "no_apply_enforcement_verification_id": no_apply_enforcement_verification_id, + "source_runner_execution_receipt_closeout_id": runner_closeout.get( + "runner_execution_receipt_closeout_id" + ), + "source_post_receipt_parser_verification_id": parser.get("verification_id"), + "source_non_executable_command_artifact_id": artifact.get("artifact_id"), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_no_apply_enforcement_verification": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_no_apply_enforcement_closeout": ( + closeout_ready + ), + "post_receipt_parser_closeout_ready": closeout_ready, + "no_apply_enforcement_verification_bound": closeout_ready, + "dry_run_execution_performed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "stdout_capture_allowed": False, + "stderr_capture_allowed": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "reads_secret_in_preview": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_post_receipt_parser_closeout = { + "post_receipt_parser_closeout_id": closeout_id, + "authorization_material_type": "controlled_dry_run_post_receipt_parser_closeout", + "source_runner_execution_receipt_closeout_id": runner_closeout.get( + "runner_execution_receipt_closeout_id" + ), + "source_post_receipt_parser_verification_id": parser.get("verification_id"), + "source_command_artifact_closeout_id": runner_closeout.get( + "source_command_artifact_closeout_id" + ), + "source_non_executable_command_artifact_id": artifact.get("artifact_id"), + "source_receipt_closeout_preview_status": preview.get("receipt_status"), + "dry_run_command_shape_hash": parser.get("required_command_shape_hash"), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_post_receipt_parser_closeout": ( + closeout_ready + ), + "post_receipt_parser_closeout_fields": post_receipt_parser_closeout_fields, + "post_receipt_parser_closeout_field_count": len( + post_receipt_parser_closeout_fields + ), + "post_receipt_parser_closeout_acceptance_gates": ( + post_receipt_parser_closeout_acceptance_gates + ), + "post_receipt_parser_closeout_acceptance_gate_count": len( + post_receipt_parser_closeout_acceptance_gates + ), + "post_receipt_parser_verification": parser, + "post_receipt_parser_verification_count": 1, + "receipt_closeout_preview": preview, + "receipt_closeout_preview_count": 1, + "no_apply_enforcement_verification": no_apply_enforcement_verification, + "no_apply_enforcement_verification_count": 1, + "no_apply_enforcement_verification_field_count": len( + no_apply_enforcement_verification_fields + ), + "runner_execution_receipt_preflight": preflight, + "runner_execution_receipt_preflight_count": 1, + "runner_execution_receipt_closeout": runner_closeout, + "runner_execution_receipt_closeout_count": 1, + "non_executable_command_artifact": artifact, + "non_executable_command_artifact_count": 1, + "dry_run_result_parser": result_parser, + "dry_run_result_parser_count": 1, + "receipt_validation_report": validation, + "receipt_validation_report_count": 1, + "target_file": runner_closeout.get("target_file"), + "expected_sha256": runner_closeout.get("expected_sha256"), + "actual_sha256": runner_closeout.get("actual_sha256"), + "hash_matches": runner_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "post_receipt_parser_closeout_only": True, + "no_apply_enforcement_verification_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + } + controlled_dry_run_post_receipt_parser_closeout_contract = { + "mode": "controlled_dry_run_post_receipt_parser_closeout_and_no_apply_enforcement_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-post-receipt-parser-closeout" + ), + "source_runner_execution_receipt_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-runner-execution-receipt-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_no_apply_enforcement_verification": ( + closeout_ready + ), + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": 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", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_POST_RECEIPT_PARSER_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(runner_closeout_result.get("success")), + "generated_at": runner_closeout_result.get("generated_at"), + "source_policy": runner_closeout_result.get("policy"), + "stats": runner_closeout_result.get("stats") or {}, + "summary": { + "controlled_dry_run_post_receipt_parser_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_post_receipt_parser_closeout_check_count": len( + checks + ), + "controlled_dry_run_post_receipt_parser_closeout_pass_count": ( + passed_count + ), + "controlled_dry_run_post_receipt_parser_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_runner_execution_receipt_closeout_ready_count": ( + summary.get( + "controlled_dry_run_runner_execution_receipt_closeout_ready_count", + 0, + ) + ), + "controlled_dry_run_runner_execution_receipt_closeout_check_count": ( + summary.get( + "controlled_dry_run_runner_execution_receipt_closeout_check_count", + 0, + ) + ), + "controlled_dry_run_command_artifact_closeout_ready_count": summary.get( + "controlled_dry_run_command_artifact_closeout_ready_count", 0 + ), + "controlled_dry_run_command_artifact_closeout_check_count": summary.get( + "controlled_dry_run_command_artifact_closeout_check_count", 0 + ), + "controlled_dry_run_execution_plan_closeout_ready_count": summary.get( + "controlled_dry_run_execution_plan_closeout_ready_count", 0 + ), + "controlled_dry_run_execution_plan_closeout_check_count": summary.get( + "controlled_dry_run_execution_plan_closeout_check_count", 0 + ), + "controlled_dry_run_runner_readiness_ready_count": summary.get( + "controlled_dry_run_runner_readiness_ready_count", 0 + ), + "controlled_dry_run_runner_readiness_check_count": summary.get( + "controlled_dry_run_runner_readiness_check_count", 0 + ), + "controlled_dry_run_receipt_closeout_ready_count": summary.get( + "controlled_dry_run_receipt_closeout_ready_count", 0 + ), + "controlled_dry_run_receipt_closeout_check_count": summary.get( + "controlled_dry_run_receipt_closeout_check_count", 0 + ), + "controlled_dry_run_package_ready_count": summary.get( + "controlled_dry_run_package_ready_count", 0 + ), + "controlled_dry_run_package_check_count": summary.get( + "controlled_dry_run_package_check_count", 0 + ), + "controlled_apply_final_preflight_ready_count": summary.get( + "controlled_apply_final_preflight_ready_count", 0 + ), + "controlled_apply_final_preflight_check_count": summary.get( + "controlled_apply_final_preflight_check_count", 0 + ), + "authorization_evidence_execution_closeout_ready_count": summary.get( + "authorization_evidence_execution_closeout_ready_count", 0 + ), + "authorization_evidence_execution_closeout_check_count": summary.get( + "authorization_evidence_execution_closeout_check_count", 0 + ), + "authorization_evidence_execution_preflight_ready_count": summary.get( + "authorization_evidence_execution_preflight_ready_count", 0 + ), + "authorization_evidence_execution_preflight_check_count": summary.get( + "authorization_evidence_execution_preflight_check_count", 0 + ), + "database_apply_final_verifier_gate_count": summary.get( + "database_apply_final_verifier_gate_count", 0 + ), + "database_apply_authorization_final_verifier_gate_ready_count": ( + summary.get( + "database_apply_authorization_final_verifier_gate_ready_count", + 0, + ) + ), + "controlled_dry_run_post_receipt_parser_closeout_count": 1, + "controlled_dry_run_post_receipt_parser_closeout_field_count": len( + post_receipt_parser_closeout_fields + ), + "controlled_dry_run_post_receipt_parser_closeout_acceptance_gate_count": len( + post_receipt_parser_closeout_acceptance_gates + ), + "no_apply_enforcement_verification_count": 1, + "no_apply_enforcement_verification_field_count": len( + no_apply_enforcement_verification_fields + ), + "post_receipt_parser_verification_count": summary.get( + "post_receipt_parser_verification_count", 0 + ), + "post_receipt_parser_verification_field_count": summary.get( + "post_receipt_parser_verification_field_count", 0 + ), + "receipt_closeout_preview_count": summary.get( + "receipt_closeout_preview_count", 0 + ), + "controlled_dry_run_runner_execution_receipt_closeout_count": summary.get( + "controlled_dry_run_runner_execution_receipt_closeout_count", 0 + ), + "controlled_dry_run_runner_execution_receipt_closeout_field_count": ( + summary.get( + "controlled_dry_run_runner_execution_receipt_closeout_field_count", + 0, + ) + ), + "controlled_dry_run_runner_execution_receipt_closeout_acceptance_gate_count": ( + summary.get( + "controlled_dry_run_runner_execution_receipt_closeout_acceptance_gate_count", + 0, + ) + ), + "runner_execution_receipt_preflight_count": summary.get( + "runner_execution_receipt_preflight_count", 0 + ), + "runner_execution_receipt_preflight_field_count": summary.get( + "runner_execution_receipt_preflight_field_count", 0 + ), + "non_executable_command_artifact_count": summary.get( + "non_executable_command_artifact_count", 0 + ), + "non_executable_command_artifact_field_count": summary.get( + "non_executable_command_artifact_field_count", 0 + ), + "dry_run_result_parser_count": summary.get( + "dry_run_result_parser_count", 0 + ), + "dry_run_result_parser_field_count": summary.get( + "dry_run_result_parser_field_count", 0 + ), + "receipt_validation_report_count": summary.get( + "receipt_validation_report_count", 0 + ), + "receipt_validation_field_count": summary.get( + "receipt_validation_field_count", 0 + ), + "rollback_binding_count": summary.get("rollback_binding_count", 0), + "post_apply_verifier_binding_count": summary.get( + "post_apply_verifier_binding_count", 0 + ), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + "same_run_truth_required_count": summary.get( + "same_run_truth_required_count", 0 + ), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + }, + "future_database_apply_controlled_dry_run_no_apply_enforcement_verification": ( + future_database_apply_controlled_dry_run_no_apply_enforcement_verification + ), + "controlled_dry_run_post_receipt_parser_closeout": ( + controlled_dry_run_post_receipt_parser_closeout + ), + "controlled_dry_run_post_receipt_parser_closeout_contract": ( + controlled_dry_run_post_receipt_parser_closeout_contract + ), + "controlled_dry_run_post_receipt_parser_closeout_checks": checks, + "source_controlled_dry_run_runner_execution_receipt_closeout_summary": summary, + "source_controlled_dry_run_runner_execution_receipt_closeout_contract": ( + runner_contract + ), + "source_controlled_dry_run_runner_execution_receipt_closeout": ( + runner_closeout + ), + "source_database_apply_controlled_dry_run_post_receipt_parser_verification": ( + future_parser + ), + "safety": { + "read_only_db_apply_controlled_dry_run_post_receipt_parser_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future controlled dry-run no-apply enforcement closeout.", + "Keep no-apply enforcement machine-verifiable and exception-only; do not introduce manual review as the default path.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_no_apply_enforcement_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out no-apply enforcement and bind the final dry-run executor guard.""" + parser_closeout_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_post_receipt_parser_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_enforcement = ( + parser_closeout_result.get( + "future_database_apply_controlled_dry_run_no_apply_enforcement_verification" + ) + or {} + ) + parser_closeout = ( + parser_closeout_result.get("controlled_dry_run_post_receipt_parser_closeout") + or {} + ) + parser_closeout_contract = ( + parser_closeout_result.get( + "controlled_dry_run_post_receipt_parser_closeout_contract" + ) + or {} + ) + summary = parser_closeout_result.get("summary") or {} + safety = parser_closeout_result.get("safety") or {} + no_apply_enforcement = ( + parser_closeout.get("no_apply_enforcement_verification") or {} + ) + parser = parser_closeout.get("post_receipt_parser_verification") or {} + preview = parser_closeout.get("receipt_closeout_preview") or {} + result_parser = parser_closeout.get("dry_run_result_parser") or {} + validation = parser_closeout.get("receipt_validation_report") or {} + rollback_binding = parser_closeout.get("rollback_binding") or {} + verifier_binding = parser_closeout.get("post_apply_verifier_binding") or {} + closeout_id = _db_apply_controlled_dry_run_no_apply_enforcement_closeout_id( + parser_closeout_result + ) + final_guard_id = f"{closeout_id}-final-dry-run-executor-guard" + no_apply_enforcement_closeout_fields = [ + "no_apply_enforcement_closeout_id", + "source_post_receipt_parser_closeout_id", + "source_no_apply_enforcement_verification_id", + "source_runner_execution_receipt_closeout_id", + "source_command_artifact_closeout_id", + "dry_run_command_shape_hash", + "final_dry_run_executor_guard_id", + "endpoint_execution_allowed", + "sql_execution_allowed", + "database_write_allowed", + "target_migration_file", + "abort_conditions", + ] + no_apply_enforcement_closeout_acceptance_gates = [ + "post_receipt_parser_closeout_ready", + "source_chain_ids_match", + "no_apply_enforcement_verification_ready", + "no_apply_blocks_endpoint_sql_db_write", + "final_dry_run_executor_guard_bound", + "final_executor_guard_blocks_execution", + "parser_and_receipt_preview_carried_forward", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "no_secret_signature_or_database_apply", + ] + final_dry_run_executor_guard_fields = [ + "guard_id", + "source_no_apply_enforcement_closeout_id", + "source_no_apply_enforcement_verification_id", + "required_enforcement_status", + "endpoint_execution_allowed", + "sql_execution_allowed", + "database_write_allowed", + "dry_run_executor_invocation_allowed", + "stdout_capture_allowed", + "stderr_capture_allowed", + "database_apply_authorized", + "guard_status", + ] + abort_conditions = [ + "abort_if_post_receipt_parser_closeout_not_ready", + "abort_if_no_apply_enforcement_verification_missing", + "abort_if_no_apply_enforcement_allows_endpoint_execution", + "abort_if_no_apply_enforcement_allows_sql_execution", + "abort_if_no_apply_enforcement_allows_database_write", + "abort_if_final_dry_run_executor_guard_allows_invocation", + "abort_if_stdout_or_stderr_capture_is_allowed", + "abort_if_database_apply_authorization_is_present", + "abort_if_command_shape_hash_mismatch", + "abort_if_target_migration_hash_changes", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_any_secret_signature_or_apply_material_is_present", + ] + final_dry_run_executor_guard = { + "guard_id": final_guard_id, + "source_no_apply_enforcement_closeout_id": closeout_id, + "source_post_receipt_parser_closeout_id": parser_closeout.get( + "post_receipt_parser_closeout_id" + ), + "source_no_apply_enforcement_verification_id": no_apply_enforcement.get( + "verification_id" + ), + "source_runner_execution_receipt_closeout_id": parser_closeout.get( + "source_runner_execution_receipt_closeout_id" + ), + "source_command_artifact_closeout_id": parser_closeout.get( + "source_command_artifact_closeout_id" + ), + "required_enforcement_status": "no_apply_enforcement_preview_ready", + "required_command_shape_hash": parser_closeout.get( + "dry_run_command_shape_hash" + ), + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "dry_run_executor_invocation_allowed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "stdout_capture_allowed": False, + "stderr_capture_allowed": False, + "database_apply_authorized": False, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "guard_status": "final_dry_run_executor_guard_preview_ready", + "final_dry_run_executor_guard_field_count": len( + final_dry_run_executor_guard_fields + ), + "final_dry_run_executor_guard_fields": final_dry_run_executor_guard_fields, + } + post_receipt_parser_closeout_ready = ( + parser_closeout_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_POST_RECEIPT_PARSER_CLOSEOUT_READY" + and summary.get("controlled_dry_run_post_receipt_parser_closeout_ready_count") + == 1 + and summary.get("controlled_dry_run_post_receipt_parser_closeout_pass_count") + == summary.get("controlled_dry_run_post_receipt_parser_closeout_check_count") + ) + source_chain_ids_match = ( + bool(parser_closeout.get("post_receipt_parser_closeout_id")) + and parser_closeout.get("post_receipt_parser_closeout_id") + == future_enforcement.get("post_receipt_parser_closeout_id") + == no_apply_enforcement.get("source_post_receipt_parser_closeout_id") + == final_dry_run_executor_guard.get( + "source_post_receipt_parser_closeout_id" + ) + and no_apply_enforcement.get("verification_id") + == future_enforcement.get("no_apply_enforcement_verification_id") + == final_dry_run_executor_guard.get( + "source_no_apply_enforcement_verification_id" + ) + and parser_closeout.get("source_runner_execution_receipt_closeout_id") + == future_enforcement.get("source_runner_execution_receipt_closeout_id") + == final_dry_run_executor_guard.get( + "source_runner_execution_receipt_closeout_id" + ) + ) + no_apply_enforcement_verification_ready = ( + no_apply_enforcement.get("enforcement_status") + == "no_apply_enforcement_preview_ready" + and no_apply_enforcement.get("verification_id") + == future_enforcement.get("no_apply_enforcement_verification_id") + and int( + no_apply_enforcement.get( + "no_apply_enforcement_verification_field_count" + ) + or 0 + ) + == 12 + ) + no_apply_blocks_endpoint_sql_db_write = ( + no_apply_enforcement.get("endpoint_execution_allowed") is False + and no_apply_enforcement.get("sql_execution_allowed") is False + and no_apply_enforcement.get("database_write_allowed") is False + and no_apply_enforcement.get("database_apply_authorized") is False + and no_apply_enforcement.get("executes_endpoint") is False + and no_apply_enforcement.get("executes_sql") is False + and no_apply_enforcement.get("writes_database") is False + and no_apply_enforcement.get("executes_database_apply") is False + and future_enforcement.get("endpoint_execution_allowed") is False + and future_enforcement.get("sql_execution_allowed") is False + and future_enforcement.get("database_write_allowed") is False + ) + final_dry_run_executor_guard_bound = ( + bool(final_dry_run_executor_guard.get("guard_id")) + and final_dry_run_executor_guard.get( + "source_no_apply_enforcement_closeout_id" + ) + == closeout_id + and final_dry_run_executor_guard.get( + "source_no_apply_enforcement_verification_id" + ) + == no_apply_enforcement.get("verification_id") + and final_dry_run_executor_guard.get("required_command_shape_hash") + == parser_closeout.get("dry_run_command_shape_hash") + and int( + final_dry_run_executor_guard.get( + "final_dry_run_executor_guard_field_count" + ) + or 0 + ) + == len(final_dry_run_executor_guard_fields) + ) + final_executor_guard_blocks_execution = ( + final_dry_run_executor_guard.get("endpoint_execution_allowed") is False + and final_dry_run_executor_guard.get("sql_execution_allowed") is False + and final_dry_run_executor_guard.get("database_write_allowed") is False + and final_dry_run_executor_guard.get("dry_run_executor_invocation_allowed") + is False + and final_dry_run_executor_guard.get("stdout_capture_allowed") is False + and final_dry_run_executor_guard.get("stderr_capture_allowed") is False + and final_dry_run_executor_guard.get("database_apply_authorized") is False + and final_dry_run_executor_guard.get("executes_database_apply") is False + and final_dry_run_executor_guard.get("executes_endpoint") is False + and final_dry_run_executor_guard.get("executes_sql") is False + and final_dry_run_executor_guard.get("writes_database") is False + ) + parser_and_receipt_preview_carried_forward = ( + parser.get("parser_verification_status") == "post_receipt_parser_preview_ready" + and parser.get("expected_execution_performed") is False + and parser.get("expected_stdout_included") is False + and parser.get("expected_stderr_included") is False + and parser.get("database_apply_authorized") is False + and preview.get("receipt_status") == "receipt_closeout_preview_not_executed" + and preview.get("execution_performed") is False + and preview.get("stdout_included") is False + and preview.get("stderr_included") is False + and preview.get("writes_database") is False + and result_parser.get("required_command_shape_hash") + == parser_closeout.get("dry_run_command_shape_hash") + and result_parser.get("database_apply_authorized") is False + and validation.get("receipt_validation_status") + == "preview_validated_not_executed" + and validation.get("writes_database") is False + ) + target_hash_locked = ( + parser_closeout.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(parser_closeout.get("expected_sha256")) + and bool(parser_closeout.get("actual_sha256")) + and parser_closeout.get("expected_sha256") + == parser_closeout.get("actual_sha256") + and parser_closeout.get("hash_matches") is True + and parser_closeout.get("target_migration_hash_locked") is True + ) + rollback_and_verifier_bound = ( + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False + ) + parser_closeout_contract_blocks_database_apply = ( + parser_closeout_contract.get("executes_database_apply") is False + and parser_closeout_contract.get("executes_endpoint") is False + and parser_closeout_contract.get("executes_sql") is False + and parser_closeout_contract.get("database_apply_authorized") is False + and parser_closeout_contract.get("ready_for_database_apply_now") is False + and parser_closeout_contract.get("signs_database_apply_authorization") + is False + and parser_closeout_contract.get("writes_database") is False + and parser_closeout_contract.get("executes_in_preview") is False + ) + side_effect_free = ( + summary.get("reads_secret_count", 0) == 0 + and summary.get("executes_script_count", 0) == 0 + and summary.get("executes_migration_count", 0) == 0 + and summary.get("executes_endpoint_count", 0) == 0 + and summary.get("executes_sql_count", 0) == 0 + and summary.get("writes_database_count", 0) == 0 + and summary.get("signs_database_apply_authorization_count", 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("executes_database_apply") is False + and no_apply_blocks_endpoint_sql_db_write + and final_executor_guard_blocks_execution + ) + checks = [ + _controlled_dry_run_no_apply_enforcement_closeout_check( + "post_receipt_parser_closeout_ready", + post_receipt_parser_closeout_ready, + { + "result": parser_closeout_result.get("result"), + "ready_count": summary.get( + "controlled_dry_run_post_receipt_parser_closeout_ready_count" + ), + "pass_count": summary.get( + "controlled_dry_run_post_receipt_parser_closeout_pass_count" + ), + "check_count": summary.get( + "controlled_dry_run_post_receipt_parser_closeout_check_count" + ), + }, + "wait_for_post_receipt_parser_closeout_ready", + ), + _controlled_dry_run_no_apply_enforcement_closeout_check( + "source_chain_ids_match", + source_chain_ids_match, + { + "post_receipt_parser_closeout_id": parser_closeout.get( + "post_receipt_parser_closeout_id" + ), + "no_apply_enforcement_verification_id": no_apply_enforcement.get( + "verification_id" + ), + "runner_execution_receipt_closeout_id": parser_closeout.get( + "source_runner_execution_receipt_closeout_id" + ), + }, + "wait_for_source_chain_ids_match", + ), + _controlled_dry_run_no_apply_enforcement_closeout_check( + "no_apply_enforcement_verification_ready", + no_apply_enforcement_verification_ready, + { + "enforcement_status": no_apply_enforcement.get( + "enforcement_status" + ), + "verification_id": no_apply_enforcement.get("verification_id"), + "field_count": no_apply_enforcement.get( + "no_apply_enforcement_verification_field_count" + ), + }, + "wait_for_no_apply_enforcement_verification_ready", + ), + _controlled_dry_run_no_apply_enforcement_closeout_check( + "no_apply_blocks_endpoint_sql_db_write", + no_apply_blocks_endpoint_sql_db_write, + { + "endpoint_execution_allowed": no_apply_enforcement.get( + "endpoint_execution_allowed" + ), + "sql_execution_allowed": no_apply_enforcement.get( + "sql_execution_allowed" + ), + "database_write_allowed": no_apply_enforcement.get( + "database_write_allowed" + ), + "database_apply_authorized": no_apply_enforcement.get( + "database_apply_authorized" + ), + }, + "abort_if_no_apply_enforcement_allows_endpoint_sql_or_db_write", + ), + _controlled_dry_run_no_apply_enforcement_closeout_check( + "final_dry_run_executor_guard_bound", + final_dry_run_executor_guard_bound, + { + "guard_id": final_dry_run_executor_guard.get("guard_id"), + "source_no_apply_enforcement_verification_id": ( + final_dry_run_executor_guard.get( + "source_no_apply_enforcement_verification_id" + ) + ), + "field_count": final_dry_run_executor_guard.get( + "final_dry_run_executor_guard_field_count" + ), + }, + "wait_for_final_dry_run_executor_guard_binding", + ), + _controlled_dry_run_no_apply_enforcement_closeout_check( + "final_executor_guard_blocks_execution", + final_executor_guard_blocks_execution, + { + "dry_run_executor_invocation_allowed": ( + final_dry_run_executor_guard.get( + "dry_run_executor_invocation_allowed" + ) + ), + "endpoint_execution_allowed": final_dry_run_executor_guard.get( + "endpoint_execution_allowed" + ), + "sql_execution_allowed": final_dry_run_executor_guard.get( + "sql_execution_allowed" + ), + "database_write_allowed": final_dry_run_executor_guard.get( + "database_write_allowed" + ), + }, + "abort_if_final_executor_guard_allows_execution", + ), + _controlled_dry_run_no_apply_enforcement_closeout_check( + "parser_and_receipt_preview_carried_forward", + parser_and_receipt_preview_carried_forward, + { + "parser_verification_status": parser.get( + "parser_verification_status" + ), + "receipt_status": preview.get("receipt_status"), + "receipt_validation_status": validation.get( + "receipt_validation_status" + ), + }, + "wait_for_parser_and_receipt_preview_carry_forward", + ), + _controlled_dry_run_no_apply_enforcement_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": parser_closeout.get("target_file"), + "hash_matches": parser_closeout.get("hash_matches"), + "expected_sha256_present": bool(parser_closeout.get("expected_sha256")), + "actual_sha256_present": bool(parser_closeout.get("actual_sha256")), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_no_apply_enforcement_closeout_check( + "rollback_and_post_apply_verifier_bindings_carried_forward", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "database_apply_authorized": verifier_binding.get( + "database_apply_authorized" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_no_apply_enforcement_closeout_check( + "post_receipt_parser_contract_blocks_database_apply", + parser_closeout_contract_blocks_database_apply, + { + "permits_future_database_apply_controlled_dry_run_no_apply_enforcement_verification": ( + parser_closeout_contract.get( + "permits_future_database_apply_controlled_dry_run_no_apply_enforcement_verification" + ) + ), + "database_apply_authorized": parser_closeout_contract.get( + "database_apply_authorized" + ), + "writes_database": parser_closeout_contract.get("writes_database"), + }, + "abort_if_post_receipt_parser_contract_authorizes_database_apply", + ), + _controlled_dry_run_no_apply_enforcement_closeout_check( + "preview_has_no_side_effects_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + }, + "abort_on_preview_side_effect_execution_or_signing", + ), + _controlled_dry_run_no_apply_enforcement_closeout_check( + "manual_review_not_required_for_safe_preview", + parser_closeout_contract.get("manual_review_mode") == "exception_only" + and safety.get("manual_review_mode") == "exception_only", + { + "contract_manual_review_mode": parser_closeout_contract.get( + "manual_review_mode" + ), + "safety_manual_review_mode": safety.get("manual_review_mode"), + }, + "keep_manual_review_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_NO_APPLY_ENFORCEMENT_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_POST_RECEIPT_PARSER_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_final_dry_run_executor_guard = { + "no_apply_enforcement_closeout_id": closeout_id, + "final_dry_run_executor_guard_id": final_guard_id, + "source_post_receipt_parser_closeout_id": parser_closeout.get( + "post_receipt_parser_closeout_id" + ), + "source_no_apply_enforcement_verification_id": no_apply_enforcement.get( + "verification_id" + ), + "source_runner_execution_receipt_closeout_id": parser_closeout.get( + "source_runner_execution_receipt_closeout_id" + ), + "source_non_executable_command_artifact_id": parser_closeout.get( + "source_non_executable_command_artifact_id" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_final_dry_run_executor_guard": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_final_executor_guard_closeout": ( + closeout_ready + ), + "no_apply_enforcement_closeout_ready": closeout_ready, + "final_dry_run_executor_guard_bound": closeout_ready, + "dry_run_executor_invocation_allowed": False, + "dry_run_execution_performed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "stdout_capture_allowed": False, + "stderr_capture_allowed": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "reads_secret_in_preview": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_no_apply_enforcement_closeout = { + "no_apply_enforcement_closeout_id": closeout_id, + "authorization_material_type": ( + "controlled_dry_run_no_apply_enforcement_closeout" + ), + "source_post_receipt_parser_closeout_id": parser_closeout.get( + "post_receipt_parser_closeout_id" + ), + "source_no_apply_enforcement_verification_id": no_apply_enforcement.get( + "verification_id" + ), + "source_runner_execution_receipt_closeout_id": parser_closeout.get( + "source_runner_execution_receipt_closeout_id" + ), + "source_command_artifact_closeout_id": parser_closeout.get( + "source_command_artifact_closeout_id" + ), + "source_non_executable_command_artifact_id": parser_closeout.get( + "source_non_executable_command_artifact_id" + ), + "dry_run_command_shape_hash": parser_closeout.get( + "dry_run_command_shape_hash" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_no_apply_enforcement_closeout": ( + closeout_ready + ), + "no_apply_enforcement_closeout_fields": no_apply_enforcement_closeout_fields, + "no_apply_enforcement_closeout_field_count": len( + no_apply_enforcement_closeout_fields + ), + "no_apply_enforcement_closeout_acceptance_gates": ( + no_apply_enforcement_closeout_acceptance_gates + ), + "no_apply_enforcement_closeout_acceptance_gate_count": len( + no_apply_enforcement_closeout_acceptance_gates + ), + "no_apply_enforcement_verification": no_apply_enforcement, + "no_apply_enforcement_verification_count": 1, + "final_dry_run_executor_guard": final_dry_run_executor_guard, + "final_dry_run_executor_guard_count": 1, + "final_dry_run_executor_guard_field_count": len( + final_dry_run_executor_guard_fields + ), + "post_receipt_parser_closeout": parser_closeout, + "post_receipt_parser_closeout_count": 1, + "post_receipt_parser_verification": parser, + "post_receipt_parser_verification_count": 1, + "receipt_closeout_preview": preview, + "receipt_closeout_preview_count": 1, + "dry_run_result_parser": result_parser, + "dry_run_result_parser_count": 1, + "receipt_validation_report": validation, + "receipt_validation_report_count": 1, + "target_file": parser_closeout.get("target_file"), + "expected_sha256": parser_closeout.get("expected_sha256"), + "actual_sha256": parser_closeout.get("actual_sha256"), + "hash_matches": parser_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "no_apply_enforcement_closeout_only": True, + "final_dry_run_executor_guard_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "dry_run_executor_invocation_allowed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + } + controlled_dry_run_no_apply_enforcement_closeout_contract = { + "mode": "controlled_dry_run_no_apply_enforcement_closeout_and_final_executor_guard_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-no-apply-enforcement-closeout" + ), + "source_post_receipt_parser_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-post-receipt-parser-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_final_dry_run_executor_guard": ( + closeout_ready + ), + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": 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", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_NO_APPLY_ENFORCEMENT_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(parser_closeout_result.get("success")), + "generated_at": parser_closeout_result.get("generated_at"), + "source_policy": parser_closeout_result.get("policy"), + "stats": parser_closeout_result.get("stats") or {}, + "summary": { + "controlled_dry_run_no_apply_enforcement_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_no_apply_enforcement_closeout_check_count": len( + checks + ), + "controlled_dry_run_no_apply_enforcement_closeout_pass_count": ( + passed_count + ), + "controlled_dry_run_no_apply_enforcement_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_post_receipt_parser_closeout_ready_count": ( + summary.get( + "controlled_dry_run_post_receipt_parser_closeout_ready_count", + 0, + ) + ), + "controlled_dry_run_post_receipt_parser_closeout_check_count": ( + summary.get( + "controlled_dry_run_post_receipt_parser_closeout_check_count", + 0, + ) + ), + "controlled_dry_run_runner_execution_receipt_closeout_ready_count": ( + summary.get( + "controlled_dry_run_runner_execution_receipt_closeout_ready_count", + 0, + ) + ), + "controlled_dry_run_runner_execution_receipt_closeout_check_count": ( + summary.get( + "controlled_dry_run_runner_execution_receipt_closeout_check_count", + 0, + ) + ), + "controlled_dry_run_command_artifact_closeout_ready_count": summary.get( + "controlled_dry_run_command_artifact_closeout_ready_count", 0 + ), + "controlled_dry_run_command_artifact_closeout_check_count": summary.get( + "controlled_dry_run_command_artifact_closeout_check_count", 0 + ), + "controlled_dry_run_execution_plan_closeout_ready_count": summary.get( + "controlled_dry_run_execution_plan_closeout_ready_count", 0 + ), + "controlled_dry_run_execution_plan_closeout_check_count": summary.get( + "controlled_dry_run_execution_plan_closeout_check_count", 0 + ), + "controlled_dry_run_runner_readiness_ready_count": summary.get( + "controlled_dry_run_runner_readiness_ready_count", 0 + ), + "controlled_dry_run_runner_readiness_check_count": summary.get( + "controlled_dry_run_runner_readiness_check_count", 0 + ), + "controlled_dry_run_receipt_closeout_ready_count": summary.get( + "controlled_dry_run_receipt_closeout_ready_count", 0 + ), + "controlled_dry_run_receipt_closeout_check_count": summary.get( + "controlled_dry_run_receipt_closeout_check_count", 0 + ), + "controlled_dry_run_package_ready_count": summary.get( + "controlled_dry_run_package_ready_count", 0 + ), + "controlled_dry_run_package_check_count": summary.get( + "controlled_dry_run_package_check_count", 0 + ), + "controlled_apply_final_preflight_ready_count": summary.get( + "controlled_apply_final_preflight_ready_count", 0 + ), + "controlled_apply_final_preflight_check_count": summary.get( + "controlled_apply_final_preflight_check_count", 0 + ), + "authorization_evidence_execution_closeout_ready_count": summary.get( + "authorization_evidence_execution_closeout_ready_count", 0 + ), + "authorization_evidence_execution_closeout_check_count": summary.get( + "authorization_evidence_execution_closeout_check_count", 0 + ), + "authorization_evidence_execution_preflight_ready_count": summary.get( + "authorization_evidence_execution_preflight_ready_count", 0 + ), + "authorization_evidence_execution_preflight_check_count": summary.get( + "authorization_evidence_execution_preflight_check_count", 0 + ), + "database_apply_final_verifier_gate_count": summary.get( + "database_apply_final_verifier_gate_count", 0 + ), + "database_apply_authorization_final_verifier_gate_ready_count": ( + summary.get( + "database_apply_authorization_final_verifier_gate_ready_count", + 0, + ) + ), + "controlled_dry_run_no_apply_enforcement_closeout_count": 1, + "controlled_dry_run_no_apply_enforcement_closeout_field_count": len( + no_apply_enforcement_closeout_fields + ), + "controlled_dry_run_no_apply_enforcement_closeout_acceptance_gate_count": len( + no_apply_enforcement_closeout_acceptance_gates + ), + "final_dry_run_executor_guard_count": 1, + "final_dry_run_executor_guard_field_count": len( + final_dry_run_executor_guard_fields + ), + "controlled_dry_run_post_receipt_parser_closeout_count": summary.get( + "controlled_dry_run_post_receipt_parser_closeout_count", 0 + ), + "controlled_dry_run_post_receipt_parser_closeout_field_count": ( + summary.get( + "controlled_dry_run_post_receipt_parser_closeout_field_count", + 0, + ) + ), + "controlled_dry_run_post_receipt_parser_closeout_acceptance_gate_count": ( + summary.get( + "controlled_dry_run_post_receipt_parser_closeout_acceptance_gate_count", + 0, + ) + ), + "no_apply_enforcement_verification_count": summary.get( + "no_apply_enforcement_verification_count", 0 + ), + "no_apply_enforcement_verification_field_count": summary.get( + "no_apply_enforcement_verification_field_count", 0 + ), + "post_receipt_parser_verification_count": summary.get( + "post_receipt_parser_verification_count", 0 + ), + "post_receipt_parser_verification_field_count": summary.get( + "post_receipt_parser_verification_field_count", 0 + ), + "receipt_closeout_preview_count": summary.get( + "receipt_closeout_preview_count", 0 + ), + "runner_execution_receipt_preflight_count": summary.get( + "runner_execution_receipt_preflight_count", 0 + ), + "runner_execution_receipt_preflight_field_count": summary.get( + "runner_execution_receipt_preflight_field_count", 0 + ), + "non_executable_command_artifact_count": summary.get( + "non_executable_command_artifact_count", 0 + ), + "non_executable_command_artifact_field_count": summary.get( + "non_executable_command_artifact_field_count", 0 + ), + "dry_run_result_parser_count": summary.get( + "dry_run_result_parser_count", 0 + ), + "dry_run_result_parser_field_count": summary.get( + "dry_run_result_parser_field_count", 0 + ), + "receipt_validation_report_count": summary.get( + "receipt_validation_report_count", 0 + ), + "receipt_validation_field_count": summary.get( + "receipt_validation_field_count", 0 + ), + "rollback_binding_count": summary.get("rollback_binding_count", 0), + "post_apply_verifier_binding_count": summary.get( + "post_apply_verifier_binding_count", 0 + ), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + "same_run_truth_required_count": summary.get( + "same_run_truth_required_count", 0 + ), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + }, + "future_database_apply_controlled_dry_run_final_dry_run_executor_guard": ( + future_database_apply_controlled_dry_run_final_dry_run_executor_guard + ), + "controlled_dry_run_no_apply_enforcement_closeout": ( + controlled_dry_run_no_apply_enforcement_closeout + ), + "controlled_dry_run_no_apply_enforcement_closeout_contract": ( + controlled_dry_run_no_apply_enforcement_closeout_contract + ), + "controlled_dry_run_no_apply_enforcement_closeout_checks": checks, + "source_controlled_dry_run_post_receipt_parser_closeout_summary": summary, + "source_controlled_dry_run_post_receipt_parser_closeout_contract": ( + parser_closeout_contract + ), + "source_controlled_dry_run_post_receipt_parser_closeout": parser_closeout, + "source_database_apply_controlled_dry_run_no_apply_enforcement_verification": ( + future_enforcement + ), + "safety": { + "read_only_db_apply_controlled_dry_run_no_apply_enforcement_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future controlled dry-run final executor guard closeout.", + "Keep the final dry-run executor guard machine-verifiable and non-invoking until an explicit apply lane is built.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_final_executor_guard_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out the final dry-run executor guard and bind replay verification.""" + no_apply_closeout_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_no_apply_enforcement_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_guard = ( + no_apply_closeout_result.get( + "future_database_apply_controlled_dry_run_final_dry_run_executor_guard" + ) + or {} + ) + no_apply_closeout = ( + no_apply_closeout_result.get( + "controlled_dry_run_no_apply_enforcement_closeout" + ) + or {} + ) + no_apply_contract = ( + no_apply_closeout_result.get( + "controlled_dry_run_no_apply_enforcement_closeout_contract" + ) + or {} + ) + summary = no_apply_closeout_result.get("summary") or {} + safety = no_apply_closeout_result.get("safety") or {} + final_guard = no_apply_closeout.get("final_dry_run_executor_guard") or {} + no_apply_enforcement = ( + no_apply_closeout.get("no_apply_enforcement_verification") or {} + ) + parser_closeout = no_apply_closeout.get("post_receipt_parser_closeout") or {} + parser = no_apply_closeout.get("post_receipt_parser_verification") or {} + preview = no_apply_closeout.get("receipt_closeout_preview") or {} + validation = no_apply_closeout.get("receipt_validation_report") or {} + rollback_binding = no_apply_closeout.get("rollback_binding") or {} + verifier_binding = no_apply_closeout.get("post_apply_verifier_binding") or {} + closeout_id = _db_apply_controlled_dry_run_final_executor_guard_closeout_id( + no_apply_closeout_result + ) + replay_verifier_id = f"{closeout_id}-pre-apply-replay-verifier" + final_executor_guard_closeout_fields = [ + "final_executor_guard_closeout_id", + "source_no_apply_enforcement_closeout_id", + "source_final_dry_run_executor_guard_id", + "source_no_apply_enforcement_verification_id", + "source_post_receipt_parser_closeout_id", + "dry_run_command_shape_hash", + "pre_apply_replay_verifier_id", + "target_migration_file", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "dry_run_executor_invocation_allowed", + "abort_conditions", + ] + final_executor_guard_closeout_acceptance_gates = [ + "no_apply_enforcement_closeout_ready", + "source_chain_ids_match", + "final_dry_run_executor_guard_ready", + "final_executor_guard_blocks_invocation", + "pre_apply_replay_verifier_bound", + "pre_apply_replay_verifier_preview_only", + "no_apply_enforcement_carried_forward", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "no_secret_signature_or_database_apply", + ] + pre_apply_replay_verifier_fields = [ + "verification_id", + "source_final_executor_guard_closeout_id", + "source_final_dry_run_executor_guard_id", + "source_no_apply_enforcement_closeout_id", + "required_guard_status", + "required_enforcement_status", + "required_command_shape_hash", + "replay_mode", + "dry_run_executor_invocation_allowed", + "endpoint_execution_allowed", + "sql_execution_allowed", + "database_write_allowed", + ] + abort_conditions = [ + "abort_if_no_apply_enforcement_closeout_not_ready", + "abort_if_final_dry_run_executor_guard_missing", + "abort_if_final_guard_allows_invocation", + "abort_if_replay_verifier_requests_execution", + "abort_if_endpoint_or_sql_execution_is_allowed", + "abort_if_database_write_or_apply_is_allowed", + "abort_if_stdout_or_stderr_capture_is_allowed", + "abort_if_command_shape_hash_mismatch", + "abort_if_target_migration_hash_changes", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_any_secret_signature_or_apply_material_is_present", + ] + pre_apply_replay_verifier = { + "verification_id": replay_verifier_id, + "source_final_executor_guard_closeout_id": closeout_id, + "source_final_dry_run_executor_guard_id": final_guard.get("guard_id"), + "source_no_apply_enforcement_closeout_id": no_apply_closeout.get( + "no_apply_enforcement_closeout_id" + ), + "source_no_apply_enforcement_verification_id": no_apply_enforcement.get( + "verification_id" + ), + "source_post_receipt_parser_closeout_id": parser_closeout.get( + "post_receipt_parser_closeout_id" + ), + "required_guard_status": "final_dry_run_executor_guard_preview_ready", + "required_enforcement_status": "no_apply_enforcement_preview_ready", + "required_command_shape_hash": final_guard.get("required_command_shape_hash"), + "replay_mode": "pre_apply_replay_preview_only", + "dry_run_executor_invocation_allowed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "stdout_capture_allowed": False, + "stderr_capture_allowed": False, + "database_apply_authorized": False, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "verifier_status": "pre_apply_replay_verifier_preview_ready", + "pre_apply_replay_verifier_field_count": len( + pre_apply_replay_verifier_fields + ), + "pre_apply_replay_verifier_fields": pre_apply_replay_verifier_fields, + } + no_apply_closeout_ready = ( + no_apply_closeout_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_NO_APPLY_ENFORCEMENT_CLOSEOUT_READY" + and summary.get("controlled_dry_run_no_apply_enforcement_closeout_ready_count") + == 1 + and summary.get("controlled_dry_run_no_apply_enforcement_closeout_pass_count") + == summary.get("controlled_dry_run_no_apply_enforcement_closeout_check_count") + ) + source_chain_ids_match = ( + bool(no_apply_closeout.get("no_apply_enforcement_closeout_id")) + and no_apply_closeout.get("no_apply_enforcement_closeout_id") + == future_guard.get("no_apply_enforcement_closeout_id") + == final_guard.get("source_no_apply_enforcement_closeout_id") + == pre_apply_replay_verifier.get( + "source_no_apply_enforcement_closeout_id" + ) + and final_guard.get("guard_id") + == future_guard.get("final_dry_run_executor_guard_id") + == pre_apply_replay_verifier.get("source_final_dry_run_executor_guard_id") + and no_apply_enforcement.get("verification_id") + == no_apply_closeout.get("source_no_apply_enforcement_verification_id") + == pre_apply_replay_verifier.get( + "source_no_apply_enforcement_verification_id" + ) + ) + final_dry_run_executor_guard_ready = ( + final_guard.get("guard_status") == "final_dry_run_executor_guard_preview_ready" + and final_guard.get("guard_id") + == future_guard.get("final_dry_run_executor_guard_id") + and int(final_guard.get("final_dry_run_executor_guard_field_count") or 0) + == 12 + ) + final_executor_guard_blocks_invocation = ( + final_guard.get("dry_run_executor_invocation_allowed") is False + and final_guard.get("endpoint_execution_allowed") is False + and final_guard.get("sql_execution_allowed") is False + and final_guard.get("database_write_allowed") is False + and final_guard.get("stdout_capture_allowed") is False + and final_guard.get("stderr_capture_allowed") is False + and final_guard.get("database_apply_authorized") is False + and final_guard.get("executes_database_apply") is False + and final_guard.get("executes_endpoint") is False + and final_guard.get("executes_sql") is False + and final_guard.get("writes_database") is False + ) + pre_apply_replay_verifier_bound = ( + bool(pre_apply_replay_verifier.get("verification_id")) + and pre_apply_replay_verifier.get("source_final_executor_guard_closeout_id") + == closeout_id + and pre_apply_replay_verifier.get("source_final_dry_run_executor_guard_id") + == final_guard.get("guard_id") + and pre_apply_replay_verifier.get("required_command_shape_hash") + == final_guard.get("required_command_shape_hash") + and int( + pre_apply_replay_verifier.get("pre_apply_replay_verifier_field_count") + or 0 + ) + == len(pre_apply_replay_verifier_fields) + ) + pre_apply_replay_verifier_preview_only = ( + pre_apply_replay_verifier.get("replay_mode") + == "pre_apply_replay_preview_only" + and pre_apply_replay_verifier.get("dry_run_executor_invocation_allowed") + is False + and pre_apply_replay_verifier.get("endpoint_execution_allowed") is False + and pre_apply_replay_verifier.get("sql_execution_allowed") is False + and pre_apply_replay_verifier.get("database_write_allowed") is False + and pre_apply_replay_verifier.get("stdout_capture_allowed") is False + and pre_apply_replay_verifier.get("stderr_capture_allowed") is False + and pre_apply_replay_verifier.get("database_apply_authorized") is False + and pre_apply_replay_verifier.get("executes_database_apply") is False + and pre_apply_replay_verifier.get("executes_endpoint") is False + and pre_apply_replay_verifier.get("executes_sql") is False + and pre_apply_replay_verifier.get("writes_database") is False + ) + no_apply_enforcement_carried_forward = ( + no_apply_enforcement.get("enforcement_status") + == "no_apply_enforcement_preview_ready" + and no_apply_enforcement.get("endpoint_execution_allowed") is False + and no_apply_enforcement.get("sql_execution_allowed") is False + and no_apply_enforcement.get("database_write_allowed") is False + and no_apply_enforcement.get("database_apply_authorized") is False + and parser.get("parser_verification_status") + == "post_receipt_parser_preview_ready" + and preview.get("receipt_status") == "receipt_closeout_preview_not_executed" + and validation.get("receipt_validation_status") + == "preview_validated_not_executed" + ) + target_hash_locked = ( + no_apply_closeout.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(no_apply_closeout.get("expected_sha256")) + and bool(no_apply_closeout.get("actual_sha256")) + and no_apply_closeout.get("expected_sha256") + == no_apply_closeout.get("actual_sha256") + and no_apply_closeout.get("hash_matches") is True + and no_apply_closeout.get("target_migration_hash_locked") is True + ) + rollback_and_verifier_bound = ( + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False + ) + no_apply_contract_blocks_database_apply = ( + no_apply_contract.get("executes_database_apply") is False + and no_apply_contract.get("executes_endpoint") is False + and no_apply_contract.get("executes_sql") is False + and no_apply_contract.get("database_apply_authorized") is False + and no_apply_contract.get("ready_for_database_apply_now") is False + and no_apply_contract.get("signs_database_apply_authorization") is False + and no_apply_contract.get("writes_database") is False + and no_apply_contract.get("executes_in_preview") is False + ) + side_effect_free = ( + summary.get("reads_secret_count", 0) == 0 + and summary.get("executes_script_count", 0) == 0 + and summary.get("executes_migration_count", 0) == 0 + and summary.get("executes_endpoint_count", 0) == 0 + and summary.get("executes_sql_count", 0) == 0 + and summary.get("writes_database_count", 0) == 0 + and summary.get("signs_database_apply_authorization_count", 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("executes_database_apply") is False + and final_executor_guard_blocks_invocation + and pre_apply_replay_verifier_preview_only + ) + checks = [ + _controlled_dry_run_final_executor_guard_closeout_check( + "no_apply_enforcement_closeout_ready", + no_apply_closeout_ready, + { + "result": no_apply_closeout_result.get("result"), + "ready_count": summary.get( + "controlled_dry_run_no_apply_enforcement_closeout_ready_count" + ), + "pass_count": summary.get( + "controlled_dry_run_no_apply_enforcement_closeout_pass_count" + ), + "check_count": summary.get( + "controlled_dry_run_no_apply_enforcement_closeout_check_count" + ), + }, + "wait_for_no_apply_enforcement_closeout_ready", + ), + _controlled_dry_run_final_executor_guard_closeout_check( + "source_chain_ids_match", + source_chain_ids_match, + { + "no_apply_enforcement_closeout_id": no_apply_closeout.get( + "no_apply_enforcement_closeout_id" + ), + "final_guard_id": final_guard.get("guard_id"), + "no_apply_enforcement_verification_id": no_apply_enforcement.get( + "verification_id" + ), + }, + "wait_for_source_chain_ids_match", + ), + _controlled_dry_run_final_executor_guard_closeout_check( + "final_dry_run_executor_guard_ready", + final_dry_run_executor_guard_ready, + { + "guard_status": final_guard.get("guard_status"), + "guard_id": final_guard.get("guard_id"), + "field_count": final_guard.get( + "final_dry_run_executor_guard_field_count" + ), + }, + "wait_for_final_dry_run_executor_guard_ready", + ), + _controlled_dry_run_final_executor_guard_closeout_check( + "final_executor_guard_blocks_invocation", + final_executor_guard_blocks_invocation, + { + "dry_run_executor_invocation_allowed": final_guard.get( + "dry_run_executor_invocation_allowed" + ), + "endpoint_execution_allowed": final_guard.get( + "endpoint_execution_allowed" + ), + "sql_execution_allowed": final_guard.get("sql_execution_allowed"), + "database_write_allowed": final_guard.get( + "database_write_allowed" + ), + }, + "abort_if_final_guard_allows_invocation", + ), + _controlled_dry_run_final_executor_guard_closeout_check( + "pre_apply_replay_verifier_bound", + pre_apply_replay_verifier_bound, + { + "verification_id": pre_apply_replay_verifier.get( + "verification_id" + ), + "source_final_dry_run_executor_guard_id": ( + pre_apply_replay_verifier.get( + "source_final_dry_run_executor_guard_id" + ) + ), + "field_count": pre_apply_replay_verifier.get( + "pre_apply_replay_verifier_field_count" + ), + }, + "wait_for_pre_apply_replay_verifier_binding", + ), + _controlled_dry_run_final_executor_guard_closeout_check( + "pre_apply_replay_verifier_preview_only", + pre_apply_replay_verifier_preview_only, + { + "replay_mode": pre_apply_replay_verifier.get("replay_mode"), + "dry_run_executor_invocation_allowed": ( + pre_apply_replay_verifier.get( + "dry_run_executor_invocation_allowed" + ) + ), + "database_apply_authorized": pre_apply_replay_verifier.get( + "database_apply_authorized" + ), + }, + "abort_if_pre_apply_replay_verifier_requests_execution", + ), + _controlled_dry_run_final_executor_guard_closeout_check( + "no_apply_enforcement_carried_forward", + no_apply_enforcement_carried_forward, + { + "enforcement_status": no_apply_enforcement.get( + "enforcement_status" + ), + "parser_verification_status": parser.get( + "parser_verification_status" + ), + "receipt_validation_status": validation.get( + "receipt_validation_status" + ), + }, + "wait_for_no_apply_enforcement_carry_forward", + ), + _controlled_dry_run_final_executor_guard_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": no_apply_closeout.get("target_file"), + "hash_matches": no_apply_closeout.get("hash_matches"), + "expected_sha256_present": bool(no_apply_closeout.get("expected_sha256")), + "actual_sha256_present": bool(no_apply_closeout.get("actual_sha256")), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_final_executor_guard_closeout_check( + "rollback_and_post_apply_verifier_bindings_carried_forward", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "database_apply_authorized": verifier_binding.get( + "database_apply_authorized" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_final_executor_guard_closeout_check( + "no_apply_enforcement_contract_blocks_database_apply", + no_apply_contract_blocks_database_apply, + { + "permits_future_database_apply_controlled_dry_run_final_dry_run_executor_guard": ( + no_apply_contract.get( + "permits_future_database_apply_controlled_dry_run_final_dry_run_executor_guard" + ) + ), + "database_apply_authorized": no_apply_contract.get( + "database_apply_authorized" + ), + "writes_database": no_apply_contract.get("writes_database"), + }, + "abort_if_no_apply_contract_authorizes_database_apply", + ), + _controlled_dry_run_final_executor_guard_closeout_check( + "preview_has_no_side_effects_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + }, + "abort_on_preview_side_effect_execution_or_signing", + ), + _controlled_dry_run_final_executor_guard_closeout_check( + "manual_review_not_required_for_safe_preview", + no_apply_contract.get("manual_review_mode") == "exception_only" + and safety.get("manual_review_mode") == "exception_only", + { + "contract_manual_review_mode": no_apply_contract.get( + "manual_review_mode" + ), + "safety_manual_review_mode": safety.get("manual_review_mode"), + }, + "keep_manual_review_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_FINAL_EXECUTOR_GUARD_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_NO_APPLY_ENFORCEMENT_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_pre_apply_replay_verifier = { + "final_executor_guard_closeout_id": closeout_id, + "pre_apply_replay_verifier_id": replay_verifier_id, + "source_no_apply_enforcement_closeout_id": no_apply_closeout.get( + "no_apply_enforcement_closeout_id" + ), + "source_final_dry_run_executor_guard_id": final_guard.get("guard_id"), + "source_no_apply_enforcement_verification_id": no_apply_enforcement.get( + "verification_id" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_pre_apply_replay_verifier": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_pre_apply_replay_closeout": ( + closeout_ready + ), + "final_executor_guard_closeout_ready": closeout_ready, + "pre_apply_replay_verifier_bound": closeout_ready, + "dry_run_executor_invocation_allowed": False, + "dry_run_execution_performed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "stdout_capture_allowed": False, + "stderr_capture_allowed": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "reads_secret_in_preview": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_final_executor_guard_closeout = { + "final_executor_guard_closeout_id": closeout_id, + "authorization_material_type": ( + "controlled_dry_run_final_executor_guard_closeout" + ), + "source_no_apply_enforcement_closeout_id": no_apply_closeout.get( + "no_apply_enforcement_closeout_id" + ), + "source_final_dry_run_executor_guard_id": final_guard.get("guard_id"), + "source_no_apply_enforcement_verification_id": no_apply_enforcement.get( + "verification_id" + ), + "source_post_receipt_parser_closeout_id": parser_closeout.get( + "post_receipt_parser_closeout_id" + ), + "dry_run_command_shape_hash": final_guard.get("required_command_shape_hash"), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_final_executor_guard_closeout": ( + closeout_ready + ), + "final_executor_guard_closeout_fields": final_executor_guard_closeout_fields, + "final_executor_guard_closeout_field_count": len( + final_executor_guard_closeout_fields + ), + "final_executor_guard_closeout_acceptance_gates": ( + final_executor_guard_closeout_acceptance_gates + ), + "final_executor_guard_closeout_acceptance_gate_count": len( + final_executor_guard_closeout_acceptance_gates + ), + "pre_apply_replay_verifier": pre_apply_replay_verifier, + "pre_apply_replay_verifier_count": 1, + "pre_apply_replay_verifier_field_count": len( + pre_apply_replay_verifier_fields + ), + "final_dry_run_executor_guard": final_guard, + "final_dry_run_executor_guard_count": 1, + "no_apply_enforcement_verification": no_apply_enforcement, + "no_apply_enforcement_verification_count": 1, + "no_apply_enforcement_closeout": no_apply_closeout, + "no_apply_enforcement_closeout_count": 1, + "post_receipt_parser_closeout": parser_closeout, + "post_receipt_parser_closeout_count": 1, + "post_receipt_parser_verification": parser, + "post_receipt_parser_verification_count": 1, + "receipt_closeout_preview": preview, + "receipt_closeout_preview_count": 1, + "receipt_validation_report": validation, + "receipt_validation_report_count": 1, + "target_file": no_apply_closeout.get("target_file"), + "expected_sha256": no_apply_closeout.get("expected_sha256"), + "actual_sha256": no_apply_closeout.get("actual_sha256"), + "hash_matches": no_apply_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "final_executor_guard_closeout_only": True, + "pre_apply_replay_verifier_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "dry_run_executor_invocation_allowed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + } + controlled_dry_run_final_executor_guard_closeout_contract = { + "mode": "controlled_dry_run_final_executor_guard_closeout_and_pre_apply_replay_verifier_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-final-executor-guard-closeout" + ), + "source_no_apply_enforcement_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-no-apply-enforcement-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_pre_apply_replay_verifier": ( + closeout_ready + ), + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": 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", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_FINAL_EXECUTOR_GUARD_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(no_apply_closeout_result.get("success")), + "generated_at": no_apply_closeout_result.get("generated_at"), + "source_policy": no_apply_closeout_result.get("policy"), + "stats": no_apply_closeout_result.get("stats") or {}, + "summary": { + "controlled_dry_run_final_executor_guard_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_final_executor_guard_closeout_check_count": len( + checks + ), + "controlled_dry_run_final_executor_guard_closeout_pass_count": ( + passed_count + ), + "controlled_dry_run_final_executor_guard_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_no_apply_enforcement_closeout_ready_count": ( + summary.get( + "controlled_dry_run_no_apply_enforcement_closeout_ready_count", + 0, + ) + ), + "controlled_dry_run_no_apply_enforcement_closeout_check_count": ( + summary.get( + "controlled_dry_run_no_apply_enforcement_closeout_check_count", + 0, + ) + ), + "controlled_dry_run_post_receipt_parser_closeout_ready_count": ( + summary.get( + "controlled_dry_run_post_receipt_parser_closeout_ready_count", + 0, + ) + ), + "controlled_dry_run_post_receipt_parser_closeout_check_count": ( + summary.get( + "controlled_dry_run_post_receipt_parser_closeout_check_count", + 0, + ) + ), + "controlled_dry_run_runner_execution_receipt_closeout_ready_count": ( + summary.get( + "controlled_dry_run_runner_execution_receipt_closeout_ready_count", + 0, + ) + ), + "controlled_dry_run_runner_execution_receipt_closeout_check_count": ( + summary.get( + "controlled_dry_run_runner_execution_receipt_closeout_check_count", + 0, + ) + ), + "controlled_dry_run_command_artifact_closeout_ready_count": summary.get( + "controlled_dry_run_command_artifact_closeout_ready_count", 0 + ), + "controlled_dry_run_command_artifact_closeout_check_count": summary.get( + "controlled_dry_run_command_artifact_closeout_check_count", 0 + ), + "controlled_dry_run_execution_plan_closeout_ready_count": summary.get( + "controlled_dry_run_execution_plan_closeout_ready_count", 0 + ), + "controlled_dry_run_execution_plan_closeout_check_count": summary.get( + "controlled_dry_run_execution_plan_closeout_check_count", 0 + ), + "controlled_dry_run_runner_readiness_ready_count": summary.get( + "controlled_dry_run_runner_readiness_ready_count", 0 + ), + "controlled_dry_run_runner_readiness_check_count": summary.get( + "controlled_dry_run_runner_readiness_check_count", 0 + ), + "controlled_dry_run_receipt_closeout_ready_count": summary.get( + "controlled_dry_run_receipt_closeout_ready_count", 0 + ), + "controlled_dry_run_receipt_closeout_check_count": summary.get( + "controlled_dry_run_receipt_closeout_check_count", 0 + ), + "controlled_dry_run_package_ready_count": summary.get( + "controlled_dry_run_package_ready_count", 0 + ), + "controlled_dry_run_package_check_count": summary.get( + "controlled_dry_run_package_check_count", 0 + ), + "controlled_apply_final_preflight_ready_count": summary.get( + "controlled_apply_final_preflight_ready_count", 0 + ), + "controlled_apply_final_preflight_check_count": summary.get( + "controlled_apply_final_preflight_check_count", 0 + ), + "authorization_evidence_execution_closeout_ready_count": summary.get( + "authorization_evidence_execution_closeout_ready_count", 0 + ), + "authorization_evidence_execution_closeout_check_count": summary.get( + "authorization_evidence_execution_closeout_check_count", 0 + ), + "authorization_evidence_execution_preflight_ready_count": summary.get( + "authorization_evidence_execution_preflight_ready_count", 0 + ), + "authorization_evidence_execution_preflight_check_count": summary.get( + "authorization_evidence_execution_preflight_check_count", 0 + ), + "database_apply_final_verifier_gate_count": summary.get( + "database_apply_final_verifier_gate_count", 0 + ), + "database_apply_authorization_final_verifier_gate_ready_count": ( + summary.get( + "database_apply_authorization_final_verifier_gate_ready_count", + 0, + ) + ), + "controlled_dry_run_final_executor_guard_closeout_count": 1, + "controlled_dry_run_final_executor_guard_closeout_field_count": len( + final_executor_guard_closeout_fields + ), + "controlled_dry_run_final_executor_guard_closeout_acceptance_gate_count": len( + final_executor_guard_closeout_acceptance_gates + ), + "pre_apply_replay_verifier_count": 1, + "pre_apply_replay_verifier_field_count": len( + pre_apply_replay_verifier_fields + ), + "controlled_dry_run_no_apply_enforcement_closeout_count": summary.get( + "controlled_dry_run_no_apply_enforcement_closeout_count", 0 + ), + "controlled_dry_run_no_apply_enforcement_closeout_field_count": ( + summary.get( + "controlled_dry_run_no_apply_enforcement_closeout_field_count", + 0, + ) + ), + "controlled_dry_run_no_apply_enforcement_closeout_acceptance_gate_count": ( + summary.get( + "controlled_dry_run_no_apply_enforcement_closeout_acceptance_gate_count", + 0, + ) + ), + "final_dry_run_executor_guard_count": summary.get( + "final_dry_run_executor_guard_count", 0 + ), + "final_dry_run_executor_guard_field_count": summary.get( + "final_dry_run_executor_guard_field_count", 0 + ), + "no_apply_enforcement_verification_count": summary.get( + "no_apply_enforcement_verification_count", 0 + ), + "no_apply_enforcement_verification_field_count": summary.get( + "no_apply_enforcement_verification_field_count", 0 + ), + "post_receipt_parser_verification_count": summary.get( + "post_receipt_parser_verification_count", 0 + ), + "post_receipt_parser_verification_field_count": summary.get( + "post_receipt_parser_verification_field_count", 0 + ), + "receipt_closeout_preview_count": summary.get( + "receipt_closeout_preview_count", 0 + ), + "receipt_validation_report_count": summary.get( + "receipt_validation_report_count", 0 + ), + "receipt_validation_field_count": summary.get( + "receipt_validation_field_count", 0 + ), + "rollback_binding_count": summary.get("rollback_binding_count", 0), + "post_apply_verifier_binding_count": summary.get( + "post_apply_verifier_binding_count", 0 + ), + "post_apply_verifier_required_count": summary.get( + "post_apply_verifier_required_count", 0 + ), + "same_run_truth_required_count": summary.get( + "same_run_truth_required_count", 0 + ), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + }, + "future_database_apply_controlled_dry_run_pre_apply_replay_verifier": ( + future_database_apply_controlled_dry_run_pre_apply_replay_verifier + ), + "controlled_dry_run_final_executor_guard_closeout": ( + controlled_dry_run_final_executor_guard_closeout + ), + "controlled_dry_run_final_executor_guard_closeout_contract": ( + controlled_dry_run_final_executor_guard_closeout_contract + ), + "controlled_dry_run_final_executor_guard_closeout_checks": checks, + "source_controlled_dry_run_no_apply_enforcement_closeout_summary": summary, + "source_controlled_dry_run_no_apply_enforcement_closeout_contract": ( + no_apply_contract + ), + "source_controlled_dry_run_no_apply_enforcement_closeout": ( + no_apply_closeout + ), + "source_database_apply_controlled_dry_run_final_dry_run_executor_guard": ( + future_guard + ), + "safety": { + "read_only_db_apply_controlled_dry_run_final_executor_guard_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future controlled dry-run pre-apply replay closeout.", + "Keep replay verification preview-only until a dedicated execution lane is explicit.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_pre_apply_replay_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out replay verification and bind an apply executor readiness contract.""" + final_closeout_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_final_executor_guard_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_replay_verifier = ( + final_closeout_result.get( + "future_database_apply_controlled_dry_run_pre_apply_replay_verifier" + ) + or {} + ) + final_closeout = ( + final_closeout_result.get( + "controlled_dry_run_final_executor_guard_closeout" + ) + or {} + ) + final_closeout_contract = ( + final_closeout_result.get( + "controlled_dry_run_final_executor_guard_closeout_contract" + ) + or {} + ) + summary = final_closeout_result.get("summary") or {} + safety = final_closeout_result.get("safety") or {} + replay_verifier = final_closeout.get("pre_apply_replay_verifier") or {} + final_guard = final_closeout.get("final_dry_run_executor_guard") or {} + no_apply_enforcement = ( + final_closeout.get("no_apply_enforcement_verification") or {} + ) + no_apply_closeout = final_closeout.get("no_apply_enforcement_closeout") or {} + rollback_binding = final_closeout.get("rollback_binding") or {} + verifier_binding = final_closeout.get("post_apply_verifier_binding") or {} + closeout_id = _db_apply_controlled_dry_run_pre_apply_replay_closeout_id( + final_closeout_result + ) + apply_executor_readiness_contract_id = ( + f"{closeout_id}-apply-executor-readiness-contract" + ) + pre_apply_replay_closeout_fields = [ + "pre_apply_replay_closeout_id", + "source_final_executor_guard_closeout_id", + "source_pre_apply_replay_verifier_id", + "source_final_dry_run_executor_guard_id", + "source_no_apply_enforcement_closeout_id", + "dry_run_command_shape_hash", + "apply_executor_readiness_contract_id", + "target_migration_file", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "dry_run_executor_invocation_allowed", + "abort_conditions", + ] + pre_apply_replay_closeout_acceptance_gates = [ + "final_executor_guard_closeout_ready", + "source_chain_ids_match", + "pre_apply_replay_verifier_ready", + "pre_apply_replay_preview_only", + "apply_executor_readiness_contract_bound", + "apply_executor_readiness_contract_blocks_apply", + "final_guard_and_no_apply_enforcement_carried_forward", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "no_secret_signature_or_database_apply", + ] + apply_executor_readiness_contract_fields = [ + "contract_id", + "source_pre_apply_replay_closeout_id", + "source_pre_apply_replay_verifier_id", + "source_final_dry_run_executor_guard_id", + "required_replay_mode", + "required_guard_status", + "required_command_shape_hash", + "dry_run_executor_invocation_allowed", + "endpoint_execution_allowed", + "sql_execution_allowed", + "database_write_allowed", + "database_apply_authorized", + ] + abort_conditions = [ + "abort_if_final_executor_guard_closeout_not_ready", + "abort_if_source_chain_ids_do_not_match", + "abort_if_pre_apply_replay_verifier_missing", + "abort_if_replay_mode_is_not_preview_only", + "abort_if_apply_executor_readiness_contract_missing", + "abort_if_contract_allows_apply_executor_invocation", + "abort_if_contract_allows_endpoint_or_sql_execution", + "abort_if_contract_allows_database_write_or_apply", + "abort_if_target_migration_hash_changes", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_any_secret_signature_or_apply_material_is_present", + ] + apply_executor_readiness_contract = { + "contract_id": apply_executor_readiness_contract_id, + "source_pre_apply_replay_closeout_id": closeout_id, + "source_pre_apply_replay_verifier_id": replay_verifier.get( + "verification_id" + ), + "source_final_executor_guard_closeout_id": final_closeout.get( + "final_executor_guard_closeout_id" + ), + "source_final_dry_run_executor_guard_id": final_guard.get("guard_id"), + "source_no_apply_enforcement_closeout_id": no_apply_closeout.get( + "no_apply_enforcement_closeout_id" + ), + "required_replay_mode": "pre_apply_replay_preview_only", + "required_guard_status": "final_dry_run_executor_guard_preview_ready", + "required_command_shape_hash": final_guard.get( + "required_command_shape_hash" + ), + "readiness_status": "apply_executor_readiness_contract_preview_ready", + "readiness_mode": "apply_executor_readiness_contract_preview_only", + "dry_run_executor_invocation_allowed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "reads_secret_in_preview": False, + "apply_executor_readiness_contract_field_count": len( + apply_executor_readiness_contract_fields + ), + "apply_executor_readiness_contract_fields": ( + apply_executor_readiness_contract_fields + ), + } + final_executor_guard_closeout_ready = ( + final_closeout_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_FINAL_EXECUTOR_GUARD_CLOSEOUT_READY" + and summary.get( + "controlled_dry_run_final_executor_guard_closeout_ready_count" + ) + == 1 + and summary.get( + "controlled_dry_run_final_executor_guard_closeout_pass_count" + ) + == summary.get( + "controlled_dry_run_final_executor_guard_closeout_check_count" + ) + ) + source_chain_ids_match = ( + bool(final_closeout.get("final_executor_guard_closeout_id")) + and final_closeout.get("final_executor_guard_closeout_id") + == future_replay_verifier.get("final_executor_guard_closeout_id") + == replay_verifier.get("source_final_executor_guard_closeout_id") + == apply_executor_readiness_contract.get( + "source_final_executor_guard_closeout_id" + ) + and replay_verifier.get("verification_id") + == future_replay_verifier.get("pre_apply_replay_verifier_id") + == apply_executor_readiness_contract.get( + "source_pre_apply_replay_verifier_id" + ) + and final_guard.get("guard_id") + == replay_verifier.get("source_final_dry_run_executor_guard_id") + == apply_executor_readiness_contract.get( + "source_final_dry_run_executor_guard_id" + ) + ) + pre_apply_replay_verifier_ready = ( + replay_verifier.get("verifier_status") + == "pre_apply_replay_verifier_preview_ready" + and replay_verifier.get("verification_id") + == future_replay_verifier.get("pre_apply_replay_verifier_id") + and int(replay_verifier.get("pre_apply_replay_verifier_field_count") or 0) + == 12 + ) + pre_apply_replay_preview_only = ( + replay_verifier.get("replay_mode") == "pre_apply_replay_preview_only" + and replay_verifier.get("dry_run_executor_invocation_allowed") is False + and replay_verifier.get("endpoint_execution_allowed") is False + and replay_verifier.get("sql_execution_allowed") is False + and replay_verifier.get("database_write_allowed") is False + and replay_verifier.get("database_apply_authorized") is False + and replay_verifier.get("executes_database_apply") is False + and replay_verifier.get("executes_endpoint") is False + and replay_verifier.get("executes_sql") is False + and replay_verifier.get("writes_database") is False + ) + apply_executor_readiness_contract_bound = ( + bool(apply_executor_readiness_contract.get("contract_id")) + and apply_executor_readiness_contract.get( + "source_pre_apply_replay_closeout_id" + ) + == closeout_id + and apply_executor_readiness_contract.get( + "source_pre_apply_replay_verifier_id" + ) + == replay_verifier.get("verification_id") + and apply_executor_readiness_contract.get("required_command_shape_hash") + == final_guard.get("required_command_shape_hash") + and int( + apply_executor_readiness_contract.get( + "apply_executor_readiness_contract_field_count" + ) + or 0 + ) + == len(apply_executor_readiness_contract_fields) + ) + apply_executor_readiness_contract_blocks_apply = ( + apply_executor_readiness_contract.get("readiness_mode") + == "apply_executor_readiness_contract_preview_only" + and apply_executor_readiness_contract.get( + "dry_run_executor_invocation_allowed" + ) + is False + and apply_executor_readiness_contract.get("endpoint_execution_allowed") + is False + and apply_executor_readiness_contract.get("sql_execution_allowed") is False + and apply_executor_readiness_contract.get("database_write_allowed") + is False + and apply_executor_readiness_contract.get("database_apply_authorized") + is False + and apply_executor_readiness_contract.get("executes_database_apply") + is False + and apply_executor_readiness_contract.get("executes_endpoint") is False + and apply_executor_readiness_contract.get("executes_sql") is False + and apply_executor_readiness_contract.get("writes_database") is False + ) + final_guard_and_no_apply_enforcement_carried_forward = ( + final_guard.get("guard_status") == "final_dry_run_executor_guard_preview_ready" + and final_guard.get("dry_run_executor_invocation_allowed") is False + and final_guard.get("database_apply_authorized") is False + and no_apply_enforcement.get("enforcement_status") + == "no_apply_enforcement_preview_ready" + and no_apply_enforcement.get("database_apply_authorized") is False + and bool(no_apply_closeout.get("no_apply_enforcement_closeout_id")) + ) + target_hash_locked = ( + final_closeout.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(final_closeout.get("expected_sha256")) + and bool(final_closeout.get("actual_sha256")) + and final_closeout.get("expected_sha256") + == final_closeout.get("actual_sha256") + and final_closeout.get("hash_matches") is True + and final_closeout.get("target_migration_hash_locked") is True + ) + rollback_and_verifier_bound = ( + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False + ) + final_executor_guard_contract_blocks_database_apply = ( + final_closeout_contract.get("executes_database_apply") is False + and final_closeout_contract.get("executes_endpoint") is False + and final_closeout_contract.get("executes_sql") is False + and final_closeout_contract.get("database_apply_authorized") is False + and final_closeout_contract.get("ready_for_database_apply_now") is False + and final_closeout_contract.get("signs_database_apply_authorization") + is False + and final_closeout_contract.get("writes_database") is False + and final_closeout_contract.get("executes_in_preview") is False + ) + side_effect_free = ( + summary.get("reads_secret_count", 0) == 0 + and summary.get("executes_script_count", 0) == 0 + and summary.get("executes_migration_count", 0) == 0 + and summary.get("executes_endpoint_count", 0) == 0 + and summary.get("executes_sql_count", 0) == 0 + and summary.get("writes_database_count", 0) == 0 + and summary.get("signs_database_apply_authorization_count", 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("executes_database_apply") is False + and pre_apply_replay_preview_only + and apply_executor_readiness_contract_blocks_apply + ) + checks = [ + _controlled_dry_run_pre_apply_replay_closeout_check( + "final_executor_guard_closeout_ready", + final_executor_guard_closeout_ready, + { + "result": final_closeout_result.get("result"), + "ready_count": summary.get( + "controlled_dry_run_final_executor_guard_closeout_ready_count" + ), + "pass_count": summary.get( + "controlled_dry_run_final_executor_guard_closeout_pass_count" + ), + "check_count": summary.get( + "controlled_dry_run_final_executor_guard_closeout_check_count" + ), + }, + "wait_for_final_executor_guard_closeout_ready", + ), + _controlled_dry_run_pre_apply_replay_closeout_check( + "source_chain_ids_match", + source_chain_ids_match, + { + "final_executor_guard_closeout_id": final_closeout.get( + "final_executor_guard_closeout_id" + ), + "pre_apply_replay_verifier_id": replay_verifier.get( + "verification_id" + ), + "final_guard_id": final_guard.get("guard_id"), + }, + "wait_for_source_chain_ids_match", + ), + _controlled_dry_run_pre_apply_replay_closeout_check( + "pre_apply_replay_verifier_ready", + pre_apply_replay_verifier_ready, + { + "verification_id": replay_verifier.get("verification_id"), + "verifier_status": replay_verifier.get("verifier_status"), + "field_count": replay_verifier.get( + "pre_apply_replay_verifier_field_count" + ), + }, + "wait_for_pre_apply_replay_verifier_ready", + ), + _controlled_dry_run_pre_apply_replay_closeout_check( + "pre_apply_replay_preview_only", + pre_apply_replay_preview_only, + { + "replay_mode": replay_verifier.get("replay_mode"), + "dry_run_executor_invocation_allowed": replay_verifier.get( + "dry_run_executor_invocation_allowed" + ), + "database_apply_authorized": replay_verifier.get( + "database_apply_authorized" + ), + }, + "abort_if_pre_apply_replay_requests_execution", + ), + _controlled_dry_run_pre_apply_replay_closeout_check( + "apply_executor_readiness_contract_bound", + apply_executor_readiness_contract_bound, + { + "contract_id": apply_executor_readiness_contract.get( + "contract_id" + ), + "source_pre_apply_replay_verifier_id": ( + apply_executor_readiness_contract.get( + "source_pre_apply_replay_verifier_id" + ) + ), + "field_count": apply_executor_readiness_contract.get( + "apply_executor_readiness_contract_field_count" + ), + }, + "wait_for_apply_executor_readiness_contract_binding", + ), + _controlled_dry_run_pre_apply_replay_closeout_check( + "apply_executor_readiness_contract_blocks_apply", + apply_executor_readiness_contract_blocks_apply, + { + "readiness_mode": apply_executor_readiness_contract.get( + "readiness_mode" + ), + "dry_run_executor_invocation_allowed": ( + apply_executor_readiness_contract.get( + "dry_run_executor_invocation_allowed" + ) + ), + "database_apply_authorized": apply_executor_readiness_contract.get( + "database_apply_authorized" + ), + }, + "abort_if_apply_executor_readiness_contract_allows_apply", + ), + _controlled_dry_run_pre_apply_replay_closeout_check( + "final_guard_and_no_apply_enforcement_carried_forward", + final_guard_and_no_apply_enforcement_carried_forward, + { + "guard_status": final_guard.get("guard_status"), + "enforcement_status": no_apply_enforcement.get( + "enforcement_status" + ), + "no_apply_enforcement_closeout_id": no_apply_closeout.get( + "no_apply_enforcement_closeout_id" + ), + }, + "wait_for_final_guard_and_no_apply_enforcement_carry_forward", + ), + _controlled_dry_run_pre_apply_replay_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": final_closeout.get("target_file"), + "hash_matches": final_closeout.get("hash_matches"), + "expected_sha256_present": bool( + final_closeout.get("expected_sha256") + ), + "actual_sha256_present": bool(final_closeout.get("actual_sha256")), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_pre_apply_replay_closeout_check( + "rollback_and_post_apply_verifier_bound", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "database_apply_authorized": verifier_binding.get( + "database_apply_authorized" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_pre_apply_replay_closeout_check( + "final_executor_guard_contract_blocks_database_apply", + final_executor_guard_contract_blocks_database_apply, + { + "permits_future_database_apply_controlled_dry_run_pre_apply_replay_verifier": ( + final_closeout_contract.get( + "permits_future_database_apply_controlled_dry_run_pre_apply_replay_verifier" + ) + ), + "database_apply_authorized": final_closeout_contract.get( + "database_apply_authorized" + ), + "writes_database": final_closeout_contract.get("writes_database"), + }, + "abort_if_final_executor_guard_contract_authorizes_database_apply", + ), + _controlled_dry_run_pre_apply_replay_closeout_check( + "preview_has_no_side_effects_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + }, + "abort_on_preview_side_effect_execution_or_signing", + ), + _controlled_dry_run_pre_apply_replay_closeout_check( + "manual_review_not_required_for_safe_preview", + final_closeout_contract.get("manual_review_mode") == "exception_only" + and safety.get("manual_review_mode") == "exception_only", + { + "contract_manual_review_mode": final_closeout_contract.get( + "manual_review_mode" + ), + "safety_manual_review_mode": safety.get("manual_review_mode"), + }, + "keep_manual_review_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_PRE_APPLY_REPLAY_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_FINAL_EXECUTOR_GUARD_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_apply_executor_readiness_contract = { + "pre_apply_replay_closeout_id": closeout_id, + "apply_executor_readiness_contract_id": ( + apply_executor_readiness_contract_id + ), + "source_final_executor_guard_closeout_id": final_closeout.get( + "final_executor_guard_closeout_id" + ), + "source_pre_apply_replay_verifier_id": replay_verifier.get( + "verification_id" + ), + "source_final_dry_run_executor_guard_id": final_guard.get("guard_id"), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_apply_executor_readiness_contract": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_apply_executor_readiness_closeout": ( + closeout_ready + ), + "pre_apply_replay_closeout_ready": closeout_ready, + "apply_executor_readiness_contract_bound": closeout_ready, + "dry_run_executor_invocation_allowed": False, + "dry_run_execution_performed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "reads_secret_in_preview": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_pre_apply_replay_closeout = { + "pre_apply_replay_closeout_id": closeout_id, + "authorization_material_type": ( + "controlled_dry_run_pre_apply_replay_closeout" + ), + "source_final_executor_guard_closeout_id": final_closeout.get( + "final_executor_guard_closeout_id" + ), + "source_pre_apply_replay_verifier_id": replay_verifier.get( + "verification_id" + ), + "source_final_dry_run_executor_guard_id": final_guard.get("guard_id"), + "source_no_apply_enforcement_closeout_id": no_apply_closeout.get( + "no_apply_enforcement_closeout_id" + ), + "dry_run_command_shape_hash": final_guard.get( + "required_command_shape_hash" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_pre_apply_replay_closeout": ( + closeout_ready + ), + "pre_apply_replay_closeout_fields": pre_apply_replay_closeout_fields, + "pre_apply_replay_closeout_field_count": len( + pre_apply_replay_closeout_fields + ), + "pre_apply_replay_closeout_acceptance_gates": ( + pre_apply_replay_closeout_acceptance_gates + ), + "pre_apply_replay_closeout_acceptance_gate_count": len( + pre_apply_replay_closeout_acceptance_gates + ), + "apply_executor_readiness_contract": apply_executor_readiness_contract, + "apply_executor_readiness_contract_count": 1, + "apply_executor_readiness_contract_field_count": len( + apply_executor_readiness_contract_fields + ), + "pre_apply_replay_verifier": replay_verifier, + "pre_apply_replay_verifier_count": 1, + "pre_apply_replay_verifier_field_count": len( + replay_verifier.get("pre_apply_replay_verifier_fields") or [] + ), + "final_executor_guard_closeout": final_closeout, + "final_executor_guard_closeout_count": 1, + "final_dry_run_executor_guard": final_guard, + "final_dry_run_executor_guard_count": 1, + "no_apply_enforcement_verification": no_apply_enforcement, + "no_apply_enforcement_verification_count": 1, + "no_apply_enforcement_closeout": no_apply_closeout, + "no_apply_enforcement_closeout_count": 1, + "target_file": final_closeout.get("target_file"), + "expected_sha256": final_closeout.get("expected_sha256"), + "actual_sha256": final_closeout.get("actual_sha256"), + "hash_matches": final_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "pre_apply_replay_closeout_only": True, + "apply_executor_readiness_contract_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "dry_run_executor_invocation_allowed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + } + controlled_dry_run_pre_apply_replay_closeout_contract = { + "mode": "controlled_dry_run_pre_apply_replay_closeout_and_apply_executor_readiness_contract_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-pre-apply-replay-closeout" + ), + "source_final_executor_guard_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-final-executor-guard-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_apply_executor_readiness_contract": ( + closeout_ready + ), + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": False, + "signs_database_apply_authorization": False, + "writes_database": False, + "executes_in_preview": False, + "secret_material_required_in_preview": False, + "manual_review_mode": "exception_only", + } + output_summary = dict(summary) + output_summary.update( + { + "controlled_dry_run_pre_apply_replay_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_pre_apply_replay_closeout_check_count": len( + checks + ), + "controlled_dry_run_pre_apply_replay_closeout_pass_count": ( + passed_count + ), + "controlled_dry_run_pre_apply_replay_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_pre_apply_replay_closeout_count": 1, + "controlled_dry_run_pre_apply_replay_closeout_field_count": len( + pre_apply_replay_closeout_fields + ), + "controlled_dry_run_pre_apply_replay_closeout_acceptance_gate_count": len( + pre_apply_replay_closeout_acceptance_gates + ), + "apply_executor_readiness_contract_count": 1, + "apply_executor_readiness_contract_field_count": len( + apply_executor_readiness_contract_fields + ), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + } + ) + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_PRE_APPLY_REPLAY_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(final_closeout_result.get("success")), + "generated_at": final_closeout_result.get("generated_at"), + "source_policy": final_closeout_result.get("policy"), + "stats": final_closeout_result.get("stats") or {}, + "summary": output_summary, + "future_database_apply_controlled_dry_run_apply_executor_readiness_contract": ( + future_database_apply_controlled_dry_run_apply_executor_readiness_contract + ), + "controlled_dry_run_pre_apply_replay_closeout": ( + controlled_dry_run_pre_apply_replay_closeout + ), + "controlled_dry_run_pre_apply_replay_closeout_contract": ( + controlled_dry_run_pre_apply_replay_closeout_contract + ), + "controlled_dry_run_pre_apply_replay_closeout_checks": checks, + "source_controlled_dry_run_final_executor_guard_closeout_summary": ( + summary + ), + "source_controlled_dry_run_final_executor_guard_closeout_contract": ( + final_closeout_contract + ), + "source_controlled_dry_run_final_executor_guard_closeout": ( + final_closeout + ), + "source_database_apply_controlled_dry_run_pre_apply_replay_verifier": ( + future_replay_verifier + ), + "safety": { + "read_only_db_apply_controlled_dry_run_pre_apply_replay_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future controlled dry-run apply executor readiness closeout.", + "Keep the dry-run executor invocation disabled until invocation readiness is machine-verifiable.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_apply_executor_readiness_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out apply executor readiness and bind a dry-run invocation receipt.""" + pre_apply_closeout_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_pre_apply_replay_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_apply_contract = ( + pre_apply_closeout_result.get( + "future_database_apply_controlled_dry_run_apply_executor_readiness_contract" + ) + or {} + ) + pre_apply_closeout = ( + pre_apply_closeout_result.get( + "controlled_dry_run_pre_apply_replay_closeout" + ) + or {} + ) + pre_apply_contract = ( + pre_apply_closeout_result.get( + "controlled_dry_run_pre_apply_replay_closeout_contract" + ) + or {} + ) + summary = pre_apply_closeout_result.get("summary") or {} + safety = pre_apply_closeout_result.get("safety") or {} + readiness_contract = pre_apply_closeout.get( + "apply_executor_readiness_contract" + ) or {} + replay_verifier = pre_apply_closeout.get("pre_apply_replay_verifier") or {} + final_guard = pre_apply_closeout.get("final_dry_run_executor_guard") or {} + no_apply_enforcement = ( + pre_apply_closeout.get("no_apply_enforcement_verification") or {} + ) + rollback_binding = pre_apply_closeout.get("rollback_binding") or {} + verifier_binding = pre_apply_closeout.get("post_apply_verifier_binding") or {} + closeout_id = _db_apply_controlled_dry_run_apply_executor_readiness_closeout_id( + pre_apply_closeout_result + ) + receipt_id = f"{closeout_id}-dry-run-invocation-readiness-receipt" + readiness_closeout_fields = [ + "apply_executor_readiness_closeout_id", + "source_pre_apply_replay_closeout_id", + "source_apply_executor_readiness_contract_id", + "source_pre_apply_replay_verifier_id", + "source_final_dry_run_executor_guard_id", + "dry_run_command_shape_hash", + "dry_run_invocation_readiness_receipt_id", + "target_migration_file", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "dry_run_executor_invocation_allowed", + "abort_conditions", + ] + readiness_closeout_acceptance_gates = [ + "pre_apply_replay_closeout_ready", + "source_chain_ids_match", + "apply_executor_readiness_contract_ready", + "apply_executor_readiness_contract_blocks_invocation", + "dry_run_invocation_readiness_receipt_bound", + "dry_run_invocation_readiness_receipt_no_execute", + "pre_apply_replay_and_final_guard_carried_forward", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "no_secret_signature_or_database_apply", + ] + dry_run_invocation_readiness_receipt_fields = [ + "receipt_id", + "source_apply_executor_readiness_closeout_id", + "source_apply_executor_readiness_contract_id", + "source_pre_apply_replay_closeout_id", + "source_pre_apply_replay_verifier_id", + "required_readiness_mode", + "required_guard_status", + "required_command_shape_hash", + "receipt_mode", + "dry_run_executor_invocation_allowed", + "endpoint_execution_allowed", + "database_apply_authorized", + ] + abort_conditions = [ + "abort_if_pre_apply_replay_closeout_not_ready", + "abort_if_source_chain_ids_do_not_match", + "abort_if_apply_executor_readiness_contract_missing", + "abort_if_contract_allows_dry_run_executor_invocation", + "abort_if_invocation_readiness_receipt_missing", + "abort_if_invocation_readiness_receipt_executes", + "abort_if_endpoint_or_sql_execution_is_allowed", + "abort_if_database_write_or_apply_is_allowed", + "abort_if_target_migration_hash_changes", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_any_secret_signature_or_apply_material_is_present", + ] + dry_run_invocation_readiness_receipt = { + "receipt_id": receipt_id, + "source_apply_executor_readiness_closeout_id": closeout_id, + "source_apply_executor_readiness_contract_id": readiness_contract.get( + "contract_id" + ), + "source_pre_apply_replay_closeout_id": pre_apply_closeout.get( + "pre_apply_replay_closeout_id" + ), + "source_pre_apply_replay_verifier_id": replay_verifier.get( + "verification_id" + ), + "source_final_dry_run_executor_guard_id": final_guard.get("guard_id"), + "required_readiness_mode": "apply_executor_readiness_contract_preview_only", + "required_guard_status": "final_dry_run_executor_guard_preview_ready", + "required_command_shape_hash": readiness_contract.get( + "required_command_shape_hash" + ), + "receipt_status": "dry_run_invocation_readiness_receipt_preview_ready", + "receipt_mode": "dry_run_invocation_readiness_preview_only", + "dry_run_executor_invocation_allowed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "reads_secret_in_preview": False, + "dry_run_invocation_readiness_receipt_field_count": len( + dry_run_invocation_readiness_receipt_fields + ), + "dry_run_invocation_readiness_receipt_fields": ( + dry_run_invocation_readiness_receipt_fields + ), + } + pre_apply_replay_closeout_ready = ( + pre_apply_closeout_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_PRE_APPLY_REPLAY_CLOSEOUT_READY" + and summary.get( + "controlled_dry_run_pre_apply_replay_closeout_ready_count" + ) + == 1 + and summary.get( + "controlled_dry_run_pre_apply_replay_closeout_pass_count" + ) + == summary.get( + "controlled_dry_run_pre_apply_replay_closeout_check_count" + ) + ) + source_chain_ids_match = ( + bool(pre_apply_closeout.get("pre_apply_replay_closeout_id")) + and pre_apply_closeout.get("pre_apply_replay_closeout_id") + == future_apply_contract.get("pre_apply_replay_closeout_id") + == readiness_contract.get("source_pre_apply_replay_closeout_id") + == dry_run_invocation_readiness_receipt.get( + "source_pre_apply_replay_closeout_id" + ) + and readiness_contract.get("contract_id") + == future_apply_contract.get("apply_executor_readiness_contract_id") + == dry_run_invocation_readiness_receipt.get( + "source_apply_executor_readiness_contract_id" + ) + and replay_verifier.get("verification_id") + == readiness_contract.get("source_pre_apply_replay_verifier_id") + == dry_run_invocation_readiness_receipt.get( + "source_pre_apply_replay_verifier_id" + ) + and final_guard.get("guard_id") + == readiness_contract.get("source_final_dry_run_executor_guard_id") + == dry_run_invocation_readiness_receipt.get( + "source_final_dry_run_executor_guard_id" + ) + ) + apply_executor_readiness_contract_ready = ( + readiness_contract.get("readiness_status") + == "apply_executor_readiness_contract_preview_ready" + and readiness_contract.get("contract_id") + == future_apply_contract.get("apply_executor_readiness_contract_id") + and int( + readiness_contract.get( + "apply_executor_readiness_contract_field_count" + ) + or 0 + ) + == 12 + ) + apply_executor_readiness_contract_blocks_invocation = ( + readiness_contract.get("readiness_mode") + == "apply_executor_readiness_contract_preview_only" + and readiness_contract.get("dry_run_executor_invocation_allowed") + is False + and readiness_contract.get("endpoint_execution_allowed") is False + and readiness_contract.get("sql_execution_allowed") is False + and readiness_contract.get("database_write_allowed") is False + and readiness_contract.get("database_apply_authorized") is False + and readiness_contract.get("executes_database_apply") is False + and readiness_contract.get("executes_endpoint") is False + and readiness_contract.get("executes_sql") is False + and readiness_contract.get("writes_database") is False + ) + dry_run_invocation_readiness_receipt_bound = ( + bool(dry_run_invocation_readiness_receipt.get("receipt_id")) + and dry_run_invocation_readiness_receipt.get( + "source_apply_executor_readiness_closeout_id" + ) + == closeout_id + and dry_run_invocation_readiness_receipt.get( + "source_apply_executor_readiness_contract_id" + ) + == readiness_contract.get("contract_id") + and dry_run_invocation_readiness_receipt.get( + "required_command_shape_hash" + ) + == readiness_contract.get("required_command_shape_hash") + and int( + dry_run_invocation_readiness_receipt.get( + "dry_run_invocation_readiness_receipt_field_count" + ) + or 0 + ) + == len(dry_run_invocation_readiness_receipt_fields) + ) + dry_run_invocation_readiness_receipt_no_execute = ( + dry_run_invocation_readiness_receipt.get("receipt_mode") + == "dry_run_invocation_readiness_preview_only" + and dry_run_invocation_readiness_receipt.get( + "dry_run_executor_invocation_allowed" + ) + is False + and dry_run_invocation_readiness_receipt.get( + "ready_for_dry_run_executor_invocation_now" + ) + is False + and dry_run_invocation_readiness_receipt.get("endpoint_execution_allowed") + is False + and dry_run_invocation_readiness_receipt.get("sql_execution_allowed") + is False + and dry_run_invocation_readiness_receipt.get("database_write_allowed") + is False + and dry_run_invocation_readiness_receipt.get("database_apply_authorized") + is False + and dry_run_invocation_readiness_receipt.get("executes_database_apply") + is False + and dry_run_invocation_readiness_receipt.get("executes_endpoint") is False + and dry_run_invocation_readiness_receipt.get("executes_sql") is False + and dry_run_invocation_readiness_receipt.get("writes_database") is False + ) + pre_apply_replay_and_final_guard_carried_forward = ( + replay_verifier.get("replay_mode") == "pre_apply_replay_preview_only" + and replay_verifier.get("dry_run_executor_invocation_allowed") is False + and replay_verifier.get("database_apply_authorized") is False + and final_guard.get("guard_status") + == "final_dry_run_executor_guard_preview_ready" + and final_guard.get("dry_run_executor_invocation_allowed") is False + and final_guard.get("database_apply_authorized") is False + and no_apply_enforcement.get("enforcement_status") + == "no_apply_enforcement_preview_ready" + and no_apply_enforcement.get("database_apply_authorized") is False + ) + target_hash_locked = ( + pre_apply_closeout.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(pre_apply_closeout.get("expected_sha256")) + and bool(pre_apply_closeout.get("actual_sha256")) + and pre_apply_closeout.get("expected_sha256") + == pre_apply_closeout.get("actual_sha256") + and pre_apply_closeout.get("hash_matches") is True + and pre_apply_closeout.get("target_migration_hash_locked") is True + ) + rollback_and_verifier_bound = ( + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False + ) + pre_apply_contract_blocks_database_apply = ( + pre_apply_contract.get("executes_database_apply") is False + and pre_apply_contract.get("executes_endpoint") is False + and pre_apply_contract.get("executes_sql") is False + and pre_apply_contract.get("database_apply_authorized") is False + and pre_apply_contract.get("ready_for_database_apply_now") is False + and pre_apply_contract.get("ready_for_dry_run_executor_invocation_now") + is False + and pre_apply_contract.get("signs_database_apply_authorization") is False + and pre_apply_contract.get("writes_database") is False + and pre_apply_contract.get("executes_in_preview") is False + ) + side_effect_free = ( + summary.get("reads_secret_count", 0) == 0 + and summary.get("executes_script_count", 0) == 0 + and summary.get("executes_migration_count", 0) == 0 + and summary.get("executes_endpoint_count", 0) == 0 + and summary.get("executes_sql_count", 0) == 0 + and summary.get("writes_database_count", 0) == 0 + and summary.get("signs_database_apply_authorization_count", 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("executes_database_apply") is False + and apply_executor_readiness_contract_blocks_invocation + and dry_run_invocation_readiness_receipt_no_execute + ) + checks = [ + _controlled_dry_run_apply_executor_readiness_closeout_check( + "pre_apply_replay_closeout_ready", + pre_apply_replay_closeout_ready, + { + "result": pre_apply_closeout_result.get("result"), + "ready_count": summary.get( + "controlled_dry_run_pre_apply_replay_closeout_ready_count" + ), + "pass_count": summary.get( + "controlled_dry_run_pre_apply_replay_closeout_pass_count" + ), + "check_count": summary.get( + "controlled_dry_run_pre_apply_replay_closeout_check_count" + ), + }, + "wait_for_pre_apply_replay_closeout_ready", + ), + _controlled_dry_run_apply_executor_readiness_closeout_check( + "source_chain_ids_match", + source_chain_ids_match, + { + "pre_apply_replay_closeout_id": pre_apply_closeout.get( + "pre_apply_replay_closeout_id" + ), + "apply_executor_readiness_contract_id": readiness_contract.get( + "contract_id" + ), + "pre_apply_replay_verifier_id": replay_verifier.get( + "verification_id" + ), + }, + "wait_for_source_chain_ids_match", + ), + _controlled_dry_run_apply_executor_readiness_closeout_check( + "apply_executor_readiness_contract_ready", + apply_executor_readiness_contract_ready, + { + "contract_id": readiness_contract.get("contract_id"), + "readiness_status": readiness_contract.get("readiness_status"), + "field_count": readiness_contract.get( + "apply_executor_readiness_contract_field_count" + ), + }, + "wait_for_apply_executor_readiness_contract_ready", + ), + _controlled_dry_run_apply_executor_readiness_closeout_check( + "apply_executor_readiness_contract_blocks_invocation", + apply_executor_readiness_contract_blocks_invocation, + { + "readiness_mode": readiness_contract.get("readiness_mode"), + "dry_run_executor_invocation_allowed": readiness_contract.get( + "dry_run_executor_invocation_allowed" + ), + "database_apply_authorized": readiness_contract.get( + "database_apply_authorized" + ), + }, + "abort_if_apply_executor_readiness_contract_allows_invocation", + ), + _controlled_dry_run_apply_executor_readiness_closeout_check( + "dry_run_invocation_readiness_receipt_bound", + dry_run_invocation_readiness_receipt_bound, + { + "receipt_id": dry_run_invocation_readiness_receipt.get( + "receipt_id" + ), + "source_apply_executor_readiness_contract_id": ( + dry_run_invocation_readiness_receipt.get( + "source_apply_executor_readiness_contract_id" + ) + ), + "field_count": dry_run_invocation_readiness_receipt.get( + "dry_run_invocation_readiness_receipt_field_count" + ), + }, + "wait_for_dry_run_invocation_readiness_receipt_binding", + ), + _controlled_dry_run_apply_executor_readiness_closeout_check( + "dry_run_invocation_readiness_receipt_no_execute", + dry_run_invocation_readiness_receipt_no_execute, + { + "receipt_mode": dry_run_invocation_readiness_receipt.get( + "receipt_mode" + ), + "dry_run_executor_invocation_allowed": ( + dry_run_invocation_readiness_receipt.get( + "dry_run_executor_invocation_allowed" + ) + ), + "ready_for_dry_run_executor_invocation_now": ( + dry_run_invocation_readiness_receipt.get( + "ready_for_dry_run_executor_invocation_now" + ) + ), + }, + "abort_if_dry_run_invocation_readiness_receipt_executes", + ), + _controlled_dry_run_apply_executor_readiness_closeout_check( + "pre_apply_replay_and_final_guard_carried_forward", + pre_apply_replay_and_final_guard_carried_forward, + { + "replay_mode": replay_verifier.get("replay_mode"), + "guard_status": final_guard.get("guard_status"), + "enforcement_status": no_apply_enforcement.get( + "enforcement_status" + ), + }, + "wait_for_pre_apply_replay_and_final_guard_carry_forward", + ), + _controlled_dry_run_apply_executor_readiness_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": pre_apply_closeout.get("target_file"), + "hash_matches": pre_apply_closeout.get("hash_matches"), + "expected_sha256_present": bool( + pre_apply_closeout.get("expected_sha256") + ), + "actual_sha256_present": bool(pre_apply_closeout.get("actual_sha256")), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_apply_executor_readiness_closeout_check( + "rollback_and_post_apply_verifier_bound", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "database_apply_authorized": verifier_binding.get( + "database_apply_authorized" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_apply_executor_readiness_closeout_check( + "pre_apply_replay_contract_blocks_database_apply", + pre_apply_contract_blocks_database_apply, + { + "permits_future_database_apply_controlled_dry_run_apply_executor_readiness_contract": ( + pre_apply_contract.get( + "permits_future_database_apply_controlled_dry_run_apply_executor_readiness_contract" + ) + ), + "database_apply_authorized": pre_apply_contract.get( + "database_apply_authorized" + ), + "writes_database": pre_apply_contract.get("writes_database"), + }, + "abort_if_pre_apply_replay_contract_authorizes_database_apply", + ), + _controlled_dry_run_apply_executor_readiness_closeout_check( + "preview_has_no_side_effects_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + }, + "abort_on_preview_side_effect_execution_or_signing", + ), + _controlled_dry_run_apply_executor_readiness_closeout_check( + "manual_review_not_required_for_safe_preview", + pre_apply_contract.get("manual_review_mode") == "exception_only" + and safety.get("manual_review_mode") == "exception_only", + { + "contract_manual_review_mode": pre_apply_contract.get( + "manual_review_mode" + ), + "safety_manual_review_mode": safety.get("manual_review_mode"), + }, + "keep_manual_review_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_APPLY_EXECUTOR_READINESS_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_PRE_APPLY_REPLAY_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_invocation_readiness_receipt = { + "apply_executor_readiness_closeout_id": closeout_id, + "dry_run_invocation_readiness_receipt_id": receipt_id, + "source_pre_apply_replay_closeout_id": pre_apply_closeout.get( + "pre_apply_replay_closeout_id" + ), + "source_apply_executor_readiness_contract_id": readiness_contract.get( + "contract_id" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_invocation_readiness_receipt": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_invocation_receipt_closeout": ( + closeout_ready + ), + "apply_executor_readiness_closeout_ready": closeout_ready, + "dry_run_invocation_readiness_receipt_bound": closeout_ready, + "dry_run_executor_invocation_allowed": False, + "dry_run_execution_performed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "reads_secret_in_preview": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_apply_executor_readiness_closeout = { + "apply_executor_readiness_closeout_id": closeout_id, + "authorization_material_type": ( + "controlled_dry_run_apply_executor_readiness_closeout" + ), + "source_pre_apply_replay_closeout_id": pre_apply_closeout.get( + "pre_apply_replay_closeout_id" + ), + "source_apply_executor_readiness_contract_id": readiness_contract.get( + "contract_id" + ), + "source_pre_apply_replay_verifier_id": replay_verifier.get( + "verification_id" + ), + "source_final_dry_run_executor_guard_id": final_guard.get("guard_id"), + "dry_run_command_shape_hash": readiness_contract.get( + "required_command_shape_hash" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_apply_executor_readiness_closeout": ( + closeout_ready + ), + "apply_executor_readiness_closeout_fields": readiness_closeout_fields, + "apply_executor_readiness_closeout_field_count": len( + readiness_closeout_fields + ), + "apply_executor_readiness_closeout_acceptance_gates": ( + readiness_closeout_acceptance_gates + ), + "apply_executor_readiness_closeout_acceptance_gate_count": len( + readiness_closeout_acceptance_gates + ), + "dry_run_invocation_readiness_receipt": ( + dry_run_invocation_readiness_receipt + ), + "dry_run_invocation_readiness_receipt_count": 1, + "dry_run_invocation_readiness_receipt_field_count": len( + dry_run_invocation_readiness_receipt_fields + ), + "apply_executor_readiness_contract": readiness_contract, + "apply_executor_readiness_contract_count": 1, + "pre_apply_replay_closeout": pre_apply_closeout, + "pre_apply_replay_closeout_count": 1, + "pre_apply_replay_verifier": replay_verifier, + "pre_apply_replay_verifier_count": 1, + "final_dry_run_executor_guard": final_guard, + "final_dry_run_executor_guard_count": 1, + "no_apply_enforcement_verification": no_apply_enforcement, + "no_apply_enforcement_verification_count": 1, + "target_file": pre_apply_closeout.get("target_file"), + "expected_sha256": pre_apply_closeout.get("expected_sha256"), + "actual_sha256": pre_apply_closeout.get("actual_sha256"), + "hash_matches": pre_apply_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "apply_executor_readiness_closeout_only": True, + "dry_run_invocation_readiness_receipt_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "dry_run_executor_invocation_allowed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + } + controlled_dry_run_apply_executor_readiness_closeout_contract = { + "mode": "controlled_dry_run_apply_executor_readiness_closeout_and_dry_run_invocation_readiness_receipt_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-apply-executor-readiness-closeout" + ), + "source_pre_apply_replay_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-pre-apply-replay-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_invocation_readiness_receipt": ( + closeout_ready + ), + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": False, + "signs_database_apply_authorization": False, + "writes_database": False, + "executes_in_preview": False, + "secret_material_required_in_preview": False, + "manual_review_mode": "exception_only", + } + output_summary = dict(summary) + output_summary.update( + { + "controlled_dry_run_apply_executor_readiness_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_apply_executor_readiness_closeout_check_count": len( + checks + ), + "controlled_dry_run_apply_executor_readiness_closeout_pass_count": ( + passed_count + ), + "controlled_dry_run_apply_executor_readiness_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_apply_executor_readiness_closeout_count": 1, + "controlled_dry_run_apply_executor_readiness_closeout_field_count": len( + readiness_closeout_fields + ), + "controlled_dry_run_apply_executor_readiness_closeout_acceptance_gate_count": len( + readiness_closeout_acceptance_gates + ), + "dry_run_invocation_readiness_receipt_count": 1, + "dry_run_invocation_readiness_receipt_field_count": len( + dry_run_invocation_readiness_receipt_fields + ), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + } + ) + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_APPLY_EXECUTOR_READINESS_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(pre_apply_closeout_result.get("success")), + "generated_at": pre_apply_closeout_result.get("generated_at"), + "source_policy": pre_apply_closeout_result.get("policy"), + "stats": pre_apply_closeout_result.get("stats") or {}, + "summary": output_summary, + "future_database_apply_controlled_dry_run_invocation_readiness_receipt": ( + future_database_apply_controlled_dry_run_invocation_readiness_receipt + ), + "controlled_dry_run_apply_executor_readiness_closeout": ( + controlled_dry_run_apply_executor_readiness_closeout + ), + "controlled_dry_run_apply_executor_readiness_closeout_contract": ( + controlled_dry_run_apply_executor_readiness_closeout_contract + ), + "controlled_dry_run_apply_executor_readiness_closeout_checks": checks, + "source_controlled_dry_run_pre_apply_replay_closeout_summary": summary, + "source_controlled_dry_run_pre_apply_replay_closeout_contract": ( + pre_apply_contract + ), + "source_controlled_dry_run_pre_apply_replay_closeout": ( + pre_apply_closeout + ), + "source_database_apply_controlled_dry_run_apply_executor_readiness_contract": ( + future_apply_contract + ), + "safety": { + "read_only_db_apply_controlled_dry_run_apply_executor_readiness_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future controlled dry-run invocation receipt closeout.", + "Keep actual dry-run executor invocation disabled until a dedicated no-write invocation lane is machine-verifiable.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_invocation_receipt_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out invocation readiness receipt and bind a no-write package.""" + readiness_closeout_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_apply_executor_readiness_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_receipt = ( + readiness_closeout_result.get( + "future_database_apply_controlled_dry_run_invocation_readiness_receipt" + ) + or {} + ) + readiness_closeout = ( + readiness_closeout_result.get( + "controlled_dry_run_apply_executor_readiness_closeout" + ) + or {} + ) + readiness_closeout_contract = ( + readiness_closeout_result.get( + "controlled_dry_run_apply_executor_readiness_closeout_contract" + ) + or {} + ) + summary = readiness_closeout_result.get("summary") or {} + safety = readiness_closeout_result.get("safety") or {} + receipt = readiness_closeout.get("dry_run_invocation_readiness_receipt") or {} + readiness_contract = ( + readiness_closeout.get("apply_executor_readiness_contract") or {} + ) + pre_apply_closeout = readiness_closeout.get("pre_apply_replay_closeout") or {} + replay_verifier = readiness_closeout.get("pre_apply_replay_verifier") or {} + final_guard = readiness_closeout.get("final_dry_run_executor_guard") or {} + no_apply_enforcement = ( + readiness_closeout.get("no_apply_enforcement_verification") or {} + ) + rollback_binding = readiness_closeout.get("rollback_binding") or {} + verifier_binding = readiness_closeout.get("post_apply_verifier_binding") or {} + closeout_id = _db_apply_controlled_dry_run_invocation_receipt_closeout_id( + readiness_closeout_result + ) + package_id = f"{closeout_id}-no-write-invocation-package" + invocation_receipt_closeout_fields = [ + "invocation_receipt_closeout_id", + "source_apply_executor_readiness_closeout_id", + "source_dry_run_invocation_readiness_receipt_id", + "source_apply_executor_readiness_contract_id", + "source_pre_apply_replay_closeout_id", + "dry_run_command_shape_hash", + "no_write_invocation_package_id", + "target_migration_file", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "dry_run_executor_invocation_allowed", + "abort_conditions", + ] + invocation_receipt_closeout_acceptance_gates = [ + "apply_executor_readiness_closeout_ready", + "source_chain_ids_match", + "dry_run_invocation_readiness_receipt_ready", + "dry_run_invocation_readiness_receipt_no_execute", + "no_write_invocation_package_bound", + "no_write_invocation_package_blocks_execution", + "apply_executor_readiness_and_replay_carried_forward", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "no_secret_signature_or_database_apply", + ] + no_write_invocation_package_fields = [ + "package_id", + "source_invocation_receipt_closeout_id", + "source_dry_run_invocation_readiness_receipt_id", + "source_apply_executor_readiness_closeout_id", + "source_apply_executor_readiness_contract_id", + "required_receipt_mode", + "required_command_shape_hash", + "package_mode", + "dry_run_executor_invocation_allowed", + "endpoint_execution_allowed", + "sql_execution_allowed", + "database_apply_authorized", + ] + abort_conditions = [ + "abort_if_apply_executor_readiness_closeout_not_ready", + "abort_if_source_chain_ids_do_not_match", + "abort_if_dry_run_invocation_readiness_receipt_missing", + "abort_if_invocation_readiness_receipt_executes", + "abort_if_no_write_invocation_package_missing", + "abort_if_no_write_invocation_package_executes", + "abort_if_endpoint_or_sql_execution_is_allowed", + "abort_if_database_write_or_apply_is_allowed", + "abort_if_target_migration_hash_changes", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_any_secret_signature_or_apply_material_is_present", + ] + no_write_invocation_package = { + "package_id": package_id, + "source_invocation_receipt_closeout_id": closeout_id, + "source_dry_run_invocation_readiness_receipt_id": receipt.get( + "receipt_id" + ), + "source_apply_executor_readiness_closeout_id": readiness_closeout.get( + "apply_executor_readiness_closeout_id" + ), + "source_apply_executor_readiness_contract_id": readiness_contract.get( + "contract_id" + ), + "source_pre_apply_replay_closeout_id": pre_apply_closeout.get( + "pre_apply_replay_closeout_id" + ), + "required_receipt_mode": "dry_run_invocation_readiness_preview_only", + "required_command_shape_hash": receipt.get("required_command_shape_hash"), + "package_status": "no_write_invocation_package_preview_ready", + "package_mode": "no_write_invocation_package_preview_only", + "dry_run_executor_invocation_allowed": False, + "ready_for_no_write_dry_run_invocation_package_now": False, + "ready_for_actual_dry_run_execution_now": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "writes_package_artifact": False, + "reads_secret_in_preview": False, + "no_write_invocation_package_field_count": len( + no_write_invocation_package_fields + ), + "no_write_invocation_package_fields": no_write_invocation_package_fields, + } + apply_executor_readiness_closeout_ready = ( + readiness_closeout_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_APPLY_EXECUTOR_READINESS_CLOSEOUT_READY" + and summary.get( + "controlled_dry_run_apply_executor_readiness_closeout_ready_count" + ) + == 1 + and summary.get( + "controlled_dry_run_apply_executor_readiness_closeout_pass_count" + ) + == summary.get( + "controlled_dry_run_apply_executor_readiness_closeout_check_count" + ) + ) + source_chain_ids_match = ( + bool(readiness_closeout.get("apply_executor_readiness_closeout_id")) + and readiness_closeout.get("apply_executor_readiness_closeout_id") + == future_receipt.get("apply_executor_readiness_closeout_id") + == receipt.get("source_apply_executor_readiness_closeout_id") + == no_write_invocation_package.get( + "source_apply_executor_readiness_closeout_id" + ) + and receipt.get("receipt_id") + == future_receipt.get("dry_run_invocation_readiness_receipt_id") + == no_write_invocation_package.get( + "source_dry_run_invocation_readiness_receipt_id" + ) + and readiness_contract.get("contract_id") + == receipt.get("source_apply_executor_readiness_contract_id") + == no_write_invocation_package.get( + "source_apply_executor_readiness_contract_id" + ) + and pre_apply_closeout.get("pre_apply_replay_closeout_id") + == receipt.get("source_pre_apply_replay_closeout_id") + == no_write_invocation_package.get("source_pre_apply_replay_closeout_id") + ) + dry_run_invocation_readiness_receipt_ready = ( + receipt.get("receipt_status") + == "dry_run_invocation_readiness_receipt_preview_ready" + and receipt.get("receipt_id") + == future_receipt.get("dry_run_invocation_readiness_receipt_id") + and int(receipt.get("dry_run_invocation_readiness_receipt_field_count") or 0) + == 12 + ) + dry_run_invocation_readiness_receipt_no_execute = ( + receipt.get("receipt_mode") == "dry_run_invocation_readiness_preview_only" + and receipt.get("dry_run_executor_invocation_allowed") is False + and receipt.get("ready_for_dry_run_executor_invocation_now") is False + and receipt.get("endpoint_execution_allowed") is False + and receipt.get("sql_execution_allowed") is False + and receipt.get("database_write_allowed") is False + and receipt.get("database_apply_authorized") is False + and receipt.get("executes_database_apply") is False + and receipt.get("executes_endpoint") is False + and receipt.get("executes_sql") is False + and receipt.get("writes_database") is False + ) + no_write_invocation_package_bound = ( + bool(no_write_invocation_package.get("package_id")) + and no_write_invocation_package.get( + "source_invocation_receipt_closeout_id" + ) + == closeout_id + and no_write_invocation_package.get( + "source_dry_run_invocation_readiness_receipt_id" + ) + == receipt.get("receipt_id") + and no_write_invocation_package.get("required_command_shape_hash") + == receipt.get("required_command_shape_hash") + and int( + no_write_invocation_package.get( + "no_write_invocation_package_field_count" + ) + or 0 + ) + == len(no_write_invocation_package_fields) + ) + no_write_invocation_package_blocks_execution = ( + no_write_invocation_package.get("package_mode") + == "no_write_invocation_package_preview_only" + and no_write_invocation_package.get("dry_run_executor_invocation_allowed") + is False + and no_write_invocation_package.get( + "ready_for_no_write_dry_run_invocation_package_now" + ) + is False + and no_write_invocation_package.get( + "ready_for_actual_dry_run_execution_now" + ) + is False + and no_write_invocation_package.get("endpoint_execution_allowed") is False + and no_write_invocation_package.get("sql_execution_allowed") is False + and no_write_invocation_package.get("database_write_allowed") is False + and no_write_invocation_package.get("database_apply_authorized") is False + and no_write_invocation_package.get("executes_database_apply") is False + and no_write_invocation_package.get("executes_endpoint") is False + and no_write_invocation_package.get("executes_sql") is False + and no_write_invocation_package.get("writes_database") is False + ) + apply_executor_readiness_and_replay_carried_forward = ( + readiness_contract.get("readiness_mode") + == "apply_executor_readiness_contract_preview_only" + and readiness_contract.get("dry_run_executor_invocation_allowed") + is False + and readiness_contract.get("database_apply_authorized") is False + and replay_verifier.get("replay_mode") == "pre_apply_replay_preview_only" + and replay_verifier.get("dry_run_executor_invocation_allowed") is False + and replay_verifier.get("database_apply_authorized") is False + and final_guard.get("guard_status") + == "final_dry_run_executor_guard_preview_ready" + and final_guard.get("dry_run_executor_invocation_allowed") is False + and no_apply_enforcement.get("enforcement_status") + == "no_apply_enforcement_preview_ready" + and no_apply_enforcement.get("database_apply_authorized") is False + ) + target_hash_locked = ( + readiness_closeout.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(readiness_closeout.get("expected_sha256")) + and bool(readiness_closeout.get("actual_sha256")) + and readiness_closeout.get("expected_sha256") + == readiness_closeout.get("actual_sha256") + and readiness_closeout.get("hash_matches") is True + and readiness_closeout.get("target_migration_hash_locked") is True + ) + rollback_and_verifier_bound = ( + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False + ) + readiness_closeout_contract_blocks_database_apply = ( + readiness_closeout_contract.get("executes_database_apply") is False + and readiness_closeout_contract.get("executes_endpoint") is False + and readiness_closeout_contract.get("executes_sql") is False + and readiness_closeout_contract.get("database_apply_authorized") is False + and readiness_closeout_contract.get("ready_for_database_apply_now") + is False + and readiness_closeout_contract.get( + "ready_for_dry_run_executor_invocation_now" + ) + is False + and readiness_closeout_contract.get("ready_for_actual_dry_run_execution_now") + is False + and readiness_closeout_contract.get("signs_database_apply_authorization") + is False + and readiness_closeout_contract.get("writes_database") is False + and readiness_closeout_contract.get("executes_in_preview") is False + ) + side_effect_free = ( + summary.get("reads_secret_count", 0) == 0 + and summary.get("executes_script_count", 0) == 0 + and summary.get("executes_migration_count", 0) == 0 + and summary.get("executes_endpoint_count", 0) == 0 + and summary.get("executes_sql_count", 0) == 0 + and summary.get("writes_database_count", 0) == 0 + and summary.get("signs_database_apply_authorization_count", 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("executes_database_apply") is False + and dry_run_invocation_readiness_receipt_no_execute + and no_write_invocation_package_blocks_execution + ) + checks = [ + _controlled_dry_run_invocation_receipt_closeout_check( + "apply_executor_readiness_closeout_ready", + apply_executor_readiness_closeout_ready, + { + "result": readiness_closeout_result.get("result"), + "ready_count": summary.get( + "controlled_dry_run_apply_executor_readiness_closeout_ready_count" + ), + "pass_count": summary.get( + "controlled_dry_run_apply_executor_readiness_closeout_pass_count" + ), + "check_count": summary.get( + "controlled_dry_run_apply_executor_readiness_closeout_check_count" + ), + }, + "wait_for_apply_executor_readiness_closeout_ready", + ), + _controlled_dry_run_invocation_receipt_closeout_check( + "source_chain_ids_match", + source_chain_ids_match, + { + "apply_executor_readiness_closeout_id": readiness_closeout.get( + "apply_executor_readiness_closeout_id" + ), + "dry_run_invocation_readiness_receipt_id": receipt.get( + "receipt_id" + ), + "apply_executor_readiness_contract_id": readiness_contract.get( + "contract_id" + ), + }, + "wait_for_source_chain_ids_match", + ), + _controlled_dry_run_invocation_receipt_closeout_check( + "dry_run_invocation_readiness_receipt_ready", + dry_run_invocation_readiness_receipt_ready, + { + "receipt_id": receipt.get("receipt_id"), + "receipt_status": receipt.get("receipt_status"), + "field_count": receipt.get( + "dry_run_invocation_readiness_receipt_field_count" + ), + }, + "wait_for_dry_run_invocation_readiness_receipt_ready", + ), + _controlled_dry_run_invocation_receipt_closeout_check( + "dry_run_invocation_readiness_receipt_no_execute", + dry_run_invocation_readiness_receipt_no_execute, + { + "receipt_mode": receipt.get("receipt_mode"), + "dry_run_executor_invocation_allowed": receipt.get( + "dry_run_executor_invocation_allowed" + ), + "ready_for_dry_run_executor_invocation_now": receipt.get( + "ready_for_dry_run_executor_invocation_now" + ), + }, + "abort_if_dry_run_invocation_readiness_receipt_executes", + ), + _controlled_dry_run_invocation_receipt_closeout_check( + "no_write_invocation_package_bound", + no_write_invocation_package_bound, + { + "package_id": no_write_invocation_package.get("package_id"), + "source_dry_run_invocation_readiness_receipt_id": ( + no_write_invocation_package.get( + "source_dry_run_invocation_readiness_receipt_id" + ) + ), + "field_count": no_write_invocation_package.get( + "no_write_invocation_package_field_count" + ), + }, + "wait_for_no_write_invocation_package_binding", + ), + _controlled_dry_run_invocation_receipt_closeout_check( + "no_write_invocation_package_blocks_execution", + no_write_invocation_package_blocks_execution, + { + "package_mode": no_write_invocation_package.get("package_mode"), + "dry_run_executor_invocation_allowed": ( + no_write_invocation_package.get( + "dry_run_executor_invocation_allowed" + ) + ), + "ready_for_actual_dry_run_execution_now": ( + no_write_invocation_package.get( + "ready_for_actual_dry_run_execution_now" + ) + ), + }, + "abort_if_no_write_invocation_package_executes", + ), + _controlled_dry_run_invocation_receipt_closeout_check( + "apply_executor_readiness_and_replay_carried_forward", + apply_executor_readiness_and_replay_carried_forward, + { + "readiness_mode": readiness_contract.get("readiness_mode"), + "replay_mode": replay_verifier.get("replay_mode"), + "guard_status": final_guard.get("guard_status"), + }, + "wait_for_apply_executor_readiness_and_replay_carry_forward", + ), + _controlled_dry_run_invocation_receipt_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": readiness_closeout.get("target_file"), + "hash_matches": readiness_closeout.get("hash_matches"), + "expected_sha256_present": bool( + readiness_closeout.get("expected_sha256") + ), + "actual_sha256_present": bool( + readiness_closeout.get("actual_sha256") + ), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_invocation_receipt_closeout_check( + "rollback_and_post_apply_verifier_bound", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "database_apply_authorized": verifier_binding.get( + "database_apply_authorized" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_invocation_receipt_closeout_check( + "apply_executor_readiness_closeout_contract_blocks_database_apply", + readiness_closeout_contract_blocks_database_apply, + { + "permits_future_database_apply_controlled_dry_run_invocation_readiness_receipt": ( + readiness_closeout_contract.get( + "permits_future_database_apply_controlled_dry_run_invocation_readiness_receipt" + ) + ), + "database_apply_authorized": readiness_closeout_contract.get( + "database_apply_authorized" + ), + "writes_database": readiness_closeout_contract.get( + "writes_database" + ), + }, + "abort_if_apply_executor_readiness_closeout_contract_authorizes_database_apply", + ), + _controlled_dry_run_invocation_receipt_closeout_check( + "preview_has_no_side_effects_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + }, + "abort_on_preview_side_effect_execution_or_signing", + ), + _controlled_dry_run_invocation_receipt_closeout_check( + "manual_review_not_required_for_safe_preview", + readiness_closeout_contract.get("manual_review_mode") + == "exception_only" + and safety.get("manual_review_mode") == "exception_only", + { + "contract_manual_review_mode": readiness_closeout_contract.get( + "manual_review_mode" + ), + "safety_manual_review_mode": safety.get("manual_review_mode"), + }, + "keep_manual_review_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_INVOCATION_RECEIPT_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_APPLY_EXECUTOR_READINESS_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_no_write_invocation_package = { + "invocation_receipt_closeout_id": closeout_id, + "no_write_invocation_package_id": package_id, + "source_apply_executor_readiness_closeout_id": readiness_closeout.get( + "apply_executor_readiness_closeout_id" + ), + "source_dry_run_invocation_readiness_receipt_id": receipt.get( + "receipt_id" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_no_write_invocation_package": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_no_write_invocation_package_closeout": ( + closeout_ready + ), + "invocation_receipt_closeout_ready": closeout_ready, + "no_write_invocation_package_bound": closeout_ready, + "dry_run_executor_invocation_allowed": False, + "dry_run_execution_performed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "reads_secret_in_preview": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_invocation_receipt_closeout = { + "invocation_receipt_closeout_id": closeout_id, + "authorization_material_type": ( + "controlled_dry_run_invocation_receipt_closeout" + ), + "source_apply_executor_readiness_closeout_id": readiness_closeout.get( + "apply_executor_readiness_closeout_id" + ), + "source_dry_run_invocation_readiness_receipt_id": receipt.get( + "receipt_id" + ), + "source_apply_executor_readiness_contract_id": readiness_contract.get( + "contract_id" + ), + "source_pre_apply_replay_closeout_id": pre_apply_closeout.get( + "pre_apply_replay_closeout_id" + ), + "dry_run_command_shape_hash": receipt.get("required_command_shape_hash"), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_invocation_receipt_closeout": ( + closeout_ready + ), + "invocation_receipt_closeout_fields": invocation_receipt_closeout_fields, + "invocation_receipt_closeout_field_count": len( + invocation_receipt_closeout_fields + ), + "invocation_receipt_closeout_acceptance_gates": ( + invocation_receipt_closeout_acceptance_gates + ), + "invocation_receipt_closeout_acceptance_gate_count": len( + invocation_receipt_closeout_acceptance_gates + ), + "no_write_invocation_package": no_write_invocation_package, + "no_write_invocation_package_count": 1, + "no_write_invocation_package_field_count": len( + no_write_invocation_package_fields + ), + "dry_run_invocation_readiness_receipt": receipt, + "dry_run_invocation_readiness_receipt_count": 1, + "apply_executor_readiness_closeout": readiness_closeout, + "apply_executor_readiness_closeout_count": 1, + "apply_executor_readiness_contract": readiness_contract, + "apply_executor_readiness_contract_count": 1, + "pre_apply_replay_closeout": pre_apply_closeout, + "pre_apply_replay_closeout_count": 1, + "pre_apply_replay_verifier": replay_verifier, + "pre_apply_replay_verifier_count": 1, + "final_dry_run_executor_guard": final_guard, + "final_dry_run_executor_guard_count": 1, + "no_apply_enforcement_verification": no_apply_enforcement, + "no_apply_enforcement_verification_count": 1, + "target_file": readiness_closeout.get("target_file"), + "expected_sha256": readiness_closeout.get("expected_sha256"), + "actual_sha256": readiness_closeout.get("actual_sha256"), + "hash_matches": readiness_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "invocation_receipt_closeout_only": True, + "no_write_invocation_package_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "dry_run_executor_invocation_allowed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + } + controlled_dry_run_invocation_receipt_closeout_contract = { + "mode": "controlled_dry_run_invocation_receipt_closeout_and_no_write_invocation_package_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-invocation-receipt-closeout" + ), + "source_apply_executor_readiness_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-apply-executor-readiness-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_no_write_invocation_package": ( + closeout_ready + ), + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": False, + "signs_database_apply_authorization": False, + "writes_database": False, + "executes_in_preview": False, + "secret_material_required_in_preview": False, + "manual_review_mode": "exception_only", + } + output_summary = dict(summary) + output_summary.update( + { + "controlled_dry_run_invocation_receipt_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_invocation_receipt_closeout_check_count": len( + checks + ), + "controlled_dry_run_invocation_receipt_closeout_pass_count": ( + passed_count + ), + "controlled_dry_run_invocation_receipt_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_invocation_receipt_closeout_count": 1, + "controlled_dry_run_invocation_receipt_closeout_field_count": len( + invocation_receipt_closeout_fields + ), + "controlled_dry_run_invocation_receipt_closeout_acceptance_gate_count": len( + invocation_receipt_closeout_acceptance_gates + ), + "no_write_invocation_package_count": 1, + "no_write_invocation_package_field_count": len( + no_write_invocation_package_fields + ), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + } + ) + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_INVOCATION_RECEIPT_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(readiness_closeout_result.get("success")), + "generated_at": readiness_closeout_result.get("generated_at"), + "source_policy": readiness_closeout_result.get("policy"), + "stats": readiness_closeout_result.get("stats") or {}, + "summary": output_summary, + "future_database_apply_controlled_dry_run_no_write_invocation_package": ( + future_database_apply_controlled_dry_run_no_write_invocation_package + ), + "controlled_dry_run_invocation_receipt_closeout": ( + controlled_dry_run_invocation_receipt_closeout + ), + "controlled_dry_run_invocation_receipt_closeout_contract": ( + controlled_dry_run_invocation_receipt_closeout_contract + ), + "controlled_dry_run_invocation_receipt_closeout_checks": checks, + "source_controlled_dry_run_apply_executor_readiness_closeout_summary": ( + summary + ), + "source_controlled_dry_run_apply_executor_readiness_closeout_contract": ( + readiness_closeout_contract + ), + "source_controlled_dry_run_apply_executor_readiness_closeout": ( + readiness_closeout + ), + "source_database_apply_controlled_dry_run_invocation_readiness_receipt": ( + future_receipt + ), + "safety": { + "read_only_db_apply_controlled_dry_run_invocation_receipt_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future controlled dry-run no-write invocation package closeout.", + "Keep actual dry-run executor invocation disabled until a dedicated execution-preflight guard is machine-verifiable.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_no_write_invocation_package_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out the no-write invocation package and bind execution preflight.""" + invocation_closeout_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_invocation_receipt_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_package = ( + invocation_closeout_result.get( + "future_database_apply_controlled_dry_run_no_write_invocation_package" + ) + or {} + ) + invocation_closeout = ( + invocation_closeout_result.get( + "controlled_dry_run_invocation_receipt_closeout" + ) + or {} + ) + invocation_closeout_contract = ( + invocation_closeout_result.get( + "controlled_dry_run_invocation_receipt_closeout_contract" + ) + or {} + ) + summary = invocation_closeout_result.get("summary") or {} + safety = invocation_closeout_result.get("safety") or {} + package = invocation_closeout.get("no_write_invocation_package") or {} + receipt = ( + invocation_closeout.get("dry_run_invocation_readiness_receipt") or {} + ) + readiness_closeout = ( + invocation_closeout.get("apply_executor_readiness_closeout") or {} + ) + readiness_contract = ( + invocation_closeout.get("apply_executor_readiness_contract") or {} + ) + pre_apply_closeout = invocation_closeout.get("pre_apply_replay_closeout") or {} + replay_verifier = invocation_closeout.get("pre_apply_replay_verifier") or {} + final_guard = invocation_closeout.get("final_dry_run_executor_guard") or {} + no_apply_enforcement = ( + invocation_closeout.get("no_apply_enforcement_verification") or {} + ) + rollback_binding = invocation_closeout.get("rollback_binding") or {} + verifier_binding = invocation_closeout.get("post_apply_verifier_binding") or {} + closeout_id = ( + _db_apply_controlled_dry_run_no_write_invocation_package_closeout_id( + invocation_closeout_result + ) + ) + guard_id = f"{closeout_id}-execution-preflight-guard" + package_closeout_fields = [ + "no_write_invocation_package_closeout_id", + "source_invocation_receipt_closeout_id", + "source_no_write_invocation_package_id", + "source_dry_run_invocation_readiness_receipt_id", + "source_apply_executor_readiness_closeout_id", + "required_command_shape_hash", + "execution_preflight_guard_id", + "target_migration_file", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "dry_run_executor_invocation_allowed", + "abort_conditions", + ] + package_closeout_acceptance_gates = [ + "invocation_receipt_closeout_ready", + "source_chain_ids_match", + "no_write_invocation_package_ready", + "no_write_invocation_package_no_execute", + "execution_preflight_guard_bound", + "execution_preflight_guard_blocks_execution", + "invocation_receipt_and_apply_readiness_carried_forward", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "no_secret_signature_or_database_apply", + ] + execution_preflight_guard_fields = [ + "guard_id", + "source_no_write_invocation_package_closeout_id", + "source_no_write_invocation_package_id", + "source_invocation_receipt_closeout_id", + "source_dry_run_invocation_readiness_receipt_id", + "required_package_mode", + "required_command_shape_hash", + "guard_mode", + "dry_run_executor_invocation_allowed", + "endpoint_execution_allowed", + "sql_execution_allowed", + "database_apply_authorized", + ] + abort_conditions = [ + "abort_if_invocation_receipt_closeout_not_ready", + "abort_if_source_chain_ids_do_not_match", + "abort_if_no_write_invocation_package_missing", + "abort_if_no_write_invocation_package_executes", + "abort_if_execution_preflight_guard_missing", + "abort_if_execution_preflight_guard_executes", + "abort_if_endpoint_or_sql_execution_is_allowed", + "abort_if_database_write_or_apply_is_allowed", + "abort_if_target_migration_hash_changes", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_any_secret_signature_or_apply_material_is_present", + ] + execution_preflight_guard = { + "guard_id": guard_id, + "source_no_write_invocation_package_closeout_id": closeout_id, + "source_no_write_invocation_package_id": package.get("package_id"), + "source_invocation_receipt_closeout_id": invocation_closeout.get( + "invocation_receipt_closeout_id" + ), + "source_dry_run_invocation_readiness_receipt_id": receipt.get( + "receipt_id" + ), + "source_apply_executor_readiness_closeout_id": readiness_closeout.get( + "apply_executor_readiness_closeout_id" + ), + "required_package_mode": "no_write_invocation_package_preview_only", + "required_command_shape_hash": package.get("required_command_shape_hash"), + "guard_status": "execution_preflight_guard_preview_ready", + "guard_mode": "execution_preflight_guard_preview_only", + "dry_run_executor_invocation_allowed": False, + "ready_for_execution_preflight_guard_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "writes_guard_artifact": False, + "reads_secret_in_preview": False, + "execution_preflight_guard_field_count": len( + execution_preflight_guard_fields + ), + "execution_preflight_guard_fields": execution_preflight_guard_fields, + } + invocation_receipt_closeout_ready = ( + invocation_closeout_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_INVOCATION_RECEIPT_CLOSEOUT_READY" + and summary.get( + "controlled_dry_run_invocation_receipt_closeout_ready_count" + ) + == 1 + and summary.get( + "controlled_dry_run_invocation_receipt_closeout_pass_count" + ) + == summary.get( + "controlled_dry_run_invocation_receipt_closeout_check_count" + ) + ) + source_chain_ids_match = ( + bool(invocation_closeout.get("invocation_receipt_closeout_id")) + and invocation_closeout.get("invocation_receipt_closeout_id") + == future_package.get("invocation_receipt_closeout_id") + == package.get("source_invocation_receipt_closeout_id") + == execution_preflight_guard.get("source_invocation_receipt_closeout_id") + and package.get("package_id") + == future_package.get("no_write_invocation_package_id") + == execution_preflight_guard.get("source_no_write_invocation_package_id") + and receipt.get("receipt_id") + == future_package.get("source_dry_run_invocation_readiness_receipt_id") + == package.get("source_dry_run_invocation_readiness_receipt_id") + == execution_preflight_guard.get( + "source_dry_run_invocation_readiness_receipt_id" + ) + and readiness_closeout.get("apply_executor_readiness_closeout_id") + == future_package.get("source_apply_executor_readiness_closeout_id") + == package.get("source_apply_executor_readiness_closeout_id") + == execution_preflight_guard.get( + "source_apply_executor_readiness_closeout_id" + ) + ) + no_write_invocation_package_ready = ( + package.get("package_status") == "no_write_invocation_package_preview_ready" + and package.get("package_id") + == future_package.get("no_write_invocation_package_id") + and int(package.get("no_write_invocation_package_field_count") or 0) + == 12 + ) + no_write_invocation_package_no_execute = ( + package.get("package_mode") == "no_write_invocation_package_preview_only" + and package.get("dry_run_executor_invocation_allowed") is False + and package.get("ready_for_no_write_dry_run_invocation_package_now") + is False + and package.get("ready_for_actual_dry_run_execution_now") is False + and package.get("endpoint_execution_allowed") is False + and package.get("sql_execution_allowed") is False + and package.get("database_write_allowed") is False + and package.get("database_apply_authorized") is False + and package.get("executes_database_apply") is False + and package.get("executes_endpoint") is False + and package.get("executes_sql") is False + and package.get("writes_database") is False + ) + execution_preflight_guard_bound = ( + bool(execution_preflight_guard.get("guard_id")) + and execution_preflight_guard.get( + "source_no_write_invocation_package_closeout_id" + ) + == closeout_id + and execution_preflight_guard.get("source_no_write_invocation_package_id") + == package.get("package_id") + and execution_preflight_guard.get("required_command_shape_hash") + == package.get("required_command_shape_hash") + and int( + execution_preflight_guard.get( + "execution_preflight_guard_field_count" + ) + or 0 + ) + == len(execution_preflight_guard_fields) + ) + execution_preflight_guard_blocks_execution = ( + execution_preflight_guard.get("guard_mode") + == "execution_preflight_guard_preview_only" + and execution_preflight_guard.get("dry_run_executor_invocation_allowed") + is False + and execution_preflight_guard.get("ready_for_execution_preflight_guard_now") + is False + and execution_preflight_guard.get( + "ready_for_dry_run_executor_invocation_now" + ) + is False + and execution_preflight_guard.get("ready_for_actual_dry_run_execution_now") + is False + and execution_preflight_guard.get("endpoint_execution_allowed") is False + and execution_preflight_guard.get("sql_execution_allowed") is False + and execution_preflight_guard.get("database_write_allowed") is False + and execution_preflight_guard.get("database_apply_authorized") is False + and execution_preflight_guard.get("executes_database_apply") is False + and execution_preflight_guard.get("executes_endpoint") is False + and execution_preflight_guard.get("executes_sql") is False + and execution_preflight_guard.get("writes_database") is False + ) + invocation_receipt_and_apply_readiness_carried_forward = ( + receipt.get("receipt_mode") == "dry_run_invocation_readiness_preview_only" + and receipt.get("dry_run_executor_invocation_allowed") is False + and readiness_contract.get("readiness_mode") + == "apply_executor_readiness_contract_preview_only" + and readiness_contract.get("dry_run_executor_invocation_allowed") + is False + and replay_verifier.get("replay_mode") == "pre_apply_replay_preview_only" + and replay_verifier.get("database_apply_authorized") is False + and final_guard.get("guard_status") + == "final_dry_run_executor_guard_preview_ready" + and final_guard.get("dry_run_executor_invocation_allowed") is False + and no_apply_enforcement.get("enforcement_status") + == "no_apply_enforcement_preview_ready" + and no_apply_enforcement.get("database_apply_authorized") is False + ) + target_hash_locked = ( + invocation_closeout.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(invocation_closeout.get("expected_sha256")) + and bool(invocation_closeout.get("actual_sha256")) + and invocation_closeout.get("expected_sha256") + == invocation_closeout.get("actual_sha256") + and invocation_closeout.get("hash_matches") is True + and invocation_closeout.get("target_migration_hash_locked") is True + ) + rollback_and_verifier_bound = ( + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False + ) + invocation_receipt_closeout_contract_blocks_database_apply = ( + invocation_closeout_contract.get("executes_database_apply") is False + and invocation_closeout_contract.get("executes_endpoint") is False + and invocation_closeout_contract.get("executes_sql") is False + and invocation_closeout_contract.get("database_apply_authorized") is False + and invocation_closeout_contract.get("ready_for_database_apply_now") + is False + and invocation_closeout_contract.get( + "ready_for_dry_run_executor_invocation_now" + ) + is False + and invocation_closeout_contract.get( + "ready_for_actual_dry_run_execution_now" + ) + is False + and invocation_closeout_contract.get("signs_database_apply_authorization") + is False + and invocation_closeout_contract.get("writes_database") is False + and invocation_closeout_contract.get("executes_in_preview") is False + ) + side_effect_free = ( + summary.get("reads_secret_count", 0) == 0 + and summary.get("executes_script_count", 0) == 0 + and summary.get("executes_migration_count", 0) == 0 + and summary.get("executes_endpoint_count", 0) == 0 + and summary.get("executes_sql_count", 0) == 0 + and summary.get("writes_database_count", 0) == 0 + and summary.get("signs_database_apply_authorization_count", 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("executes_database_apply") is False + and no_write_invocation_package_no_execute + and execution_preflight_guard_blocks_execution + ) + checks = [ + _controlled_dry_run_no_write_invocation_package_closeout_check( + "invocation_receipt_closeout_ready", + invocation_receipt_closeout_ready, + { + "result": invocation_closeout_result.get("result"), + "ready_count": summary.get( + "controlled_dry_run_invocation_receipt_closeout_ready_count" + ), + "pass_count": summary.get( + "controlled_dry_run_invocation_receipt_closeout_pass_count" + ), + "check_count": summary.get( + "controlled_dry_run_invocation_receipt_closeout_check_count" + ), + }, + "wait_for_invocation_receipt_closeout_ready", + ), + _controlled_dry_run_no_write_invocation_package_closeout_check( + "source_chain_ids_match", + source_chain_ids_match, + { + "invocation_receipt_closeout_id": invocation_closeout.get( + "invocation_receipt_closeout_id" + ), + "no_write_invocation_package_id": package.get("package_id"), + "dry_run_invocation_readiness_receipt_id": receipt.get( + "receipt_id" + ), + }, + "wait_for_source_chain_ids_match", + ), + _controlled_dry_run_no_write_invocation_package_closeout_check( + "no_write_invocation_package_ready", + no_write_invocation_package_ready, + { + "package_id": package.get("package_id"), + "package_status": package.get("package_status"), + "field_count": package.get("no_write_invocation_package_field_count"), + }, + "wait_for_no_write_invocation_package_ready", + ), + _controlled_dry_run_no_write_invocation_package_closeout_check( + "no_write_invocation_package_no_execute", + no_write_invocation_package_no_execute, + { + "package_mode": package.get("package_mode"), + "dry_run_executor_invocation_allowed": package.get( + "dry_run_executor_invocation_allowed" + ), + "ready_for_actual_dry_run_execution_now": package.get( + "ready_for_actual_dry_run_execution_now" + ), + }, + "abort_if_no_write_invocation_package_executes", + ), + _controlled_dry_run_no_write_invocation_package_closeout_check( + "execution_preflight_guard_bound", + execution_preflight_guard_bound, + { + "guard_id": execution_preflight_guard.get("guard_id"), + "source_no_write_invocation_package_id": ( + execution_preflight_guard.get( + "source_no_write_invocation_package_id" + ) + ), + "field_count": execution_preflight_guard.get( + "execution_preflight_guard_field_count" + ), + }, + "wait_for_execution_preflight_guard_binding", + ), + _controlled_dry_run_no_write_invocation_package_closeout_check( + "execution_preflight_guard_blocks_execution", + execution_preflight_guard_blocks_execution, + { + "guard_mode": execution_preflight_guard.get("guard_mode"), + "dry_run_executor_invocation_allowed": ( + execution_preflight_guard.get( + "dry_run_executor_invocation_allowed" + ) + ), + "ready_for_actual_dry_run_execution_now": ( + execution_preflight_guard.get( + "ready_for_actual_dry_run_execution_now" + ) + ), + }, + "abort_if_execution_preflight_guard_executes", + ), + _controlled_dry_run_no_write_invocation_package_closeout_check( + "invocation_receipt_and_apply_readiness_carried_forward", + invocation_receipt_and_apply_readiness_carried_forward, + { + "receipt_mode": receipt.get("receipt_mode"), + "readiness_mode": readiness_contract.get("readiness_mode"), + "guard_status": final_guard.get("guard_status"), + }, + "wait_for_invocation_receipt_and_apply_readiness_carry_forward", + ), + _controlled_dry_run_no_write_invocation_package_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": invocation_closeout.get("target_file"), + "hash_matches": invocation_closeout.get("hash_matches"), + "expected_sha256_present": bool( + invocation_closeout.get("expected_sha256") + ), + "actual_sha256_present": bool( + invocation_closeout.get("actual_sha256") + ), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_no_write_invocation_package_closeout_check( + "rollback_and_post_apply_verifier_bound", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "database_apply_authorized": verifier_binding.get( + "database_apply_authorized" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_no_write_invocation_package_closeout_check( + "invocation_receipt_closeout_contract_blocks_database_apply", + invocation_receipt_closeout_contract_blocks_database_apply, + { + "permits_future_database_apply_controlled_dry_run_no_write_invocation_package": ( + invocation_closeout_contract.get( + "permits_future_database_apply_controlled_dry_run_no_write_invocation_package" + ) + ), + "database_apply_authorized": invocation_closeout_contract.get( + "database_apply_authorized" + ), + "writes_database": invocation_closeout_contract.get( + "writes_database" + ), + }, + "abort_if_invocation_receipt_closeout_contract_authorizes_database_apply", + ), + _controlled_dry_run_no_write_invocation_package_closeout_check( + "preview_has_no_side_effects_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + }, + "abort_on_preview_side_effect_execution_or_signing", + ), + _controlled_dry_run_no_write_invocation_package_closeout_check( + "manual_review_not_required_for_safe_preview", + invocation_closeout_contract.get("manual_review_mode") + == "exception_only" + and safety.get("manual_review_mode") == "exception_only", + { + "contract_manual_review_mode": invocation_closeout_contract.get( + "manual_review_mode" + ), + "safety_manual_review_mode": safety.get("manual_review_mode"), + }, + "keep_manual_review_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_NO_WRITE_INVOCATION_PACKAGE_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_INVOCATION_RECEIPT_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_execution_preflight_guard = { + "no_write_invocation_package_closeout_id": closeout_id, + "execution_preflight_guard_id": guard_id, + "source_invocation_receipt_closeout_id": invocation_closeout.get( + "invocation_receipt_closeout_id" + ), + "source_no_write_invocation_package_id": package.get("package_id"), + "source_dry_run_invocation_readiness_receipt_id": receipt.get( + "receipt_id" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_execution_preflight_guard": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_execution_preflight_guard_closeout": ( + closeout_ready + ), + "no_write_invocation_package_closeout_ready": closeout_ready, + "execution_preflight_guard_bound": closeout_ready, + "dry_run_executor_invocation_allowed": False, + "dry_run_execution_performed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "reads_secret_in_preview": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_no_write_invocation_package_closeout = { + "no_write_invocation_package_closeout_id": closeout_id, + "authorization_material_type": ( + "controlled_dry_run_no_write_invocation_package_closeout" + ), + "source_invocation_receipt_closeout_id": invocation_closeout.get( + "invocation_receipt_closeout_id" + ), + "source_no_write_invocation_package_id": package.get("package_id"), + "source_dry_run_invocation_readiness_receipt_id": receipt.get( + "receipt_id" + ), + "source_apply_executor_readiness_closeout_id": readiness_closeout.get( + "apply_executor_readiness_closeout_id" + ), + "required_command_shape_hash": package.get("required_command_shape_hash"), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_no_write_invocation_package_closeout": ( + closeout_ready + ), + "no_write_invocation_package_closeout_fields": package_closeout_fields, + "no_write_invocation_package_closeout_field_count": len( + package_closeout_fields + ), + "no_write_invocation_package_closeout_acceptance_gates": ( + package_closeout_acceptance_gates + ), + "no_write_invocation_package_closeout_acceptance_gate_count": len( + package_closeout_acceptance_gates + ), + "execution_preflight_guard": execution_preflight_guard, + "execution_preflight_guard_count": 1, + "execution_preflight_guard_field_count": len( + execution_preflight_guard_fields + ), + "no_write_invocation_package": package, + "no_write_invocation_package_count": 1, + "invocation_receipt_closeout": invocation_closeout, + "invocation_receipt_closeout_count": 1, + "dry_run_invocation_readiness_receipt": receipt, + "dry_run_invocation_readiness_receipt_count": 1, + "apply_executor_readiness_closeout": readiness_closeout, + "apply_executor_readiness_closeout_count": 1, + "apply_executor_readiness_contract": readiness_contract, + "apply_executor_readiness_contract_count": 1, + "pre_apply_replay_closeout": pre_apply_closeout, + "pre_apply_replay_closeout_count": 1, + "pre_apply_replay_verifier": replay_verifier, + "pre_apply_replay_verifier_count": 1, + "final_dry_run_executor_guard": final_guard, + "final_dry_run_executor_guard_count": 1, + "no_apply_enforcement_verification": no_apply_enforcement, + "no_apply_enforcement_verification_count": 1, + "target_file": invocation_closeout.get("target_file"), + "expected_sha256": invocation_closeout.get("expected_sha256"), + "actual_sha256": invocation_closeout.get("actual_sha256"), + "hash_matches": invocation_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "no_write_invocation_package_closeout_only": True, + "execution_preflight_guard_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "dry_run_executor_invocation_allowed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + } + controlled_dry_run_no_write_invocation_package_closeout_contract = { + "mode": "controlled_dry_run_no_write_invocation_package_closeout_and_execution_preflight_guard_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-no-write-invocation-package-closeout" + ), + "source_invocation_receipt_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-invocation-receipt-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_execution_preflight_guard": ( + closeout_ready + ), + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": False, + "signs_database_apply_authorization": False, + "writes_database": False, + "executes_in_preview": False, + "secret_material_required_in_preview": False, + "manual_review_mode": "exception_only", + } + output_summary = dict(summary) + output_summary.update( + { + "controlled_dry_run_no_write_invocation_package_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_no_write_invocation_package_closeout_check_count": len( + checks + ), + "controlled_dry_run_no_write_invocation_package_closeout_pass_count": ( + passed_count + ), + "controlled_dry_run_no_write_invocation_package_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_no_write_invocation_package_closeout_count": 1, + "controlled_dry_run_no_write_invocation_package_closeout_field_count": len( + package_closeout_fields + ), + "controlled_dry_run_no_write_invocation_package_closeout_acceptance_gate_count": len( + package_closeout_acceptance_gates + ), + "execution_preflight_guard_count": 1, + "execution_preflight_guard_field_count": len( + execution_preflight_guard_fields + ), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + } + ) + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_NO_WRITE_INVOCATION_PACKAGE_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(invocation_closeout_result.get("success")), + "generated_at": invocation_closeout_result.get("generated_at"), + "source_policy": invocation_closeout_result.get("policy"), + "stats": invocation_closeout_result.get("stats") or {}, + "summary": output_summary, + "future_database_apply_controlled_dry_run_execution_preflight_guard": ( + future_database_apply_controlled_dry_run_execution_preflight_guard + ), + "controlled_dry_run_no_write_invocation_package_closeout": ( + controlled_dry_run_no_write_invocation_package_closeout + ), + "controlled_dry_run_no_write_invocation_package_closeout_contract": ( + controlled_dry_run_no_write_invocation_package_closeout_contract + ), + "controlled_dry_run_no_write_invocation_package_closeout_checks": checks, + "source_controlled_dry_run_invocation_receipt_closeout_summary": summary, + "source_controlled_dry_run_invocation_receipt_closeout_contract": ( + invocation_closeout_contract + ), + "source_controlled_dry_run_invocation_receipt_closeout": ( + invocation_closeout + ), + "source_database_apply_controlled_dry_run_no_write_invocation_package": ( + future_package + ), + "safety": { + "read_only_db_apply_controlled_dry_run_no_write_invocation_package_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future controlled dry-run execution preflight guard closeout.", + "Keep actual dry-run executor invocation disabled until the execution preflight guard is separately closed out.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_execution_preflight_guard_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out the execution preflight guard and bind runner invocation boundary.""" + package_closeout_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_no_write_invocation_package_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_guard = ( + package_closeout_result.get( + "future_database_apply_controlled_dry_run_execution_preflight_guard" + ) + or {} + ) + package_closeout = ( + package_closeout_result.get( + "controlled_dry_run_no_write_invocation_package_closeout" + ) + or {} + ) + package_closeout_contract = ( + package_closeout_result.get( + "controlled_dry_run_no_write_invocation_package_closeout_contract" + ) + or {} + ) + summary = package_closeout_result.get("summary") or {} + safety = package_closeout_result.get("safety") or {} + execution_preflight_guard = package_closeout.get("execution_preflight_guard") or {} + package = package_closeout.get("no_write_invocation_package") or {} + invocation_closeout = package_closeout.get("invocation_receipt_closeout") or {} + receipt = ( + package_closeout.get("dry_run_invocation_readiness_receipt") or {} + ) + readiness_closeout = ( + package_closeout.get("apply_executor_readiness_closeout") or {} + ) + readiness_contract = ( + package_closeout.get("apply_executor_readiness_contract") or {} + ) + pre_apply_closeout = package_closeout.get("pre_apply_replay_closeout") or {} + replay_verifier = package_closeout.get("pre_apply_replay_verifier") or {} + final_guard = package_closeout.get("final_dry_run_executor_guard") or {} + no_apply_enforcement = ( + package_closeout.get("no_apply_enforcement_verification") or {} + ) + rollback_binding = package_closeout.get("rollback_binding") or {} + verifier_binding = package_closeout.get("post_apply_verifier_binding") or {} + closeout_id = _db_apply_controlled_dry_run_execution_preflight_guard_closeout_id( + package_closeout_result + ) + boundary_id = f"{closeout_id}-runner-invocation-boundary" + guard_closeout_fields = [ + "execution_preflight_guard_closeout_id", + "source_no_write_invocation_package_closeout_id", + "source_execution_preflight_guard_id", + "source_no_write_invocation_package_id", + "source_invocation_receipt_closeout_id", + "required_command_shape_hash", + "runner_invocation_boundary_id", + "target_migration_file", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "dry_run_executor_invocation_allowed", + "abort_conditions", + ] + guard_closeout_acceptance_gates = [ + "no_write_invocation_package_closeout_ready", + "source_chain_ids_match", + "execution_preflight_guard_ready", + "execution_preflight_guard_no_execute", + "runner_invocation_boundary_bound", + "runner_invocation_boundary_blocks_execution", + "no_write_package_and_invocation_receipt_carried_forward", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "no_secret_signature_or_database_apply", + ] + runner_invocation_boundary_fields = [ + "boundary_id", + "source_execution_preflight_guard_closeout_id", + "source_execution_preflight_guard_id", + "source_no_write_invocation_package_closeout_id", + "source_no_write_invocation_package_id", + "required_guard_mode", + "required_command_shape_hash", + "boundary_mode", + "dry_run_executor_invocation_allowed", + "endpoint_execution_allowed", + "sql_execution_allowed", + "database_apply_authorized", + ] + abort_conditions = [ + "abort_if_no_write_invocation_package_closeout_not_ready", + "abort_if_source_chain_ids_do_not_match", + "abort_if_execution_preflight_guard_missing", + "abort_if_execution_preflight_guard_executes", + "abort_if_runner_invocation_boundary_missing", + "abort_if_runner_invocation_boundary_allows_invocation", + "abort_if_endpoint_or_sql_execution_is_allowed", + "abort_if_database_write_or_apply_is_allowed", + "abort_if_target_migration_hash_changes", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_any_secret_signature_or_apply_material_is_present", + ] + runner_invocation_boundary = { + "boundary_id": boundary_id, + "source_execution_preflight_guard_closeout_id": closeout_id, + "source_execution_preflight_guard_id": execution_preflight_guard.get( + "guard_id" + ), + "source_no_write_invocation_package_closeout_id": package_closeout.get( + "no_write_invocation_package_closeout_id" + ), + "source_no_write_invocation_package_id": package.get("package_id"), + "source_invocation_receipt_closeout_id": invocation_closeout.get( + "invocation_receipt_closeout_id" + ), + "required_guard_mode": "execution_preflight_guard_preview_only", + "required_command_shape_hash": execution_preflight_guard.get( + "required_command_shape_hash" + ), + "boundary_status": "runner_invocation_boundary_preview_ready", + "boundary_mode": "runner_invocation_boundary_preview_only", + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "ready_for_runner_invocation_boundary_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "reads_secret_in_preview": False, + "runner_invocation_boundary_field_count": len( + runner_invocation_boundary_fields + ), + "runner_invocation_boundary_fields": runner_invocation_boundary_fields, + } + package_closeout_ready = ( + package_closeout_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_NO_WRITE_INVOCATION_PACKAGE_CLOSEOUT_READY" + and summary.get( + "controlled_dry_run_no_write_invocation_package_closeout_ready_count" + ) + == 1 + and summary.get( + "controlled_dry_run_no_write_invocation_package_closeout_pass_count" + ) + == summary.get( + "controlled_dry_run_no_write_invocation_package_closeout_check_count" + ) + ) + source_chain_ids_match = ( + bool(package_closeout.get("no_write_invocation_package_closeout_id")) + and package_closeout.get("no_write_invocation_package_closeout_id") + == future_guard.get("no_write_invocation_package_closeout_id") + == execution_preflight_guard.get( + "source_no_write_invocation_package_closeout_id" + ) + == runner_invocation_boundary.get( + "source_no_write_invocation_package_closeout_id" + ) + and execution_preflight_guard.get("guard_id") + == future_guard.get("execution_preflight_guard_id") + == runner_invocation_boundary.get( + "source_execution_preflight_guard_id" + ) + and package.get("package_id") + == future_guard.get("source_no_write_invocation_package_id") + == execution_preflight_guard.get( + "source_no_write_invocation_package_id" + ) + == runner_invocation_boundary.get( + "source_no_write_invocation_package_id" + ) + and invocation_closeout.get("invocation_receipt_closeout_id") + == future_guard.get("source_invocation_receipt_closeout_id") + == execution_preflight_guard.get("source_invocation_receipt_closeout_id") + == runner_invocation_boundary.get("source_invocation_receipt_closeout_id") + ) + execution_preflight_guard_ready = ( + execution_preflight_guard.get("guard_status") + == "execution_preflight_guard_preview_ready" + and execution_preflight_guard.get("guard_id") + == future_guard.get("execution_preflight_guard_id") + and int( + execution_preflight_guard.get("execution_preflight_guard_field_count") + or 0 + ) + == 12 + ) + execution_preflight_guard_no_execute = ( + execution_preflight_guard.get("guard_mode") + == "execution_preflight_guard_preview_only" + and execution_preflight_guard.get("dry_run_executor_invocation_allowed") + is False + and execution_preflight_guard.get("ready_for_execution_preflight_guard_now") + is False + and execution_preflight_guard.get( + "ready_for_dry_run_executor_invocation_now" + ) + is False + and execution_preflight_guard.get("ready_for_actual_dry_run_execution_now") + is False + and execution_preflight_guard.get("endpoint_execution_allowed") is False + and execution_preflight_guard.get("sql_execution_allowed") is False + and execution_preflight_guard.get("database_write_allowed") is False + and execution_preflight_guard.get("database_apply_authorized") is False + and execution_preflight_guard.get("executes_database_apply") is False + and execution_preflight_guard.get("executes_endpoint") is False + and execution_preflight_guard.get("executes_sql") is False + and execution_preflight_guard.get("writes_database") is False + ) + runner_invocation_boundary_bound = ( + bool(runner_invocation_boundary.get("boundary_id")) + and runner_invocation_boundary.get( + "source_execution_preflight_guard_closeout_id" + ) + == closeout_id + and runner_invocation_boundary.get("source_execution_preflight_guard_id") + == execution_preflight_guard.get("guard_id") + and runner_invocation_boundary.get("required_command_shape_hash") + == execution_preflight_guard.get("required_command_shape_hash") + and int( + runner_invocation_boundary.get( + "runner_invocation_boundary_field_count" + ) + or 0 + ) + == len(runner_invocation_boundary_fields) + ) + runner_invocation_boundary_blocks_execution = ( + runner_invocation_boundary.get("boundary_mode") + == "runner_invocation_boundary_preview_only" + and runner_invocation_boundary.get("dry_run_executor_invocation_allowed") + is False + and runner_invocation_boundary.get("runner_invocation_allowed") is False + and runner_invocation_boundary.get( + "ready_for_runner_invocation_boundary_now" + ) + is False + and runner_invocation_boundary.get( + "ready_for_dry_run_executor_invocation_now" + ) + is False + and runner_invocation_boundary.get("ready_for_actual_dry_run_execution_now") + is False + and runner_invocation_boundary.get("endpoint_execution_allowed") is False + and runner_invocation_boundary.get("sql_execution_allowed") is False + and runner_invocation_boundary.get("database_write_allowed") is False + and runner_invocation_boundary.get("database_apply_authorized") is False + and runner_invocation_boundary.get("executes_database_apply") is False + and runner_invocation_boundary.get("executes_endpoint") is False + and runner_invocation_boundary.get("executes_sql") is False + and runner_invocation_boundary.get("writes_database") is False + and runner_invocation_boundary.get("captures_stdout") is False + and runner_invocation_boundary.get("captures_stderr") is False + ) + no_write_package_and_invocation_receipt_carried_forward = ( + package.get("package_mode") == "no_write_invocation_package_preview_only" + and package.get("dry_run_executor_invocation_allowed") is False + and invocation_closeout.get("invocation_receipt_closeout_only") is True + and invocation_closeout.get("database_apply_authorized") is False + and receipt.get("receipt_mode") == "dry_run_invocation_readiness_preview_only" + and receipt.get("dry_run_executor_invocation_allowed") is False + and readiness_contract.get("readiness_mode") + == "apply_executor_readiness_contract_preview_only" + and readiness_contract.get("dry_run_executor_invocation_allowed") + is False + and replay_verifier.get("replay_mode") == "pre_apply_replay_preview_only" + and replay_verifier.get("database_apply_authorized") is False + and final_guard.get("guard_status") + == "final_dry_run_executor_guard_preview_ready" + and final_guard.get("dry_run_executor_invocation_allowed") is False + and no_apply_enforcement.get("enforcement_status") + == "no_apply_enforcement_preview_ready" + and no_apply_enforcement.get("database_apply_authorized") is False + ) + target_hash_locked = ( + package_closeout.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(package_closeout.get("expected_sha256")) + and bool(package_closeout.get("actual_sha256")) + and package_closeout.get("expected_sha256") + == package_closeout.get("actual_sha256") + and package_closeout.get("hash_matches") is True + and package_closeout.get("target_migration_hash_locked") is True + ) + rollback_and_verifier_bound = ( + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False + ) + package_closeout_contract_blocks_database_apply = ( + package_closeout_contract.get("executes_database_apply") is False + and package_closeout_contract.get("executes_endpoint") is False + and package_closeout_contract.get("executes_sql") is False + and package_closeout_contract.get("database_apply_authorized") is False + and package_closeout_contract.get("ready_for_database_apply_now") + is False + and package_closeout_contract.get( + "ready_for_dry_run_executor_invocation_now" + ) + is False + and package_closeout_contract.get("ready_for_actual_dry_run_execution_now") + is False + and package_closeout_contract.get("signs_database_apply_authorization") + is False + and package_closeout_contract.get("writes_database") is False + and package_closeout_contract.get("executes_in_preview") is False + ) + side_effect_free = ( + summary.get("reads_secret_count", 0) == 0 + and summary.get("executes_script_count", 0) == 0 + and summary.get("executes_migration_count", 0) == 0 + and summary.get("executes_endpoint_count", 0) == 0 + and summary.get("executes_sql_count", 0) == 0 + and summary.get("writes_database_count", 0) == 0 + and summary.get("signs_database_apply_authorization_count", 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("executes_database_apply") is False + and execution_preflight_guard_no_execute + and runner_invocation_boundary_blocks_execution + ) + checks = [ + _controlled_dry_run_execution_preflight_guard_closeout_check( + "no_write_invocation_package_closeout_ready", + package_closeout_ready, + { + "result": package_closeout_result.get("result"), + "ready_count": summary.get( + "controlled_dry_run_no_write_invocation_package_closeout_ready_count" + ), + "pass_count": summary.get( + "controlled_dry_run_no_write_invocation_package_closeout_pass_count" + ), + "check_count": summary.get( + "controlled_dry_run_no_write_invocation_package_closeout_check_count" + ), + }, + "wait_for_no_write_invocation_package_closeout_ready", + ), + _controlled_dry_run_execution_preflight_guard_closeout_check( + "source_chain_ids_match", + source_chain_ids_match, + { + "no_write_invocation_package_closeout_id": package_closeout.get( + "no_write_invocation_package_closeout_id" + ), + "execution_preflight_guard_id": execution_preflight_guard.get( + "guard_id" + ), + "no_write_invocation_package_id": package.get("package_id"), + }, + "wait_for_source_chain_ids_match", + ), + _controlled_dry_run_execution_preflight_guard_closeout_check( + "execution_preflight_guard_ready", + execution_preflight_guard_ready, + { + "guard_id": execution_preflight_guard.get("guard_id"), + "guard_status": execution_preflight_guard.get("guard_status"), + "field_count": execution_preflight_guard.get( + "execution_preflight_guard_field_count" + ), + }, + "wait_for_execution_preflight_guard_ready", + ), + _controlled_dry_run_execution_preflight_guard_closeout_check( + "execution_preflight_guard_no_execute", + execution_preflight_guard_no_execute, + { + "guard_mode": execution_preflight_guard.get("guard_mode"), + "dry_run_executor_invocation_allowed": ( + execution_preflight_guard.get( + "dry_run_executor_invocation_allowed" + ) + ), + "ready_for_actual_dry_run_execution_now": ( + execution_preflight_guard.get( + "ready_for_actual_dry_run_execution_now" + ) + ), + }, + "abort_if_execution_preflight_guard_executes", + ), + _controlled_dry_run_execution_preflight_guard_closeout_check( + "runner_invocation_boundary_bound", + runner_invocation_boundary_bound, + { + "boundary_id": runner_invocation_boundary.get("boundary_id"), + "source_execution_preflight_guard_id": ( + runner_invocation_boundary.get( + "source_execution_preflight_guard_id" + ) + ), + "field_count": runner_invocation_boundary.get( + "runner_invocation_boundary_field_count" + ), + }, + "wait_for_runner_invocation_boundary_binding", + ), + _controlled_dry_run_execution_preflight_guard_closeout_check( + "runner_invocation_boundary_blocks_execution", + runner_invocation_boundary_blocks_execution, + { + "boundary_mode": runner_invocation_boundary.get("boundary_mode"), + "dry_run_executor_invocation_allowed": ( + runner_invocation_boundary.get( + "dry_run_executor_invocation_allowed" + ) + ), + "runner_invocation_allowed": runner_invocation_boundary.get( + "runner_invocation_allowed" + ), + }, + "abort_if_runner_invocation_boundary_allows_invocation", + ), + _controlled_dry_run_execution_preflight_guard_closeout_check( + "no_write_package_and_invocation_receipt_carried_forward", + no_write_package_and_invocation_receipt_carried_forward, + { + "package_mode": package.get("package_mode"), + "receipt_mode": receipt.get("receipt_mode"), + "readiness_mode": readiness_contract.get("readiness_mode"), + }, + "wait_for_no_write_package_and_invocation_receipt_carry_forward", + ), + _controlled_dry_run_execution_preflight_guard_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": package_closeout.get("target_file"), + "hash_matches": package_closeout.get("hash_matches"), + "expected_sha256_present": bool( + package_closeout.get("expected_sha256") + ), + "actual_sha256_present": bool( + package_closeout.get("actual_sha256") + ), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_execution_preflight_guard_closeout_check( + "rollback_and_post_apply_verifier_bound", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "database_apply_authorized": verifier_binding.get( + "database_apply_authorized" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_execution_preflight_guard_closeout_check( + "no_write_invocation_package_closeout_contract_blocks_database_apply", + package_closeout_contract_blocks_database_apply, + { + "permits_future_database_apply_controlled_dry_run_execution_preflight_guard": ( + package_closeout_contract.get( + "permits_future_database_apply_controlled_dry_run_execution_preflight_guard" + ) + ), + "database_apply_authorized": package_closeout_contract.get( + "database_apply_authorized" + ), + "writes_database": package_closeout_contract.get( + "writes_database" + ), + }, + "abort_if_no_write_invocation_package_closeout_contract_authorizes_database_apply", + ), + _controlled_dry_run_execution_preflight_guard_closeout_check( + "preview_has_no_side_effects_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + }, + "abort_on_preview_side_effect_execution_or_signing", + ), + _controlled_dry_run_execution_preflight_guard_closeout_check( + "manual_review_not_required_for_safe_preview", + package_closeout_contract.get("manual_review_mode") + == "exception_only" + and safety.get("manual_review_mode") == "exception_only", + { + "contract_manual_review_mode": package_closeout_contract.get( + "manual_review_mode" + ), + "safety_manual_review_mode": safety.get("manual_review_mode"), + }, + "keep_manual_review_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_EXECUTION_PREFLIGHT_GUARD_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_NO_WRITE_INVOCATION_PACKAGE_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_runner_invocation_boundary = { + "execution_preflight_guard_closeout_id": closeout_id, + "runner_invocation_boundary_id": boundary_id, + "source_no_write_invocation_package_closeout_id": package_closeout.get( + "no_write_invocation_package_closeout_id" + ), + "source_execution_preflight_guard_id": execution_preflight_guard.get( + "guard_id" + ), + "source_no_write_invocation_package_id": package.get("package_id"), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_runner_invocation_boundary": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_runner_invocation_boundary_closeout": ( + closeout_ready + ), + "execution_preflight_guard_closeout_ready": closeout_ready, + "runner_invocation_boundary_bound": closeout_ready, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "dry_run_execution_performed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "reads_secret_in_preview": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_execution_preflight_guard_closeout = { + "execution_preflight_guard_closeout_id": closeout_id, + "authorization_material_type": ( + "controlled_dry_run_execution_preflight_guard_closeout" + ), + "source_no_write_invocation_package_closeout_id": package_closeout.get( + "no_write_invocation_package_closeout_id" + ), + "source_execution_preflight_guard_id": execution_preflight_guard.get( + "guard_id" + ), + "source_no_write_invocation_package_id": package.get("package_id"), + "source_invocation_receipt_closeout_id": invocation_closeout.get( + "invocation_receipt_closeout_id" + ), + "required_command_shape_hash": execution_preflight_guard.get( + "required_command_shape_hash" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_execution_preflight_guard_closeout": ( + closeout_ready + ), + "execution_preflight_guard_closeout_fields": guard_closeout_fields, + "execution_preflight_guard_closeout_field_count": len( + guard_closeout_fields + ), + "execution_preflight_guard_closeout_acceptance_gates": ( + guard_closeout_acceptance_gates + ), + "execution_preflight_guard_closeout_acceptance_gate_count": len( + guard_closeout_acceptance_gates + ), + "runner_invocation_boundary": runner_invocation_boundary, + "runner_invocation_boundary_count": 1, + "runner_invocation_boundary_field_count": len( + runner_invocation_boundary_fields + ), + "execution_preflight_guard": execution_preflight_guard, + "execution_preflight_guard_count": 1, + "no_write_invocation_package_closeout": package_closeout, + "no_write_invocation_package_closeout_count": 1, + "no_write_invocation_package": package, + "no_write_invocation_package_count": 1, + "invocation_receipt_closeout": invocation_closeout, + "invocation_receipt_closeout_count": 1, + "dry_run_invocation_readiness_receipt": receipt, + "dry_run_invocation_readiness_receipt_count": 1, + "apply_executor_readiness_closeout": readiness_closeout, + "apply_executor_readiness_closeout_count": 1, + "apply_executor_readiness_contract": readiness_contract, + "apply_executor_readiness_contract_count": 1, + "pre_apply_replay_closeout": pre_apply_closeout, + "pre_apply_replay_closeout_count": 1, + "pre_apply_replay_verifier": replay_verifier, + "pre_apply_replay_verifier_count": 1, + "final_dry_run_executor_guard": final_guard, + "final_dry_run_executor_guard_count": 1, + "no_apply_enforcement_verification": no_apply_enforcement, + "no_apply_enforcement_verification_count": 1, + "target_file": package_closeout.get("target_file"), + "expected_sha256": package_closeout.get("expected_sha256"), + "actual_sha256": package_closeout.get("actual_sha256"), + "hash_matches": package_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "execution_preflight_guard_closeout_only": True, + "runner_invocation_boundary_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + } + controlled_dry_run_execution_preflight_guard_closeout_contract = { + "mode": "controlled_dry_run_execution_preflight_guard_closeout_and_runner_invocation_boundary_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-execution-preflight-guard-closeout" + ), + "source_no_write_invocation_package_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-no-write-invocation-package-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_runner_invocation_boundary": ( + closeout_ready + ), + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": False, + "signs_database_apply_authorization": False, + "writes_database": False, + "executes_in_preview": False, + "secret_material_required_in_preview": False, + "manual_review_mode": "exception_only", + } + output_summary = dict(summary) + output_summary.update( + { + "controlled_dry_run_execution_preflight_guard_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_execution_preflight_guard_closeout_check_count": len( + checks + ), + "controlled_dry_run_execution_preflight_guard_closeout_pass_count": ( + passed_count + ), + "controlled_dry_run_execution_preflight_guard_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_execution_preflight_guard_closeout_count": 1, + "controlled_dry_run_execution_preflight_guard_closeout_field_count": len( + guard_closeout_fields + ), + "controlled_dry_run_execution_preflight_guard_closeout_acceptance_gate_count": len( + guard_closeout_acceptance_gates + ), + "runner_invocation_boundary_count": 1, + "runner_invocation_boundary_field_count": len( + runner_invocation_boundary_fields + ), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + } + ) + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_EXECUTION_PREFLIGHT_GUARD_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(package_closeout_result.get("success")), + "generated_at": package_closeout_result.get("generated_at"), + "source_policy": package_closeout_result.get("policy"), + "stats": package_closeout_result.get("stats") or {}, + "summary": output_summary, + "future_database_apply_controlled_dry_run_runner_invocation_boundary": ( + future_database_apply_controlled_dry_run_runner_invocation_boundary + ), + "controlled_dry_run_execution_preflight_guard_closeout": ( + controlled_dry_run_execution_preflight_guard_closeout + ), + "controlled_dry_run_execution_preflight_guard_closeout_contract": ( + controlled_dry_run_execution_preflight_guard_closeout_contract + ), + "controlled_dry_run_execution_preflight_guard_closeout_checks": checks, + "source_controlled_dry_run_no_write_invocation_package_closeout_summary": ( + summary + ), + "source_controlled_dry_run_no_write_invocation_package_closeout_contract": ( + package_closeout_contract + ), + "source_controlled_dry_run_no_write_invocation_package_closeout": ( + package_closeout + ), + "source_database_apply_controlled_dry_run_execution_preflight_guard": ( + future_guard + ), + "safety": { + "read_only_db_apply_controlled_dry_run_execution_preflight_guard_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future controlled dry-run runner invocation boundary closeout.", + "Keep actual runner invocation disabled until the boundary closeout is separately machine-verifiable.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_runner_invocation_boundary_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out runner invocation boundary and bind no-execution handoff.""" + guard_closeout_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_execution_preflight_guard_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_boundary = ( + guard_closeout_result.get( + "future_database_apply_controlled_dry_run_runner_invocation_boundary" + ) + or {} + ) + guard_closeout = ( + guard_closeout_result.get( + "controlled_dry_run_execution_preflight_guard_closeout" + ) + or {} + ) + guard_closeout_contract = ( + guard_closeout_result.get( + "controlled_dry_run_execution_preflight_guard_closeout_contract" + ) + or {} + ) + summary = guard_closeout_result.get("summary") or {} + safety = guard_closeout_result.get("safety") or {} + boundary = guard_closeout.get("runner_invocation_boundary") or {} + execution_preflight_guard = guard_closeout.get("execution_preflight_guard") or {} + package_closeout = guard_closeout.get("no_write_invocation_package_closeout") or {} + package = guard_closeout.get("no_write_invocation_package") or {} + invocation_closeout = guard_closeout.get("invocation_receipt_closeout") or {} + receipt = guard_closeout.get("dry_run_invocation_readiness_receipt") or {} + readiness_closeout = guard_closeout.get("apply_executor_readiness_closeout") or {} + readiness_contract = guard_closeout.get("apply_executor_readiness_contract") or {} + pre_apply_closeout = guard_closeout.get("pre_apply_replay_closeout") or {} + replay_verifier = guard_closeout.get("pre_apply_replay_verifier") or {} + final_guard = guard_closeout.get("final_dry_run_executor_guard") or {} + no_apply_enforcement = ( + guard_closeout.get("no_apply_enforcement_verification") or {} + ) + rollback_binding = guard_closeout.get("rollback_binding") or {} + verifier_binding = guard_closeout.get("post_apply_verifier_binding") or {} + closeout_id = _db_apply_controlled_dry_run_runner_invocation_boundary_closeout_id( + guard_closeout_result + ) + handoff_id = f"{closeout_id}-no-execution-receipt-handoff" + boundary_closeout_fields = [ + "runner_invocation_boundary_closeout_id", + "source_execution_preflight_guard_closeout_id", + "source_runner_invocation_boundary_id", + "source_execution_preflight_guard_id", + "source_no_write_invocation_package_id", + "required_command_shape_hash", + "no_execution_receipt_handoff_id", + "target_migration_file", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "dry_run_executor_invocation_allowed", + "abort_conditions", + ] + boundary_closeout_acceptance_gates = [ + "execution_preflight_guard_closeout_ready", + "source_chain_ids_match", + "runner_invocation_boundary_ready", + "runner_invocation_boundary_no_execute", + "no_execution_receipt_handoff_bound", + "no_execution_receipt_handoff_blocks_execution", + "execution_preflight_guard_and_no_write_package_carried_forward", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "no_secret_signature_or_database_apply", + ] + no_execution_receipt_handoff_fields = [ + "handoff_id", + "source_runner_invocation_boundary_closeout_id", + "source_runner_invocation_boundary_id", + "source_execution_preflight_guard_closeout_id", + "source_execution_preflight_guard_id", + "required_boundary_mode", + "required_command_shape_hash", + "handoff_mode", + "dry_run_executor_invocation_allowed", + "endpoint_execution_allowed", + "sql_execution_allowed", + "database_apply_authorized", + ] + abort_conditions = [ + "abort_if_execution_preflight_guard_closeout_not_ready", + "abort_if_source_chain_ids_do_not_match", + "abort_if_runner_invocation_boundary_missing", + "abort_if_runner_invocation_boundary_allows_invocation", + "abort_if_no_execution_receipt_handoff_missing", + "abort_if_no_execution_receipt_handoff_executes", + "abort_if_endpoint_or_sql_execution_is_allowed", + "abort_if_database_write_or_apply_is_allowed", + "abort_if_target_migration_hash_changes", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_any_secret_signature_or_apply_material_is_present", + ] + no_execution_receipt_handoff = { + "handoff_id": handoff_id, + "source_runner_invocation_boundary_closeout_id": closeout_id, + "source_runner_invocation_boundary_id": boundary.get("boundary_id"), + "source_execution_preflight_guard_closeout_id": guard_closeout.get( + "execution_preflight_guard_closeout_id" + ), + "source_execution_preflight_guard_id": execution_preflight_guard.get( + "guard_id" + ), + "source_no_write_invocation_package_id": package.get("package_id"), + "required_boundary_mode": "runner_invocation_boundary_preview_only", + "required_command_shape_hash": boundary.get("required_command_shape_hash"), + "handoff_status": "no_execution_receipt_handoff_preview_ready", + "handoff_mode": "no_execution_receipt_handoff_preview_only", + "execution_receipt_present": False, + "execution_receipt_required": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "ready_for_no_execution_receipt_handoff_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "reads_secret_in_preview": False, + "no_execution_receipt_handoff_field_count": len( + no_execution_receipt_handoff_fields + ), + "no_execution_receipt_handoff_fields": no_execution_receipt_handoff_fields, + } + guard_closeout_ready = ( + guard_closeout_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_EXECUTION_PREFLIGHT_GUARD_CLOSEOUT_READY" + and summary.get( + "controlled_dry_run_execution_preflight_guard_closeout_ready_count" + ) + == 1 + and summary.get( + "controlled_dry_run_execution_preflight_guard_closeout_pass_count" + ) + == summary.get( + "controlled_dry_run_execution_preflight_guard_closeout_check_count" + ) + ) + source_chain_ids_match = ( + bool(guard_closeout.get("execution_preflight_guard_closeout_id")) + and guard_closeout.get("execution_preflight_guard_closeout_id") + == future_boundary.get("execution_preflight_guard_closeout_id") + == boundary.get("source_execution_preflight_guard_closeout_id") + == no_execution_receipt_handoff.get( + "source_execution_preflight_guard_closeout_id" + ) + and boundary.get("boundary_id") + == future_boundary.get("runner_invocation_boundary_id") + == no_execution_receipt_handoff.get( + "source_runner_invocation_boundary_id" + ) + and execution_preflight_guard.get("guard_id") + == future_boundary.get("source_execution_preflight_guard_id") + == boundary.get("source_execution_preflight_guard_id") + == no_execution_receipt_handoff.get( + "source_execution_preflight_guard_id" + ) + and package.get("package_id") + == future_boundary.get("source_no_write_invocation_package_id") + == boundary.get("source_no_write_invocation_package_id") + == no_execution_receipt_handoff.get( + "source_no_write_invocation_package_id" + ) + ) + runner_invocation_boundary_ready = ( + boundary.get("boundary_status") + == "runner_invocation_boundary_preview_ready" + and boundary.get("boundary_id") + == future_boundary.get("runner_invocation_boundary_id") + and int(boundary.get("runner_invocation_boundary_field_count") or 0) + == 12 + ) + runner_invocation_boundary_no_execute = ( + boundary.get("boundary_mode") == "runner_invocation_boundary_preview_only" + and boundary.get("dry_run_executor_invocation_allowed") is False + and boundary.get("runner_invocation_allowed") is False + and boundary.get("ready_for_runner_invocation_boundary_now") is False + and boundary.get("ready_for_dry_run_executor_invocation_now") is False + and boundary.get("ready_for_actual_dry_run_execution_now") is False + and boundary.get("endpoint_execution_allowed") is False + and boundary.get("sql_execution_allowed") is False + and boundary.get("database_write_allowed") is False + and boundary.get("database_apply_authorized") is False + and boundary.get("executes_database_apply") is False + and boundary.get("executes_endpoint") is False + and boundary.get("executes_sql") is False + and boundary.get("writes_database") is False + and boundary.get("captures_stdout") is False + and boundary.get("captures_stderr") is False + ) + no_execution_receipt_handoff_bound = ( + bool(no_execution_receipt_handoff.get("handoff_id")) + and no_execution_receipt_handoff.get( + "source_runner_invocation_boundary_closeout_id" + ) + == closeout_id + and no_execution_receipt_handoff.get( + "source_runner_invocation_boundary_id" + ) + == boundary.get("boundary_id") + and no_execution_receipt_handoff.get("required_command_shape_hash") + == boundary.get("required_command_shape_hash") + and int( + no_execution_receipt_handoff.get( + "no_execution_receipt_handoff_field_count" + ) + or 0 + ) + == len(no_execution_receipt_handoff_fields) + ) + no_execution_receipt_handoff_blocks_execution = ( + no_execution_receipt_handoff.get("handoff_mode") + == "no_execution_receipt_handoff_preview_only" + and no_execution_receipt_handoff.get("execution_receipt_present") is False + and no_execution_receipt_handoff.get("execution_receipt_required") is False + and no_execution_receipt_handoff.get("dry_run_executor_invocation_allowed") + is False + and no_execution_receipt_handoff.get("runner_invocation_allowed") is False + and no_execution_receipt_handoff.get( + "ready_for_no_execution_receipt_handoff_now" + ) + is False + and no_execution_receipt_handoff.get( + "ready_for_dry_run_executor_invocation_now" + ) + is False + and no_execution_receipt_handoff.get("ready_for_actual_dry_run_execution_now") + is False + and no_execution_receipt_handoff.get("endpoint_execution_allowed") is False + and no_execution_receipt_handoff.get("sql_execution_allowed") is False + and no_execution_receipt_handoff.get("database_write_allowed") is False + and no_execution_receipt_handoff.get("database_apply_authorized") is False + and no_execution_receipt_handoff.get("executes_database_apply") is False + and no_execution_receipt_handoff.get("executes_endpoint") is False + and no_execution_receipt_handoff.get("executes_sql") is False + and no_execution_receipt_handoff.get("writes_database") is False + and no_execution_receipt_handoff.get("captures_stdout") is False + and no_execution_receipt_handoff.get("captures_stderr") is False + and no_execution_receipt_handoff.get("stdout_included") is False + and no_execution_receipt_handoff.get("stderr_included") is False + ) + execution_preflight_guard_and_no_write_package_carried_forward = ( + execution_preflight_guard.get("guard_mode") + == "execution_preflight_guard_preview_only" + and execution_preflight_guard.get("dry_run_executor_invocation_allowed") + is False + and guard_closeout.get("execution_preflight_guard_closeout_only") is True + and package_closeout.get("no_write_invocation_package_closeout_only") is True + and package_closeout.get("database_apply_authorized") is False + and package.get("package_mode") == "no_write_invocation_package_preview_only" + and package.get("dry_run_executor_invocation_allowed") is False + and invocation_closeout.get("invocation_receipt_closeout_only") is True + and invocation_closeout.get("database_apply_authorized") is False + and receipt.get("receipt_mode") == "dry_run_invocation_readiness_preview_only" + and receipt.get("dry_run_executor_invocation_allowed") is False + and readiness_contract.get("readiness_mode") + == "apply_executor_readiness_contract_preview_only" + and readiness_contract.get("dry_run_executor_invocation_allowed") + is False + and replay_verifier.get("replay_mode") == "pre_apply_replay_preview_only" + and replay_verifier.get("database_apply_authorized") is False + and final_guard.get("guard_status") + == "final_dry_run_executor_guard_preview_ready" + and final_guard.get("dry_run_executor_invocation_allowed") is False + and no_apply_enforcement.get("enforcement_status") + == "no_apply_enforcement_preview_ready" + and no_apply_enforcement.get("database_apply_authorized") is False + ) + target_hash_locked = ( + guard_closeout.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(guard_closeout.get("expected_sha256")) + and bool(guard_closeout.get("actual_sha256")) + and guard_closeout.get("expected_sha256") + == guard_closeout.get("actual_sha256") + and guard_closeout.get("hash_matches") is True + and guard_closeout.get("target_migration_hash_locked") is True + ) + rollback_and_verifier_bound = ( + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False + ) + guard_closeout_contract_blocks_database_apply = ( + guard_closeout_contract.get("executes_database_apply") is False + and guard_closeout_contract.get("executes_endpoint") is False + and guard_closeout_contract.get("executes_sql") is False + and guard_closeout_contract.get("database_apply_authorized") is False + and guard_closeout_contract.get("ready_for_database_apply_now") is False + and guard_closeout_contract.get("ready_for_dry_run_executor_invocation_now") + is False + and guard_closeout_contract.get("ready_for_actual_dry_run_execution_now") + is False + and guard_closeout_contract.get("signs_database_apply_authorization") + is False + and guard_closeout_contract.get("writes_database") is False + and guard_closeout_contract.get("executes_in_preview") is False + ) + side_effect_free = ( + summary.get("reads_secret_count", 0) == 0 + and summary.get("executes_script_count", 0) == 0 + and summary.get("executes_migration_count", 0) == 0 + and summary.get("executes_endpoint_count", 0) == 0 + and summary.get("executes_sql_count", 0) == 0 + and summary.get("writes_database_count", 0) == 0 + and summary.get("signs_database_apply_authorization_count", 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("executes_database_apply") is False + and runner_invocation_boundary_no_execute + and no_execution_receipt_handoff_blocks_execution + ) + checks = [ + _controlled_dry_run_runner_invocation_boundary_closeout_check( + "execution_preflight_guard_closeout_ready", + guard_closeout_ready, + { + "result": guard_closeout_result.get("result"), + "ready_count": summary.get( + "controlled_dry_run_execution_preflight_guard_closeout_ready_count" + ), + "pass_count": summary.get( + "controlled_dry_run_execution_preflight_guard_closeout_pass_count" + ), + "check_count": summary.get( + "controlled_dry_run_execution_preflight_guard_closeout_check_count" + ), + }, + "wait_for_execution_preflight_guard_closeout_ready", + ), + _controlled_dry_run_runner_invocation_boundary_closeout_check( + "source_chain_ids_match", + source_chain_ids_match, + { + "execution_preflight_guard_closeout_id": guard_closeout.get( + "execution_preflight_guard_closeout_id" + ), + "runner_invocation_boundary_id": boundary.get("boundary_id"), + "execution_preflight_guard_id": execution_preflight_guard.get( + "guard_id" + ), + }, + "wait_for_source_chain_ids_match", + ), + _controlled_dry_run_runner_invocation_boundary_closeout_check( + "runner_invocation_boundary_ready", + runner_invocation_boundary_ready, + { + "boundary_id": boundary.get("boundary_id"), + "boundary_status": boundary.get("boundary_status"), + "field_count": boundary.get( + "runner_invocation_boundary_field_count" + ), + }, + "wait_for_runner_invocation_boundary_ready", + ), + _controlled_dry_run_runner_invocation_boundary_closeout_check( + "runner_invocation_boundary_no_execute", + runner_invocation_boundary_no_execute, + { + "boundary_mode": boundary.get("boundary_mode"), + "dry_run_executor_invocation_allowed": boundary.get( + "dry_run_executor_invocation_allowed" + ), + "runner_invocation_allowed": boundary.get( + "runner_invocation_allowed" + ), + }, + "abort_if_runner_invocation_boundary_allows_invocation", + ), + _controlled_dry_run_runner_invocation_boundary_closeout_check( + "no_execution_receipt_handoff_bound", + no_execution_receipt_handoff_bound, + { + "handoff_id": no_execution_receipt_handoff.get("handoff_id"), + "source_runner_invocation_boundary_id": ( + no_execution_receipt_handoff.get( + "source_runner_invocation_boundary_id" + ) + ), + "field_count": no_execution_receipt_handoff.get( + "no_execution_receipt_handoff_field_count" + ), + }, + "wait_for_no_execution_receipt_handoff_binding", + ), + _controlled_dry_run_runner_invocation_boundary_closeout_check( + "no_execution_receipt_handoff_blocks_execution", + no_execution_receipt_handoff_blocks_execution, + { + "handoff_mode": no_execution_receipt_handoff.get("handoff_mode"), + "execution_receipt_present": no_execution_receipt_handoff.get( + "execution_receipt_present" + ), + "runner_invocation_allowed": no_execution_receipt_handoff.get( + "runner_invocation_allowed" + ), + }, + "abort_if_no_execution_receipt_handoff_executes", + ), + _controlled_dry_run_runner_invocation_boundary_closeout_check( + "execution_preflight_guard_and_no_write_package_carried_forward", + execution_preflight_guard_and_no_write_package_carried_forward, + { + "guard_closeout_only": guard_closeout.get( + "execution_preflight_guard_closeout_only" + ), + "package_closeout_only": package_closeout.get( + "no_write_invocation_package_closeout_only" + ), + "guard_mode": execution_preflight_guard.get("guard_mode"), + "package_mode": package.get("package_mode"), + "receipt_mode": receipt.get("receipt_mode"), + }, + "wait_for_execution_preflight_guard_and_no_write_package_carry_forward", + ), + _controlled_dry_run_runner_invocation_boundary_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": guard_closeout.get("target_file"), + "hash_matches": guard_closeout.get("hash_matches"), + "expected_sha256_present": bool( + guard_closeout.get("expected_sha256") + ), + "actual_sha256_present": bool( + guard_closeout.get("actual_sha256") + ), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_runner_invocation_boundary_closeout_check( + "rollback_and_post_apply_verifier_bound", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "database_apply_authorized": verifier_binding.get( + "database_apply_authorized" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_runner_invocation_boundary_closeout_check( + "execution_preflight_guard_closeout_contract_blocks_database_apply", + guard_closeout_contract_blocks_database_apply, + { + "permits_future_database_apply_controlled_dry_run_runner_invocation_boundary": ( + guard_closeout_contract.get( + "permits_future_database_apply_controlled_dry_run_runner_invocation_boundary" + ) + ), + "database_apply_authorized": guard_closeout_contract.get( + "database_apply_authorized" + ), + "writes_database": guard_closeout_contract.get("writes_database"), + }, + "abort_if_execution_preflight_guard_closeout_contract_authorizes_database_apply", + ), + _controlled_dry_run_runner_invocation_boundary_closeout_check( + "preview_has_no_side_effects_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + }, + "abort_on_preview_side_effect_execution_or_signing", + ), + _controlled_dry_run_runner_invocation_boundary_closeout_check( + "manual_review_not_required_for_safe_preview", + guard_closeout_contract.get("manual_review_mode") + == "exception_only" + and safety.get("manual_review_mode") == "exception_only", + { + "contract_manual_review_mode": guard_closeout_contract.get( + "manual_review_mode" + ), + "safety_manual_review_mode": safety.get("manual_review_mode"), + }, + "keep_manual_review_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_RUNNER_INVOCATION_BOUNDARY_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_EXECUTION_PREFLIGHT_GUARD_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_no_execution_receipt_handoff = { + "runner_invocation_boundary_closeout_id": closeout_id, + "no_execution_receipt_handoff_id": handoff_id, + "source_execution_preflight_guard_closeout_id": guard_closeout.get( + "execution_preflight_guard_closeout_id" + ), + "source_runner_invocation_boundary_id": boundary.get("boundary_id"), + "source_execution_preflight_guard_id": execution_preflight_guard.get( + "guard_id" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_no_execution_receipt_handoff": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_no_execution_receipt_handoff_closeout": ( + closeout_ready + ), + "runner_invocation_boundary_closeout_ready": closeout_ready, + "no_execution_receipt_handoff_bound": closeout_ready, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "dry_run_execution_performed": False, + "execution_receipt_present": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "reads_secret_in_preview": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_runner_invocation_boundary_closeout = { + "runner_invocation_boundary_closeout_id": closeout_id, + "authorization_material_type": ( + "controlled_dry_run_runner_invocation_boundary_closeout" + ), + "source_execution_preflight_guard_closeout_id": guard_closeout.get( + "execution_preflight_guard_closeout_id" + ), + "source_runner_invocation_boundary_id": boundary.get("boundary_id"), + "source_execution_preflight_guard_id": execution_preflight_guard.get( + "guard_id" + ), + "source_no_write_invocation_package_id": package.get("package_id"), + "required_command_shape_hash": boundary.get("required_command_shape_hash"), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_runner_invocation_boundary_closeout": ( + closeout_ready + ), + "runner_invocation_boundary_closeout_fields": boundary_closeout_fields, + "runner_invocation_boundary_closeout_field_count": len( + boundary_closeout_fields + ), + "runner_invocation_boundary_closeout_acceptance_gates": ( + boundary_closeout_acceptance_gates + ), + "runner_invocation_boundary_closeout_acceptance_gate_count": len( + boundary_closeout_acceptance_gates + ), + "no_execution_receipt_handoff": no_execution_receipt_handoff, + "no_execution_receipt_handoff_count": 1, + "no_execution_receipt_handoff_field_count": len( + no_execution_receipt_handoff_fields + ), + "runner_invocation_boundary": boundary, + "runner_invocation_boundary_count": 1, + "execution_preflight_guard_closeout": guard_closeout, + "execution_preflight_guard_closeout_count": 1, + "execution_preflight_guard": execution_preflight_guard, + "execution_preflight_guard_count": 1, + "no_write_invocation_package_closeout": package_closeout, + "no_write_invocation_package_closeout_count": 1, + "no_write_invocation_package": package, + "no_write_invocation_package_count": 1, + "invocation_receipt_closeout": invocation_closeout, + "invocation_receipt_closeout_count": 1, + "dry_run_invocation_readiness_receipt": receipt, + "dry_run_invocation_readiness_receipt_count": 1, + "apply_executor_readiness_closeout": readiness_closeout, + "apply_executor_readiness_closeout_count": 1, + "apply_executor_readiness_contract": readiness_contract, + "apply_executor_readiness_contract_count": 1, + "pre_apply_replay_closeout": pre_apply_closeout, + "pre_apply_replay_closeout_count": 1, + "pre_apply_replay_verifier": replay_verifier, + "pre_apply_replay_verifier_count": 1, + "final_dry_run_executor_guard": final_guard, + "final_dry_run_executor_guard_count": 1, + "no_apply_enforcement_verification": no_apply_enforcement, + "no_apply_enforcement_verification_count": 1, + "target_file": guard_closeout.get("target_file"), + "expected_sha256": guard_closeout.get("expected_sha256"), + "actual_sha256": guard_closeout.get("actual_sha256"), + "hash_matches": guard_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "runner_invocation_boundary_closeout_only": True, + "no_execution_receipt_handoff_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "captures_stdout": False, + "captures_stderr": False, + } + controlled_dry_run_runner_invocation_boundary_closeout_contract = { + "mode": "controlled_dry_run_runner_invocation_boundary_closeout_and_no_execution_receipt_handoff_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-runner-invocation-boundary-closeout" + ), + "source_execution_preflight_guard_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-execution-preflight-guard-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_no_execution_receipt_handoff": ( + closeout_ready + ), + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": False, + "signs_database_apply_authorization": False, + "writes_database": False, + "executes_in_preview": False, + "secret_material_required_in_preview": False, + "manual_review_mode": "exception_only", + } + output_summary = dict(summary) + output_summary.update( + { + "controlled_dry_run_runner_invocation_boundary_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_runner_invocation_boundary_closeout_check_count": len( + checks + ), + "controlled_dry_run_runner_invocation_boundary_closeout_pass_count": ( + passed_count + ), + "controlled_dry_run_runner_invocation_boundary_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_runner_invocation_boundary_closeout_count": 1, + "controlled_dry_run_runner_invocation_boundary_closeout_field_count": len( + boundary_closeout_fields + ), + "controlled_dry_run_runner_invocation_boundary_closeout_acceptance_gate_count": len( + boundary_closeout_acceptance_gates + ), + "no_execution_receipt_handoff_count": 1, + "no_execution_receipt_handoff_field_count": len( + no_execution_receipt_handoff_fields + ), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + } + ) + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_RUNNER_INVOCATION_BOUNDARY_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(guard_closeout_result.get("success")), + "generated_at": guard_closeout_result.get("generated_at"), + "source_policy": guard_closeout_result.get("policy"), + "stats": guard_closeout_result.get("stats") or {}, + "summary": output_summary, + "future_database_apply_controlled_dry_run_no_execution_receipt_handoff": ( + future_database_apply_controlled_dry_run_no_execution_receipt_handoff + ), + "controlled_dry_run_runner_invocation_boundary_closeout": ( + controlled_dry_run_runner_invocation_boundary_closeout + ), + "controlled_dry_run_runner_invocation_boundary_closeout_contract": ( + controlled_dry_run_runner_invocation_boundary_closeout_contract + ), + "controlled_dry_run_runner_invocation_boundary_closeout_checks": checks, + "source_controlled_dry_run_execution_preflight_guard_closeout_summary": ( + summary + ), + "source_controlled_dry_run_execution_preflight_guard_closeout_contract": ( + guard_closeout_contract + ), + "source_controlled_dry_run_execution_preflight_guard_closeout": ( + guard_closeout + ), + "source_database_apply_controlled_dry_run_runner_invocation_boundary": ( + future_boundary + ), + "safety": { + "read_only_db_apply_controlled_dry_run_runner_invocation_boundary_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future controlled dry-run no-execution receipt handoff closeout.", + "Keep actual runner invocation disabled until an execution receipt is separately authorized outside this preview lane.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_no_execution_receipt_handoff_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out no-execution handoff and bind final no-runner proof.""" + boundary_closeout_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_runner_invocation_boundary_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_handoff = ( + boundary_closeout_result.get( + "future_database_apply_controlled_dry_run_no_execution_receipt_handoff" + ) + or {} + ) + boundary_closeout = ( + boundary_closeout_result.get( + "controlled_dry_run_runner_invocation_boundary_closeout" + ) + or {} + ) + boundary_contract = ( + boundary_closeout_result.get( + "controlled_dry_run_runner_invocation_boundary_closeout_contract" + ) + or {} + ) + summary = boundary_closeout_result.get("summary") or {} + safety = boundary_closeout_result.get("safety") or {} + handoff = boundary_closeout.get("no_execution_receipt_handoff") or {} + boundary = boundary_closeout.get("runner_invocation_boundary") or {} + guard_closeout = boundary_closeout.get("execution_preflight_guard_closeout") or {} + execution_preflight_guard = boundary_closeout.get("execution_preflight_guard") or {} + package_closeout = boundary_closeout.get("no_write_invocation_package_closeout") or {} + package = boundary_closeout.get("no_write_invocation_package") or {} + invocation_closeout = boundary_closeout.get("invocation_receipt_closeout") or {} + receipt = boundary_closeout.get("dry_run_invocation_readiness_receipt") or {} + readiness_closeout = boundary_closeout.get("apply_executor_readiness_closeout") or {} + readiness_contract = boundary_closeout.get("apply_executor_readiness_contract") or {} + pre_apply_closeout = boundary_closeout.get("pre_apply_replay_closeout") or {} + replay_verifier = boundary_closeout.get("pre_apply_replay_verifier") or {} + final_guard = boundary_closeout.get("final_dry_run_executor_guard") or {} + no_apply_enforcement = ( + boundary_closeout.get("no_apply_enforcement_verification") or {} + ) + rollback_binding = boundary_closeout.get("rollback_binding") or {} + verifier_binding = boundary_closeout.get("post_apply_verifier_binding") or {} + closeout_id = ( + _db_apply_controlled_dry_run_no_execution_receipt_handoff_closeout_id( + boundary_closeout_result + ) + ) + proof_id = f"{closeout_id}-final-no-runner-execution-proof" + handoff_closeout_fields = [ + "no_execution_receipt_handoff_closeout_id", + "source_runner_invocation_boundary_closeout_id", + "source_no_execution_receipt_handoff_id", + "source_runner_invocation_boundary_id", + "source_execution_preflight_guard_closeout_id", + "final_no_runner_execution_proof_id", + "required_command_shape_hash", + "target_migration_file", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "dry_run_executor_invocation_allowed", + "abort_conditions", + ] + handoff_closeout_acceptance_gates = [ + "runner_invocation_boundary_closeout_ready", + "source_chain_ids_match", + "no_execution_receipt_handoff_ready", + "no_execution_receipt_handoff_no_execute", + "final_no_runner_execution_proof_bound", + "final_no_runner_execution_proof_blocks_execution", + "previous_closeouts_carried_forward", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "no_secret_signature_or_database_apply", + ] + final_no_runner_execution_proof_fields = [ + "proof_id", + "source_no_execution_receipt_handoff_closeout_id", + "source_no_execution_receipt_handoff_id", + "source_runner_invocation_boundary_closeout_id", + "source_runner_invocation_boundary_id", + "source_execution_preflight_guard_closeout_id", + "proof_mode", + "dry_run_executor_invocation_allowed", + "runner_invocation_allowed", + "endpoint_execution_allowed", + "sql_execution_allowed", + "database_apply_authorized", + ] + abort_conditions = [ + "abort_if_runner_invocation_boundary_closeout_not_ready", + "abort_if_source_chain_ids_do_not_match", + "abort_if_no_execution_receipt_handoff_missing", + "abort_if_no_execution_receipt_handoff_allows_execution", + "abort_if_final_no_runner_execution_proof_missing", + "abort_if_final_no_runner_execution_proof_reports_execution", + "abort_if_endpoint_or_sql_execution_is_allowed", + "abort_if_database_write_or_apply_is_allowed", + "abort_if_target_migration_hash_changes", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_any_secret_signature_or_apply_material_is_present", + ] + final_no_runner_execution_proof = { + "proof_id": proof_id, + "source_no_execution_receipt_handoff_closeout_id": closeout_id, + "source_no_execution_receipt_handoff_id": handoff.get("handoff_id"), + "source_runner_invocation_boundary_closeout_id": boundary_closeout.get( + "runner_invocation_boundary_closeout_id" + ), + "source_runner_invocation_boundary_id": boundary.get("boundary_id"), + "source_execution_preflight_guard_closeout_id": guard_closeout.get( + "execution_preflight_guard_closeout_id" + ), + "source_execution_preflight_guard_id": execution_preflight_guard.get( + "guard_id" + ), + "required_handoff_mode": "no_execution_receipt_handoff_preview_only", + "required_command_shape_hash": handoff.get("required_command_shape_hash"), + "proof_status": "final_no_runner_execution_proof_preview_ready", + "proof_mode": "final_no_runner_execution_proof_preview_only", + "execution_receipt_present": False, + "execution_receipt_required": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "ready_for_final_no_runner_execution_proof_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "reads_secret_in_preview": False, + "final_no_runner_execution_proof_field_count": len( + final_no_runner_execution_proof_fields + ), + "final_no_runner_execution_proof_fields": ( + final_no_runner_execution_proof_fields + ), + } + boundary_closeout_ready = ( + boundary_closeout_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_RUNNER_INVOCATION_BOUNDARY_CLOSEOUT_READY" + and summary.get( + "controlled_dry_run_runner_invocation_boundary_closeout_ready_count" + ) + == 1 + and summary.get( + "controlled_dry_run_runner_invocation_boundary_closeout_pass_count" + ) + == summary.get( + "controlled_dry_run_runner_invocation_boundary_closeout_check_count" + ) + ) + source_chain_ids_match = ( + bool(boundary_closeout.get("runner_invocation_boundary_closeout_id")) + and boundary_closeout.get("runner_invocation_boundary_closeout_id") + == future_handoff.get("runner_invocation_boundary_closeout_id") + == handoff.get("source_runner_invocation_boundary_closeout_id") + == final_no_runner_execution_proof.get( + "source_runner_invocation_boundary_closeout_id" + ) + and handoff.get("handoff_id") + == future_handoff.get("no_execution_receipt_handoff_id") + == final_no_runner_execution_proof.get( + "source_no_execution_receipt_handoff_id" + ) + and boundary.get("boundary_id") + == future_handoff.get("source_runner_invocation_boundary_id") + == handoff.get("source_runner_invocation_boundary_id") + == final_no_runner_execution_proof.get( + "source_runner_invocation_boundary_id" + ) + and guard_closeout.get("execution_preflight_guard_closeout_id") + == future_handoff.get("source_execution_preflight_guard_closeout_id") + == handoff.get("source_execution_preflight_guard_closeout_id") + == final_no_runner_execution_proof.get( + "source_execution_preflight_guard_closeout_id" + ) + and execution_preflight_guard.get("guard_id") + == future_handoff.get("source_execution_preflight_guard_id") + == handoff.get("source_execution_preflight_guard_id") + == final_no_runner_execution_proof.get("source_execution_preflight_guard_id") + ) + no_execution_receipt_handoff_ready = ( + handoff.get("handoff_status") == "no_execution_receipt_handoff_preview_ready" + and handoff.get("handoff_id") + == future_handoff.get("no_execution_receipt_handoff_id") + and int(handoff.get("no_execution_receipt_handoff_field_count") or 0) + == 12 + and summary.get("no_execution_receipt_handoff_count") == 1 + ) + no_execution_receipt_handoff_no_execute = ( + handoff.get("handoff_mode") == "no_execution_receipt_handoff_preview_only" + and handoff.get("execution_receipt_present") is False + and handoff.get("execution_receipt_required") is False + and handoff.get("dry_run_executor_invocation_allowed") is False + and handoff.get("runner_invocation_allowed") is False + and handoff.get("ready_for_no_execution_receipt_handoff_now") is False + and handoff.get("ready_for_dry_run_executor_invocation_now") is False + and handoff.get("ready_for_actual_dry_run_execution_now") is False + and handoff.get("endpoint_execution_allowed") is False + and handoff.get("sql_execution_allowed") is False + and handoff.get("database_write_allowed") is False + and handoff.get("database_apply_authorized") is False + and handoff.get("executes_database_apply") is False + and handoff.get("executes_endpoint") is False + and handoff.get("executes_sql") is False + and handoff.get("writes_database") is False + and handoff.get("captures_stdout") is False + and handoff.get("captures_stderr") is False + and handoff.get("stdout_included") is False + and handoff.get("stderr_included") is False + ) + final_no_runner_execution_proof_bound = ( + bool(final_no_runner_execution_proof.get("proof_id")) + and final_no_runner_execution_proof.get( + "source_no_execution_receipt_handoff_closeout_id" + ) + == closeout_id + and final_no_runner_execution_proof.get( + "source_no_execution_receipt_handoff_id" + ) + == handoff.get("handoff_id") + and final_no_runner_execution_proof.get("required_command_shape_hash") + == handoff.get("required_command_shape_hash") + and int( + final_no_runner_execution_proof.get( + "final_no_runner_execution_proof_field_count" + ) + or 0 + ) + == len(final_no_runner_execution_proof_fields) + ) + final_no_runner_execution_proof_blocks_execution = ( + final_no_runner_execution_proof.get("proof_mode") + == "final_no_runner_execution_proof_preview_only" + and final_no_runner_execution_proof.get("execution_receipt_present") + is False + and final_no_runner_execution_proof.get("execution_receipt_required") + is False + and final_no_runner_execution_proof.get("dry_run_executor_invoked") + is False + and final_no_runner_execution_proof.get("runner_invocation_performed") + is False + and final_no_runner_execution_proof.get("endpoint_executed") is False + and final_no_runner_execution_proof.get("sql_executed") is False + and final_no_runner_execution_proof.get("database_written") is False + and final_no_runner_execution_proof.get( + "dry_run_executor_invocation_allowed" + ) + is False + and final_no_runner_execution_proof.get("runner_invocation_allowed") + is False + and final_no_runner_execution_proof.get( + "ready_for_dry_run_executor_invocation_now" + ) + is False + and final_no_runner_execution_proof.get( + "ready_for_actual_dry_run_execution_now" + ) + is False + and final_no_runner_execution_proof.get("endpoint_execution_allowed") + is False + and final_no_runner_execution_proof.get("sql_execution_allowed") is False + and final_no_runner_execution_proof.get("database_write_allowed") is False + and final_no_runner_execution_proof.get("database_apply_authorized") + is False + and final_no_runner_execution_proof.get("executes_database_apply") + is False + and final_no_runner_execution_proof.get("executes_endpoint") is False + and final_no_runner_execution_proof.get("executes_sql") is False + and final_no_runner_execution_proof.get("writes_database") is False + and final_no_runner_execution_proof.get("captures_stdout") is False + and final_no_runner_execution_proof.get("captures_stderr") is False + and final_no_runner_execution_proof.get("stdout_included") is False + and final_no_runner_execution_proof.get("stderr_included") is False + ) + previous_closeouts_carried_forward = ( + boundary_closeout.get("runner_invocation_boundary_closeout_only") is True + and boundary_closeout.get("no_execution_receipt_handoff_only") is True + and boundary_closeout.get("database_apply_authorized") is False + and guard_closeout.get("execution_preflight_guard_closeout_only") is True + and package_closeout.get("no_write_invocation_package_closeout_only") + is True + and package.get("package_mode") == "no_write_invocation_package_preview_only" + and invocation_closeout.get("invocation_receipt_closeout_only") is True + and receipt.get("receipt_mode") == "dry_run_invocation_readiness_preview_only" + and readiness_contract.get("readiness_mode") + == "apply_executor_readiness_contract_preview_only" + and replay_verifier.get("replay_mode") == "pre_apply_replay_preview_only" + and final_guard.get("guard_status") + == "final_dry_run_executor_guard_preview_ready" + and no_apply_enforcement.get("enforcement_status") + == "no_apply_enforcement_preview_ready" + and no_execution_receipt_handoff_no_execute + ) + target_hash_locked = ( + boundary_closeout.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(boundary_closeout.get("expected_sha256")) + and bool(boundary_closeout.get("actual_sha256")) + and boundary_closeout.get("expected_sha256") + == boundary_closeout.get("actual_sha256") + and boundary_closeout.get("hash_matches") is True + and boundary_closeout.get("target_migration_hash_locked") is True + ) + rollback_and_verifier_bound = ( + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False + ) + boundary_contract_blocks_database_apply = ( + boundary_contract.get("executes_database_apply") is False + and boundary_contract.get("executes_endpoint") is False + and boundary_contract.get("executes_sql") is False + and boundary_contract.get("database_apply_authorized") is False + and boundary_contract.get("ready_for_database_apply_now") is False + and boundary_contract.get("ready_for_dry_run_executor_invocation_now") + is False + and boundary_contract.get("ready_for_actual_dry_run_execution_now") + is False + and boundary_contract.get("signs_database_apply_authorization") is False + and boundary_contract.get("writes_database") is False + and boundary_contract.get("executes_in_preview") is False + ) + side_effect_free = ( + summary.get("reads_secret_count", 0) == 0 + and summary.get("executes_script_count", 0) == 0 + and summary.get("executes_migration_count", 0) == 0 + and summary.get("executes_endpoint_count", 0) == 0 + and summary.get("executes_sql_count", 0) == 0 + and summary.get("writes_database_count", 0) == 0 + and summary.get("signs_database_apply_authorization_count", 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("executes_database_apply") is False + and no_execution_receipt_handoff_no_execute + and final_no_runner_execution_proof_blocks_execution + ) + checks = [ + _controlled_dry_run_no_execution_receipt_handoff_closeout_check( + "runner_invocation_boundary_closeout_ready", + boundary_closeout_ready, + { + "result": boundary_closeout_result.get("result"), + "ready_count": summary.get( + "controlled_dry_run_runner_invocation_boundary_closeout_ready_count" + ), + "pass_count": summary.get( + "controlled_dry_run_runner_invocation_boundary_closeout_pass_count" + ), + "check_count": summary.get( + "controlled_dry_run_runner_invocation_boundary_closeout_check_count" + ), + }, + "wait_for_runner_invocation_boundary_closeout_ready", + ), + _controlled_dry_run_no_execution_receipt_handoff_closeout_check( + "source_chain_ids_match", + source_chain_ids_match, + { + "runner_invocation_boundary_closeout_id": boundary_closeout.get( + "runner_invocation_boundary_closeout_id" + ), + "no_execution_receipt_handoff_id": handoff.get("handoff_id"), + "proof_id": final_no_runner_execution_proof.get("proof_id"), + }, + "wait_for_source_chain_ids_match", + ), + _controlled_dry_run_no_execution_receipt_handoff_closeout_check( + "no_execution_receipt_handoff_ready", + no_execution_receipt_handoff_ready, + { + "handoff_id": handoff.get("handoff_id"), + "handoff_status": handoff.get("handoff_status"), + "field_count": handoff.get("no_execution_receipt_handoff_field_count"), + }, + "wait_for_no_execution_receipt_handoff_ready", + ), + _controlled_dry_run_no_execution_receipt_handoff_closeout_check( + "no_execution_receipt_handoff_no_execute", + no_execution_receipt_handoff_no_execute, + { + "handoff_mode": handoff.get("handoff_mode"), + "execution_receipt_present": handoff.get("execution_receipt_present"), + "runner_invocation_allowed": handoff.get("runner_invocation_allowed"), + }, + "abort_if_no_execution_receipt_handoff_executes", + ), + _controlled_dry_run_no_execution_receipt_handoff_closeout_check( + "final_no_runner_execution_proof_bound", + final_no_runner_execution_proof_bound, + { + "proof_id": final_no_runner_execution_proof.get("proof_id"), + "source_no_execution_receipt_handoff_id": ( + final_no_runner_execution_proof.get( + "source_no_execution_receipt_handoff_id" + ) + ), + "field_count": final_no_runner_execution_proof.get( + "final_no_runner_execution_proof_field_count" + ), + }, + "wait_for_final_no_runner_execution_proof_binding", + ), + _controlled_dry_run_no_execution_receipt_handoff_closeout_check( + "final_no_runner_execution_proof_blocks_execution", + final_no_runner_execution_proof_blocks_execution, + { + "proof_mode": final_no_runner_execution_proof.get("proof_mode"), + "dry_run_executor_invoked": final_no_runner_execution_proof.get( + "dry_run_executor_invoked" + ), + "runner_invocation_performed": final_no_runner_execution_proof.get( + "runner_invocation_performed" + ), + }, + "abort_if_final_no_runner_execution_proof_reports_execution", + ), + _controlled_dry_run_no_execution_receipt_handoff_closeout_check( + "previous_closeouts_carried_forward", + previous_closeouts_carried_forward, + { + "runner_invocation_boundary_closeout_only": boundary_closeout.get( + "runner_invocation_boundary_closeout_only" + ), + "no_execution_receipt_handoff_only": boundary_closeout.get( + "no_execution_receipt_handoff_only" + ), + "handoff_mode": handoff.get("handoff_mode"), + }, + "wait_for_previous_closeouts_carry_forward", + ), + _controlled_dry_run_no_execution_receipt_handoff_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": boundary_closeout.get("target_file"), + "hash_matches": boundary_closeout.get("hash_matches"), + "expected_sha256_present": bool( + boundary_closeout.get("expected_sha256") + ), + "actual_sha256_present": bool(boundary_closeout.get("actual_sha256")), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_no_execution_receipt_handoff_closeout_check( + "rollback_and_post_apply_verifier_bound", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "database_apply_authorized": verifier_binding.get( + "database_apply_authorized" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_no_execution_receipt_handoff_closeout_check( + "runner_invocation_boundary_closeout_contract_blocks_database_apply", + boundary_contract_blocks_database_apply, + { + "permits_future_database_apply_controlled_dry_run_no_execution_receipt_handoff": ( + boundary_contract.get( + "permits_future_database_apply_controlled_dry_run_no_execution_receipt_handoff" + ) + ), + "database_apply_authorized": boundary_contract.get( + "database_apply_authorized" + ), + "writes_database": boundary_contract.get("writes_database"), + }, + "abort_if_runner_invocation_boundary_closeout_contract_authorizes_database_apply", + ), + _controlled_dry_run_no_execution_receipt_handoff_closeout_check( + "preview_has_no_side_effects_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + }, + "abort_on_preview_side_effect_execution_or_signing", + ), + _controlled_dry_run_no_execution_receipt_handoff_closeout_check( + "manual_review_not_required_for_safe_preview", + boundary_contract.get("manual_review_mode") == "exception_only" + and safety.get("manual_review_mode") == "exception_only", + { + "contract_manual_review_mode": boundary_contract.get( + "manual_review_mode" + ), + "safety_manual_review_mode": safety.get("manual_review_mode"), + }, + "keep_manual_review_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_NO_EXECUTION_RECEIPT_HANDOFF_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_RUNNER_INVOCATION_BOUNDARY_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_final_no_runner_execution_proof = { + "no_execution_receipt_handoff_closeout_id": closeout_id, + "final_no_runner_execution_proof_id": proof_id, + "source_runner_invocation_boundary_closeout_id": boundary_closeout.get( + "runner_invocation_boundary_closeout_id" + ), + "source_no_execution_receipt_handoff_id": handoff.get("handoff_id"), + "source_runner_invocation_boundary_id": boundary.get("boundary_id"), + "source_execution_preflight_guard_closeout_id": guard_closeout.get( + "execution_preflight_guard_closeout_id" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_final_no_runner_execution_proof": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_final_no_runner_execution_proof_closeout": ( + closeout_ready + ), + "no_execution_receipt_handoff_closeout_ready": closeout_ready, + "final_no_runner_execution_proof_bound": closeout_ready, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "dry_run_execution_performed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "reads_secret_in_preview": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_no_execution_receipt_handoff_closeout = { + "no_execution_receipt_handoff_closeout_id": closeout_id, + "authorization_material_type": ( + "controlled_dry_run_no_execution_receipt_handoff_closeout" + ), + "source_runner_invocation_boundary_closeout_id": boundary_closeout.get( + "runner_invocation_boundary_closeout_id" + ), + "source_no_execution_receipt_handoff_id": handoff.get("handoff_id"), + "source_runner_invocation_boundary_id": boundary.get("boundary_id"), + "source_execution_preflight_guard_closeout_id": guard_closeout.get( + "execution_preflight_guard_closeout_id" + ), + "source_execution_preflight_guard_id": execution_preflight_guard.get( + "guard_id" + ), + "required_command_shape_hash": handoff.get("required_command_shape_hash"), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_no_execution_receipt_handoff_closeout": ( + closeout_ready + ), + "no_execution_receipt_handoff_closeout_fields": handoff_closeout_fields, + "no_execution_receipt_handoff_closeout_field_count": len( + handoff_closeout_fields + ), + "no_execution_receipt_handoff_closeout_acceptance_gates": ( + handoff_closeout_acceptance_gates + ), + "no_execution_receipt_handoff_closeout_acceptance_gate_count": len( + handoff_closeout_acceptance_gates + ), + "final_no_runner_execution_proof": final_no_runner_execution_proof, + "final_no_runner_execution_proof_count": 1, + "final_no_runner_execution_proof_field_count": len( + final_no_runner_execution_proof_fields + ), + "no_execution_receipt_handoff": handoff, + "no_execution_receipt_handoff_count": 1, + "runner_invocation_boundary_closeout": boundary_closeout, + "runner_invocation_boundary_closeout_count": 1, + "runner_invocation_boundary": boundary, + "runner_invocation_boundary_count": 1, + "execution_preflight_guard_closeout": guard_closeout, + "execution_preflight_guard_closeout_count": 1, + "execution_preflight_guard": execution_preflight_guard, + "execution_preflight_guard_count": 1, + "no_write_invocation_package_closeout": package_closeout, + "no_write_invocation_package_closeout_count": 1, + "no_write_invocation_package": package, + "no_write_invocation_package_count": 1, + "invocation_receipt_closeout": invocation_closeout, + "invocation_receipt_closeout_count": 1, + "dry_run_invocation_readiness_receipt": receipt, + "dry_run_invocation_readiness_receipt_count": 1, + "apply_executor_readiness_closeout": readiness_closeout, + "apply_executor_readiness_closeout_count": 1, + "apply_executor_readiness_contract": readiness_contract, + "apply_executor_readiness_contract_count": 1, + "pre_apply_replay_closeout": pre_apply_closeout, + "pre_apply_replay_closeout_count": 1, + "pre_apply_replay_verifier": replay_verifier, + "pre_apply_replay_verifier_count": 1, + "final_dry_run_executor_guard": final_guard, + "final_dry_run_executor_guard_count": 1, + "no_apply_enforcement_verification": no_apply_enforcement, + "no_apply_enforcement_verification_count": 1, + "target_file": boundary_closeout.get("target_file"), + "expected_sha256": boundary_closeout.get("expected_sha256"), + "actual_sha256": boundary_closeout.get("actual_sha256"), + "hash_matches": boundary_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "no_execution_receipt_handoff_closeout_only": True, + "final_no_runner_execution_proof_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + } + controlled_dry_run_no_execution_receipt_handoff_closeout_contract = { + "mode": "controlled_dry_run_no_execution_receipt_handoff_closeout_and_final_no_runner_execution_proof_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-no-execution-receipt-handoff-closeout" + ), + "source_runner_invocation_boundary_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-runner-invocation-boundary-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_final_no_runner_execution_proof": ( + closeout_ready + ), + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": False, + "signs_database_apply_authorization": False, + "writes_database": False, + "executes_in_preview": False, + "secret_material_required_in_preview": False, + "manual_review_mode": "exception_only", + } + output_summary = dict(summary) + output_summary.update( + { + "controlled_dry_run_no_execution_receipt_handoff_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_no_execution_receipt_handoff_closeout_check_count": len( + checks + ), + "controlled_dry_run_no_execution_receipt_handoff_closeout_pass_count": ( + passed_count + ), + "controlled_dry_run_no_execution_receipt_handoff_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_no_execution_receipt_handoff_closeout_count": 1, + "controlled_dry_run_no_execution_receipt_handoff_closeout_field_count": len( + handoff_closeout_fields + ), + "controlled_dry_run_no_execution_receipt_handoff_closeout_acceptance_gate_count": len( + handoff_closeout_acceptance_gates + ), + "final_no_runner_execution_proof_count": 1, + "final_no_runner_execution_proof_field_count": len( + final_no_runner_execution_proof_fields + ), + "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, + "dry_run_executor_invoked_count": 0, + "runner_invocation_performed_count": 0, + "endpoint_executed_count": 0, + "sql_executed_count": 0, + "database_written_count": 0, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + } + ) + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_NO_EXECUTION_RECEIPT_HANDOFF_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(boundary_closeout_result.get("success")), + "generated_at": boundary_closeout_result.get("generated_at"), + "source_policy": boundary_closeout_result.get("policy"), + "stats": boundary_closeout_result.get("stats") or {}, + "summary": output_summary, + "future_database_apply_controlled_dry_run_final_no_runner_execution_proof": ( + future_database_apply_controlled_dry_run_final_no_runner_execution_proof + ), + "controlled_dry_run_no_execution_receipt_handoff_closeout": ( + controlled_dry_run_no_execution_receipt_handoff_closeout + ), + "controlled_dry_run_no_execution_receipt_handoff_closeout_contract": ( + controlled_dry_run_no_execution_receipt_handoff_closeout_contract + ), + "controlled_dry_run_no_execution_receipt_handoff_closeout_checks": checks, + "source_controlled_dry_run_runner_invocation_boundary_closeout_summary": ( + summary + ), + "source_controlled_dry_run_runner_invocation_boundary_closeout_contract": ( + boundary_contract + ), + "source_controlled_dry_run_runner_invocation_boundary_closeout": ( + boundary_closeout + ), + "source_database_apply_controlled_dry_run_no_execution_receipt_handoff": ( + future_handoff + ), + "safety": { + "read_only_db_apply_controlled_dry_run_no_execution_receipt_handoff_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future final no-runner-execution proof closeout.", + "Keep actual runner invocation disabled; this proof confirms no execution happened in this preview lane.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_final_no_runner_execution_proof_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out final no-runner proof and bind controlled executor quarantine.""" + proof_closeout_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_no_execution_receipt_handoff_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_proof = ( + proof_closeout_result.get( + "future_database_apply_controlled_dry_run_final_no_runner_execution_proof" + ) + or {} + ) + handoff_closeout = ( + proof_closeout_result.get( + "controlled_dry_run_no_execution_receipt_handoff_closeout" + ) + or {} + ) + handoff_contract = ( + proof_closeout_result.get( + "controlled_dry_run_no_execution_receipt_handoff_closeout_contract" + ) + or {} + ) + summary = proof_closeout_result.get("summary") or {} + safety = proof_closeout_result.get("safety") or {} + final_proof = handoff_closeout.get("final_no_runner_execution_proof") or {} + handoff = handoff_closeout.get("no_execution_receipt_handoff") or {} + boundary_closeout = handoff_closeout.get("runner_invocation_boundary_closeout") or {} + boundary = handoff_closeout.get("runner_invocation_boundary") or {} + guard_closeout = handoff_closeout.get("execution_preflight_guard_closeout") or {} + execution_preflight_guard = handoff_closeout.get("execution_preflight_guard") or {} + package_closeout = handoff_closeout.get("no_write_invocation_package_closeout") or {} + package = handoff_closeout.get("no_write_invocation_package") or {} + invocation_closeout = handoff_closeout.get("invocation_receipt_closeout") or {} + receipt = handoff_closeout.get("dry_run_invocation_readiness_receipt") or {} + readiness_closeout = handoff_closeout.get("apply_executor_readiness_closeout") or {} + readiness_contract = handoff_closeout.get("apply_executor_readiness_contract") or {} + pre_apply_closeout = handoff_closeout.get("pre_apply_replay_closeout") or {} + replay_verifier = handoff_closeout.get("pre_apply_replay_verifier") or {} + final_guard = handoff_closeout.get("final_dry_run_executor_guard") or {} + no_apply_enforcement = ( + handoff_closeout.get("no_apply_enforcement_verification") or {} + ) + rollback_binding = handoff_closeout.get("rollback_binding") or {} + verifier_binding = handoff_closeout.get("post_apply_verifier_binding") or {} + closeout_id = ( + _db_apply_controlled_dry_run_final_no_runner_execution_proof_closeout_id( + proof_closeout_result + ) + ) + quarantine_id = f"{closeout_id}-controlled-executor-quarantine-proof" + proof_closeout_fields = [ + "final_no_runner_execution_proof_closeout_id", + "source_no_execution_receipt_handoff_closeout_id", + "source_final_no_runner_execution_proof_id", + "source_no_execution_receipt_handoff_id", + "source_runner_invocation_boundary_closeout_id", + "controlled_executor_quarantine_proof_id", + "required_command_shape_hash", + "target_migration_file", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "dry_run_executor_invocation_allowed", + "abort_conditions", + ] + proof_closeout_acceptance_gates = [ + "no_execution_receipt_handoff_closeout_ready", + "source_chain_ids_match", + "final_no_runner_execution_proof_ready", + "final_no_runner_execution_proof_no_execute", + "controlled_executor_quarantine_proof_bound", + "controlled_executor_quarantine_proof_blocks_execution", + "previous_closeouts_carried_forward", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "no_secret_signature_or_database_apply", + ] + controlled_executor_quarantine_proof_fields = [ + "quarantine_proof_id", + "source_final_no_runner_execution_proof_closeout_id", + "source_final_no_runner_execution_proof_id", + "source_no_execution_receipt_handoff_closeout_id", + "source_no_execution_receipt_handoff_id", + "source_runner_invocation_boundary_closeout_id", + "quarantine_mode", + "dry_run_executor_invocation_allowed", + "runner_invocation_allowed", + "endpoint_execution_allowed", + "sql_execution_allowed", + "database_apply_authorized", + ] + abort_conditions = [ + "abort_if_no_execution_receipt_handoff_closeout_not_ready", + "abort_if_source_chain_ids_do_not_match", + "abort_if_final_no_runner_execution_proof_missing", + "abort_if_final_no_runner_execution_proof_reports_execution", + "abort_if_controlled_executor_quarantine_proof_missing", + "abort_if_controlled_executor_quarantine_proof_allows_execution", + "abort_if_endpoint_or_sql_execution_is_allowed", + "abort_if_database_write_or_apply_is_allowed", + "abort_if_target_migration_hash_changes", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_any_secret_signature_or_apply_material_is_present", + ] + controlled_executor_quarantine_proof = { + "quarantine_proof_id": quarantine_id, + "source_final_no_runner_execution_proof_closeout_id": closeout_id, + "source_final_no_runner_execution_proof_id": final_proof.get("proof_id"), + "source_no_execution_receipt_handoff_closeout_id": handoff_closeout.get( + "no_execution_receipt_handoff_closeout_id" + ), + "source_no_execution_receipt_handoff_id": handoff.get("handoff_id"), + "source_runner_invocation_boundary_closeout_id": boundary_closeout.get( + "runner_invocation_boundary_closeout_id" + ), + "source_runner_invocation_boundary_id": boundary.get("boundary_id"), + "source_execution_preflight_guard_closeout_id": guard_closeout.get( + "execution_preflight_guard_closeout_id" + ), + "required_proof_mode": "final_no_runner_execution_proof_preview_only", + "required_command_shape_hash": final_proof.get("required_command_shape_hash"), + "quarantine_status": "controlled_executor_quarantine_proof_preview_ready", + "quarantine_mode": "controlled_executor_quarantine_proof_preview_only", + "controlled_executor_quarantine_bound": True, + "executor_quarantine_enforced": True, + "execution_receipt_present": False, + "execution_receipt_required": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "ready_for_controlled_executor_quarantine_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "reads_secret_in_preview": False, + "controlled_executor_quarantine_proof_field_count": len( + controlled_executor_quarantine_proof_fields + ), + "controlled_executor_quarantine_proof_fields": ( + controlled_executor_quarantine_proof_fields + ), + } + handoff_closeout_ready = ( + proof_closeout_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_NO_EXECUTION_RECEIPT_HANDOFF_CLOSEOUT_READY" + and summary.get( + "controlled_dry_run_no_execution_receipt_handoff_closeout_ready_count" + ) + == 1 + and summary.get( + "controlled_dry_run_no_execution_receipt_handoff_closeout_pass_count" + ) + == summary.get( + "controlled_dry_run_no_execution_receipt_handoff_closeout_check_count" + ) + ) + source_chain_ids_match = ( + bool(handoff_closeout.get("no_execution_receipt_handoff_closeout_id")) + and handoff_closeout.get("no_execution_receipt_handoff_closeout_id") + == future_proof.get("no_execution_receipt_handoff_closeout_id") + == final_proof.get("source_no_execution_receipt_handoff_closeout_id") + == controlled_executor_quarantine_proof.get( + "source_no_execution_receipt_handoff_closeout_id" + ) + and final_proof.get("proof_id") + == future_proof.get("final_no_runner_execution_proof_id") + == controlled_executor_quarantine_proof.get( + "source_final_no_runner_execution_proof_id" + ) + and handoff.get("handoff_id") + == future_proof.get("source_no_execution_receipt_handoff_id") + == final_proof.get("source_no_execution_receipt_handoff_id") + == controlled_executor_quarantine_proof.get( + "source_no_execution_receipt_handoff_id" + ) + and boundary_closeout.get("runner_invocation_boundary_closeout_id") + == future_proof.get("source_runner_invocation_boundary_closeout_id") + == final_proof.get("source_runner_invocation_boundary_closeout_id") + == controlled_executor_quarantine_proof.get( + "source_runner_invocation_boundary_closeout_id" + ) + and boundary.get("boundary_id") + == future_proof.get("source_runner_invocation_boundary_id") + == controlled_executor_quarantine_proof.get( + "source_runner_invocation_boundary_id" + ) + ) + final_no_runner_execution_proof_ready = ( + final_proof.get("proof_status") + == "final_no_runner_execution_proof_preview_ready" + and final_proof.get("proof_id") + == future_proof.get("final_no_runner_execution_proof_id") + and int(final_proof.get("final_no_runner_execution_proof_field_count") or 0) + == 12 + and summary.get("final_no_runner_execution_proof_count") == 1 + ) + final_no_runner_execution_proof_no_execute = ( + final_proof.get("proof_mode") + == "final_no_runner_execution_proof_preview_only" + and final_proof.get("execution_receipt_present") is False + and final_proof.get("execution_receipt_required") is False + and final_proof.get("dry_run_executor_invoked") is False + and final_proof.get("runner_invocation_performed") is False + and final_proof.get("endpoint_executed") is False + and final_proof.get("sql_executed") is False + and final_proof.get("database_written") is False + and final_proof.get("dry_run_executor_invocation_allowed") is False + and final_proof.get("runner_invocation_allowed") is False + and final_proof.get("ready_for_final_no_runner_execution_proof_now") + is False + and final_proof.get("ready_for_dry_run_executor_invocation_now") is False + and final_proof.get("ready_for_actual_dry_run_execution_now") is False + and final_proof.get("endpoint_execution_allowed") is False + and final_proof.get("sql_execution_allowed") is False + and final_proof.get("database_write_allowed") is False + and final_proof.get("database_apply_authorized") is False + and final_proof.get("executes_database_apply") is False + and final_proof.get("executes_endpoint") is False + and final_proof.get("executes_sql") is False + and final_proof.get("writes_database") is False + and final_proof.get("captures_stdout") is False + and final_proof.get("captures_stderr") is False + and final_proof.get("stdout_included") is False + and final_proof.get("stderr_included") is False + ) + controlled_executor_quarantine_proof_bound = ( + bool(controlled_executor_quarantine_proof.get("quarantine_proof_id")) + and controlled_executor_quarantine_proof.get( + "source_final_no_runner_execution_proof_closeout_id" + ) + == closeout_id + and controlled_executor_quarantine_proof.get( + "source_final_no_runner_execution_proof_id" + ) + == final_proof.get("proof_id") + and controlled_executor_quarantine_proof.get("required_command_shape_hash") + == final_proof.get("required_command_shape_hash") + and int( + controlled_executor_quarantine_proof.get( + "controlled_executor_quarantine_proof_field_count" + ) + or 0 + ) + == len(controlled_executor_quarantine_proof_fields) + ) + controlled_executor_quarantine_proof_blocks_execution = ( + controlled_executor_quarantine_proof.get("quarantine_mode") + == "controlled_executor_quarantine_proof_preview_only" + and controlled_executor_quarantine_proof.get( + "controlled_executor_quarantine_bound" + ) + is True + and controlled_executor_quarantine_proof.get("executor_quarantine_enforced") + is True + and controlled_executor_quarantine_proof.get("execution_receipt_present") + is False + and controlled_executor_quarantine_proof.get("execution_receipt_required") + is False + and controlled_executor_quarantine_proof.get("dry_run_executor_invoked") + is False + and controlled_executor_quarantine_proof.get("runner_invocation_performed") + is False + and controlled_executor_quarantine_proof.get("endpoint_executed") is False + and controlled_executor_quarantine_proof.get("sql_executed") is False + and controlled_executor_quarantine_proof.get("database_written") is False + and controlled_executor_quarantine_proof.get( + "dry_run_executor_invocation_allowed" + ) + is False + and controlled_executor_quarantine_proof.get("runner_invocation_allowed") + is False + and controlled_executor_quarantine_proof.get( + "ready_for_dry_run_executor_invocation_now" + ) + is False + and controlled_executor_quarantine_proof.get( + "ready_for_actual_dry_run_execution_now" + ) + is False + and controlled_executor_quarantine_proof.get("endpoint_execution_allowed") + is False + and controlled_executor_quarantine_proof.get("sql_execution_allowed") is False + and controlled_executor_quarantine_proof.get("database_write_allowed") is False + and controlled_executor_quarantine_proof.get("database_apply_authorized") + is False + and controlled_executor_quarantine_proof.get("executes_database_apply") + is False + and controlled_executor_quarantine_proof.get("executes_endpoint") is False + and controlled_executor_quarantine_proof.get("executes_sql") is False + and controlled_executor_quarantine_proof.get("writes_database") is False + and controlled_executor_quarantine_proof.get("captures_stdout") is False + and controlled_executor_quarantine_proof.get("captures_stderr") is False + and controlled_executor_quarantine_proof.get("stdout_included") is False + and controlled_executor_quarantine_proof.get("stderr_included") is False + ) + previous_closeouts_carried_forward = ( + handoff_closeout.get("no_execution_receipt_handoff_closeout_only") is True + and handoff_closeout.get("final_no_runner_execution_proof_only") is True + and handoff_closeout.get("database_apply_authorized") is False + and boundary_closeout.get("runner_invocation_boundary_closeout_only") is True + and boundary_closeout.get("no_execution_receipt_handoff_only") is True + and guard_closeout.get("execution_preflight_guard_closeout_only") is True + and package_closeout.get("no_write_invocation_package_closeout_only") + is True + and package.get("package_mode") == "no_write_invocation_package_preview_only" + and invocation_closeout.get("invocation_receipt_closeout_only") is True + and receipt.get("receipt_mode") == "dry_run_invocation_readiness_preview_only" + and readiness_contract.get("readiness_mode") + == "apply_executor_readiness_contract_preview_only" + and replay_verifier.get("replay_mode") == "pre_apply_replay_preview_only" + and final_guard.get("guard_status") + == "final_dry_run_executor_guard_preview_ready" + and no_apply_enforcement.get("enforcement_status") + == "no_apply_enforcement_preview_ready" + and final_no_runner_execution_proof_no_execute + ) + target_hash_locked = ( + handoff_closeout.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(handoff_closeout.get("expected_sha256")) + and bool(handoff_closeout.get("actual_sha256")) + and handoff_closeout.get("expected_sha256") + == handoff_closeout.get("actual_sha256") + and handoff_closeout.get("hash_matches") is True + and handoff_closeout.get("target_migration_hash_locked") is True + ) + rollback_and_verifier_bound = ( + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False + ) + handoff_contract_blocks_database_apply = ( + handoff_contract.get("executes_database_apply") is False + and handoff_contract.get("executes_endpoint") is False + and handoff_contract.get("executes_sql") is False + and handoff_contract.get("database_apply_authorized") is False + and handoff_contract.get("ready_for_database_apply_now") is False + and handoff_contract.get("ready_for_dry_run_executor_invocation_now") + is False + and handoff_contract.get("ready_for_actual_dry_run_execution_now") + is False + and handoff_contract.get("signs_database_apply_authorization") is False + and handoff_contract.get("writes_database") is False + and handoff_contract.get("executes_in_preview") is False + ) + side_effect_free = ( + summary.get("reads_secret_count", 0) == 0 + and summary.get("executes_script_count", 0) == 0 + and summary.get("executes_migration_count", 0) == 0 + and summary.get("executes_endpoint_count", 0) == 0 + and summary.get("executes_sql_count", 0) == 0 + and summary.get("writes_database_count", 0) == 0 + and summary.get("signs_database_apply_authorization_count", 0) == 0 + and summary.get("dry_run_executor_invoked_count", 0) == 0 + and summary.get("runner_invocation_performed_count", 0) == 0 + and summary.get("endpoint_executed_count", 0) == 0 + and summary.get("sql_executed_count", 0) == 0 + and summary.get("database_written_count", 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("executes_database_apply") is False + and final_no_runner_execution_proof_no_execute + and controlled_executor_quarantine_proof_blocks_execution + ) + checks = [ + _controlled_dry_run_final_no_runner_execution_proof_closeout_check( + "no_execution_receipt_handoff_closeout_ready", + handoff_closeout_ready, + { + "result": proof_closeout_result.get("result"), + "ready_count": summary.get( + "controlled_dry_run_no_execution_receipt_handoff_closeout_ready_count" + ), + "pass_count": summary.get( + "controlled_dry_run_no_execution_receipt_handoff_closeout_pass_count" + ), + "check_count": summary.get( + "controlled_dry_run_no_execution_receipt_handoff_closeout_check_count" + ), + }, + "wait_for_no_execution_receipt_handoff_closeout_ready", + ), + _controlled_dry_run_final_no_runner_execution_proof_closeout_check( + "source_chain_ids_match", + source_chain_ids_match, + { + "no_execution_receipt_handoff_closeout_id": handoff_closeout.get( + "no_execution_receipt_handoff_closeout_id" + ), + "final_no_runner_execution_proof_id": final_proof.get("proof_id"), + "quarantine_proof_id": controlled_executor_quarantine_proof.get( + "quarantine_proof_id" + ), + }, + "wait_for_source_chain_ids_match", + ), + _controlled_dry_run_final_no_runner_execution_proof_closeout_check( + "final_no_runner_execution_proof_ready", + final_no_runner_execution_proof_ready, + { + "proof_id": final_proof.get("proof_id"), + "proof_status": final_proof.get("proof_status"), + "field_count": final_proof.get( + "final_no_runner_execution_proof_field_count" + ), + }, + "wait_for_final_no_runner_execution_proof_ready", + ), + _controlled_dry_run_final_no_runner_execution_proof_closeout_check( + "final_no_runner_execution_proof_no_execute", + final_no_runner_execution_proof_no_execute, + { + "proof_mode": final_proof.get("proof_mode"), + "dry_run_executor_invoked": final_proof.get( + "dry_run_executor_invoked" + ), + "runner_invocation_performed": final_proof.get( + "runner_invocation_performed" + ), + }, + "abort_if_final_no_runner_execution_proof_reports_execution", + ), + _controlled_dry_run_final_no_runner_execution_proof_closeout_check( + "controlled_executor_quarantine_proof_bound", + controlled_executor_quarantine_proof_bound, + { + "quarantine_proof_id": controlled_executor_quarantine_proof.get( + "quarantine_proof_id" + ), + "source_final_no_runner_execution_proof_id": ( + controlled_executor_quarantine_proof.get( + "source_final_no_runner_execution_proof_id" + ) + ), + "field_count": controlled_executor_quarantine_proof.get( + "controlled_executor_quarantine_proof_field_count" + ), + }, + "wait_for_controlled_executor_quarantine_proof_binding", + ), + _controlled_dry_run_final_no_runner_execution_proof_closeout_check( + "controlled_executor_quarantine_proof_blocks_execution", + controlled_executor_quarantine_proof_blocks_execution, + { + "quarantine_mode": controlled_executor_quarantine_proof.get( + "quarantine_mode" + ), + "dry_run_executor_invoked": controlled_executor_quarantine_proof.get( + "dry_run_executor_invoked" + ), + "runner_invocation_performed": controlled_executor_quarantine_proof.get( + "runner_invocation_performed" + ), + }, + "abort_if_controlled_executor_quarantine_proof_allows_execution", + ), + _controlled_dry_run_final_no_runner_execution_proof_closeout_check( + "previous_closeouts_carried_forward", + previous_closeouts_carried_forward, + { + "no_execution_receipt_handoff_closeout_only": handoff_closeout.get( + "no_execution_receipt_handoff_closeout_only" + ), + "final_no_runner_execution_proof_only": handoff_closeout.get( + "final_no_runner_execution_proof_only" + ), + "proof_mode": final_proof.get("proof_mode"), + }, + "wait_for_previous_closeouts_carry_forward", + ), + _controlled_dry_run_final_no_runner_execution_proof_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": handoff_closeout.get("target_file"), + "hash_matches": handoff_closeout.get("hash_matches"), + "expected_sha256_present": bool(handoff_closeout.get("expected_sha256")), + "actual_sha256_present": bool(handoff_closeout.get("actual_sha256")), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_final_no_runner_execution_proof_closeout_check( + "rollback_and_post_apply_verifier_bound", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "database_apply_authorized": verifier_binding.get( + "database_apply_authorized" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_final_no_runner_execution_proof_closeout_check( + "no_execution_receipt_handoff_closeout_contract_blocks_database_apply", + handoff_contract_blocks_database_apply, + { + "permits_future_database_apply_controlled_dry_run_final_no_runner_execution_proof": ( + handoff_contract.get( + "permits_future_database_apply_controlled_dry_run_final_no_runner_execution_proof" + ) + ), + "database_apply_authorized": handoff_contract.get( + "database_apply_authorized" + ), + "writes_database": handoff_contract.get("writes_database"), + }, + "abort_if_no_execution_receipt_handoff_closeout_contract_authorizes_database_apply", + ), + _controlled_dry_run_final_no_runner_execution_proof_closeout_check( + "preview_has_no_side_effects_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + "dry_run_executor_invoked_count": summary.get( + "dry_run_executor_invoked_count", 0 + ), + }, + "abort_on_preview_side_effect_execution_or_signing", + ), + _controlled_dry_run_final_no_runner_execution_proof_closeout_check( + "manual_review_not_required_for_safe_preview", + handoff_contract.get("manual_review_mode") == "exception_only" + and safety.get("manual_review_mode") == "exception_only", + { + "contract_manual_review_mode": handoff_contract.get( + "manual_review_mode" + ), + "safety_manual_review_mode": safety.get("manual_review_mode"), + }, + "keep_manual_review_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_FINAL_NO_RUNNER_EXECUTION_PROOF_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_NO_EXECUTION_RECEIPT_HANDOFF_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_controlled_executor_quarantine_proof = { + "final_no_runner_execution_proof_closeout_id": closeout_id, + "controlled_executor_quarantine_proof_id": quarantine_id, + "source_no_execution_receipt_handoff_closeout_id": handoff_closeout.get( + "no_execution_receipt_handoff_closeout_id" + ), + "source_final_no_runner_execution_proof_id": final_proof.get("proof_id"), + "source_no_execution_receipt_handoff_id": handoff.get("handoff_id"), + "source_runner_invocation_boundary_closeout_id": boundary_closeout.get( + "runner_invocation_boundary_closeout_id" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_controlled_executor_quarantine_proof": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_controlled_executor_quarantine_proof_closeout": ( + closeout_ready + ), + "final_no_runner_execution_proof_closeout_ready": closeout_ready, + "controlled_executor_quarantine_proof_bound": closeout_ready, + "controlled_executor_quarantine_bound": True, + "executor_quarantine_enforced": True, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "dry_run_execution_performed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "reads_secret_in_preview": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_final_no_runner_execution_proof_closeout = { + "final_no_runner_execution_proof_closeout_id": closeout_id, + "authorization_material_type": ( + "controlled_dry_run_final_no_runner_execution_proof_closeout" + ), + "source_no_execution_receipt_handoff_closeout_id": handoff_closeout.get( + "no_execution_receipt_handoff_closeout_id" + ), + "source_final_no_runner_execution_proof_id": final_proof.get("proof_id"), + "source_no_execution_receipt_handoff_id": handoff.get("handoff_id"), + "source_runner_invocation_boundary_closeout_id": boundary_closeout.get( + "runner_invocation_boundary_closeout_id" + ), + "source_runner_invocation_boundary_id": boundary.get("boundary_id"), + "source_execution_preflight_guard_closeout_id": guard_closeout.get( + "execution_preflight_guard_closeout_id" + ), + "required_command_shape_hash": final_proof.get("required_command_shape_hash"), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_final_no_runner_execution_proof_closeout": ( + closeout_ready + ), + "final_no_runner_execution_proof_closeout_fields": proof_closeout_fields, + "final_no_runner_execution_proof_closeout_field_count": len( + proof_closeout_fields + ), + "final_no_runner_execution_proof_closeout_acceptance_gates": ( + proof_closeout_acceptance_gates + ), + "final_no_runner_execution_proof_closeout_acceptance_gate_count": len( + proof_closeout_acceptance_gates + ), + "controlled_executor_quarantine_proof": controlled_executor_quarantine_proof, + "controlled_executor_quarantine_proof_count": 1, + "controlled_executor_quarantine_proof_field_count": len( + controlled_executor_quarantine_proof_fields + ), + "final_no_runner_execution_proof": final_proof, + "final_no_runner_execution_proof_count": 1, + "no_execution_receipt_handoff_closeout": handoff_closeout, + "no_execution_receipt_handoff_closeout_count": 1, + "no_execution_receipt_handoff": handoff, + "no_execution_receipt_handoff_count": 1, + "runner_invocation_boundary_closeout": boundary_closeout, + "runner_invocation_boundary_closeout_count": 1, + "runner_invocation_boundary": boundary, + "runner_invocation_boundary_count": 1, + "execution_preflight_guard_closeout": guard_closeout, + "execution_preflight_guard_closeout_count": 1, + "execution_preflight_guard": execution_preflight_guard, + "execution_preflight_guard_count": 1, + "no_write_invocation_package_closeout": package_closeout, + "no_write_invocation_package_closeout_count": 1, + "no_write_invocation_package": package, + "no_write_invocation_package_count": 1, + "invocation_receipt_closeout": invocation_closeout, + "invocation_receipt_closeout_count": 1, + "dry_run_invocation_readiness_receipt": receipt, + "dry_run_invocation_readiness_receipt_count": 1, + "apply_executor_readiness_closeout": readiness_closeout, + "apply_executor_readiness_closeout_count": 1, + "apply_executor_readiness_contract": readiness_contract, + "apply_executor_readiness_contract_count": 1, + "pre_apply_replay_closeout": pre_apply_closeout, + "pre_apply_replay_closeout_count": 1, + "pre_apply_replay_verifier": replay_verifier, + "pre_apply_replay_verifier_count": 1, + "final_dry_run_executor_guard": final_guard, + "final_dry_run_executor_guard_count": 1, + "no_apply_enforcement_verification": no_apply_enforcement, + "no_apply_enforcement_verification_count": 1, + "target_file": handoff_closeout.get("target_file"), + "expected_sha256": handoff_closeout.get("expected_sha256"), + "actual_sha256": handoff_closeout.get("actual_sha256"), + "hash_matches": handoff_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "final_no_runner_execution_proof_closeout_only": True, + "controlled_executor_quarantine_proof_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "controlled_executor_quarantine_bound": True, + "executor_quarantine_enforced": True, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + } + controlled_dry_run_final_no_runner_execution_proof_closeout_contract = { + "mode": "controlled_dry_run_final_no_runner_execution_proof_closeout_and_controlled_executor_quarantine_proof_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-final-no-runner-execution-proof-closeout" + ), + "source_no_execution_receipt_handoff_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-no-execution-receipt-handoff-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_controlled_executor_quarantine_proof": ( + closeout_ready + ), + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": False, + "signs_database_apply_authorization": False, + "writes_database": False, + "executes_in_preview": False, + "secret_material_required_in_preview": False, + "manual_review_mode": "exception_only", + } + output_summary = dict(summary) + output_summary.update( + { + "controlled_dry_run_final_no_runner_execution_proof_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_final_no_runner_execution_proof_closeout_check_count": len( + checks + ), + "controlled_dry_run_final_no_runner_execution_proof_closeout_pass_count": ( + passed_count + ), + "controlled_dry_run_final_no_runner_execution_proof_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_final_no_runner_execution_proof_closeout_count": 1, + "controlled_dry_run_final_no_runner_execution_proof_closeout_field_count": len( + proof_closeout_fields + ), + "controlled_dry_run_final_no_runner_execution_proof_closeout_acceptance_gate_count": len( + proof_closeout_acceptance_gates + ), + "controlled_executor_quarantine_proof_count": 1, + "controlled_executor_quarantine_proof_field_count": len( + controlled_executor_quarantine_proof_fields + ), + "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, + "dry_run_executor_invoked_count": 0, + "runner_invocation_performed_count": 0, + "endpoint_executed_count": 0, + "sql_executed_count": 0, + "database_written_count": 0, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + } + ) + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_FINAL_NO_RUNNER_EXECUTION_PROOF_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(proof_closeout_result.get("success")), + "generated_at": proof_closeout_result.get("generated_at"), + "source_policy": proof_closeout_result.get("policy"), + "stats": proof_closeout_result.get("stats") or {}, + "summary": output_summary, + "future_database_apply_controlled_dry_run_controlled_executor_quarantine_proof": ( + future_database_apply_controlled_dry_run_controlled_executor_quarantine_proof + ), + "controlled_dry_run_final_no_runner_execution_proof_closeout": ( + controlled_dry_run_final_no_runner_execution_proof_closeout + ), + "controlled_dry_run_final_no_runner_execution_proof_closeout_contract": ( + controlled_dry_run_final_no_runner_execution_proof_closeout_contract + ), + "controlled_dry_run_final_no_runner_execution_proof_closeout_checks": checks, + "source_controlled_dry_run_no_execution_receipt_handoff_closeout_summary": ( + summary + ), + "source_controlled_dry_run_no_execution_receipt_handoff_closeout_contract": ( + handoff_contract + ), + "source_controlled_dry_run_no_execution_receipt_handoff_closeout": ( + handoff_closeout + ), + "source_database_apply_controlled_dry_run_final_no_runner_execution_proof": ( + future_proof + ), + "safety": { + "read_only_db_apply_controlled_dry_run_final_no_runner_execution_proof_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future controlled executor quarantine proof closeout.", + "Keep actual dry-run executor invocation disabled; this proof confirms executor quarantine in this preview lane.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_controlled_executor_quarantine_proof_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out executor quarantine and freeze the dry-run execution envelope.""" + quarantine_closeout_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_final_no_runner_execution_proof_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_quarantine = ( + quarantine_closeout_result.get( + "future_database_apply_controlled_dry_run_controlled_executor_quarantine_proof" + ) + or {} + ) + quarantine_closeout = ( + quarantine_closeout_result.get( + "controlled_dry_run_final_no_runner_execution_proof_closeout" + ) + or {} + ) + quarantine_contract = ( + quarantine_closeout_result.get( + "controlled_dry_run_final_no_runner_execution_proof_closeout_contract" + ) + or {} + ) + summary = quarantine_closeout_result.get("summary") or {} + safety = quarantine_closeout_result.get("safety") or {} + quarantine = quarantine_closeout.get("controlled_executor_quarantine_proof") or {} + final_proof = quarantine_closeout.get("final_no_runner_execution_proof") or {} + handoff_closeout = ( + quarantine_closeout.get("no_execution_receipt_handoff_closeout") or {} + ) + handoff = quarantine_closeout.get("no_execution_receipt_handoff") or {} + boundary_closeout = ( + quarantine_closeout.get("runner_invocation_boundary_closeout") or {} + ) + boundary = quarantine_closeout.get("runner_invocation_boundary") or {} + guard_closeout = ( + quarantine_closeout.get("execution_preflight_guard_closeout") or {} + ) + package_closeout = ( + quarantine_closeout.get("no_write_invocation_package_closeout") or {} + ) + invocation_closeout = quarantine_closeout.get("invocation_receipt_closeout") or {} + readiness_closeout = ( + quarantine_closeout.get("apply_executor_readiness_closeout") or {} + ) + pre_apply_closeout = quarantine_closeout.get("pre_apply_replay_closeout") or {} + final_guard = quarantine_closeout.get("final_dry_run_executor_guard") or {} + no_apply_enforcement = ( + quarantine_closeout.get("no_apply_enforcement_verification") or {} + ) + rollback_binding = quarantine_closeout.get("rollback_binding") or {} + verifier_binding = quarantine_closeout.get("post_apply_verifier_binding") or {} + source_closeout_id = quarantine_closeout.get( + "final_no_runner_execution_proof_closeout_id" + ) + closeout_id = ( + _db_apply_controlled_dry_run_controlled_executor_quarantine_proof_closeout_id( + quarantine_closeout_result + ) + ) + freeze_id = f"{closeout_id}-dry-run-execution-envelope-freeze-proof" + quarantine_closeout_fields = [ + "controlled_executor_quarantine_proof_closeout_id", + "source_final_no_runner_execution_proof_closeout_id", + "source_controlled_executor_quarantine_proof_id", + "source_final_no_runner_execution_proof_id", + "dry_run_execution_envelope_freeze_proof_id", + "required_command_shape_hash", + "target_migration_file", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "execution_envelope_frozen", + "dry_run_executor_invocation_allowed", + "abort_conditions", + ] + quarantine_closeout_acceptance_gates = [ + "final_no_runner_execution_proof_closeout_ready", + "source_chain_ids_match", + "controlled_executor_quarantine_proof_ready", + "controlled_executor_quarantine_proof_no_execute", + "dry_run_execution_envelope_freeze_proof_bound", + "dry_run_execution_envelope_freeze_proof_blocks_execution", + "previous_closeouts_carried_forward", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "no_secret_signature_or_database_apply", + ] + dry_run_execution_envelope_freeze_proof_fields = [ + "freeze_proof_id", + "source_controlled_executor_quarantine_proof_closeout_id", + "source_controlled_executor_quarantine_proof_id", + "source_final_no_runner_execution_proof_closeout_id", + "source_final_no_runner_execution_proof_id", + "source_no_execution_receipt_handoff_closeout_id", + "freeze_mode", + "execution_envelope_frozen", + "dry_run_executor_invocation_allowed", + "endpoint_execution_allowed", + "sql_execution_allowed", + "database_apply_authorized", + ] + abort_conditions = [ + "abort_if_final_no_runner_execution_proof_closeout_not_ready", + "abort_if_source_chain_ids_do_not_match", + "abort_if_controlled_executor_quarantine_proof_missing", + "abort_if_controlled_executor_quarantine_proof_reports_execution", + "abort_if_dry_run_execution_envelope_freeze_proof_missing", + "abort_if_execution_envelope_is_mutable_or_allows_execution", + "abort_if_endpoint_or_sql_execution_is_allowed", + "abort_if_database_write_or_apply_is_allowed", + "abort_if_target_migration_hash_changes", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_any_secret_signature_or_apply_material_is_present", + ] + dry_run_execution_envelope_freeze_proof = { + "freeze_proof_id": freeze_id, + "source_controlled_executor_quarantine_proof_closeout_id": closeout_id, + "source_controlled_executor_quarantine_proof_id": quarantine.get( + "quarantine_proof_id" + ), + "source_final_no_runner_execution_proof_closeout_id": source_closeout_id, + "source_final_no_runner_execution_proof_id": final_proof.get("proof_id"), + "source_no_execution_receipt_handoff_closeout_id": handoff_closeout.get( + "no_execution_receipt_handoff_closeout_id" + ), + "source_no_execution_receipt_handoff_id": handoff.get("handoff_id"), + "source_runner_invocation_boundary_closeout_id": boundary_closeout.get( + "runner_invocation_boundary_closeout_id" + ), + "source_runner_invocation_boundary_id": boundary.get("boundary_id"), + "required_command_shape_hash": quarantine.get("required_command_shape_hash"), + "freeze_status": "dry_run_execution_envelope_freeze_proof_preview_ready", + "freeze_mode": "dry_run_execution_envelope_freeze_proof_preview_only", + "execution_envelope_frozen": True, + "execution_envelope_mutation_allowed": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "execution_receipt_required": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "ready_for_controlled_executor_quarantine_closeout_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "dry_run_execution_envelope_freeze_proof_field_count": len( + dry_run_execution_envelope_freeze_proof_fields + ), + "dry_run_execution_envelope_freeze_proof_fields": ( + dry_run_execution_envelope_freeze_proof_fields + ), + } + quarantine_closeout_ready = ( + quarantine_closeout_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_FINAL_NO_RUNNER_EXECUTION_PROOF_CLOSEOUT_READY" + and summary.get( + "controlled_dry_run_final_no_runner_execution_proof_closeout_ready_count" + ) + == 1 + and summary.get( + "controlled_dry_run_final_no_runner_execution_proof_closeout_pass_count" + ) + == summary.get( + "controlled_dry_run_final_no_runner_execution_proof_closeout_check_count" + ) + ) + source_chain_ids_match = ( + bool(source_closeout_id) + and source_closeout_id + == future_quarantine.get("final_no_runner_execution_proof_closeout_id") + == quarantine.get("source_final_no_runner_execution_proof_closeout_id") + == dry_run_execution_envelope_freeze_proof.get( + "source_final_no_runner_execution_proof_closeout_id" + ) + and quarantine.get("quarantine_proof_id") + == future_quarantine.get("controlled_executor_quarantine_proof_id") + == dry_run_execution_envelope_freeze_proof.get( + "source_controlled_executor_quarantine_proof_id" + ) + and final_proof.get("proof_id") + == future_quarantine.get("source_final_no_runner_execution_proof_id") + == quarantine.get("source_final_no_runner_execution_proof_id") + == dry_run_execution_envelope_freeze_proof.get( + "source_final_no_runner_execution_proof_id" + ) + and handoff_closeout.get("no_execution_receipt_handoff_closeout_id") + == future_quarantine.get("source_no_execution_receipt_handoff_closeout_id") + == quarantine.get("source_no_execution_receipt_handoff_closeout_id") + == dry_run_execution_envelope_freeze_proof.get( + "source_no_execution_receipt_handoff_closeout_id" + ) + and handoff.get("handoff_id") + == future_quarantine.get("source_no_execution_receipt_handoff_id") + == quarantine.get("source_no_execution_receipt_handoff_id") + == dry_run_execution_envelope_freeze_proof.get( + "source_no_execution_receipt_handoff_id" + ) + and boundary_closeout.get("runner_invocation_boundary_closeout_id") + == future_quarantine.get("source_runner_invocation_boundary_closeout_id") + == quarantine.get("source_runner_invocation_boundary_closeout_id") + == dry_run_execution_envelope_freeze_proof.get( + "source_runner_invocation_boundary_closeout_id" + ) + ) + controlled_executor_quarantine_proof_ready = ( + quarantine_closeout_ready + and quarantine.get("quarantine_status") + == "controlled_executor_quarantine_proof_preview_ready" + and quarantine.get("quarantine_proof_id") + == future_quarantine.get("controlled_executor_quarantine_proof_id") + and int(quarantine.get("controlled_executor_quarantine_proof_field_count") or 0) + == 12 + and summary.get("controlled_executor_quarantine_proof_count") == 1 + ) + controlled_executor_quarantine_proof_no_execute = ( + quarantine.get("quarantine_mode") + == "controlled_executor_quarantine_proof_preview_only" + and quarantine.get("controlled_executor_quarantine_bound") is True + and quarantine.get("executor_quarantine_enforced") is True + and quarantine.get("execution_receipt_present") is False + and quarantine.get("execution_receipt_required") is False + and quarantine.get("dry_run_executor_invoked") is False + and quarantine.get("runner_invocation_performed") is False + and quarantine.get("endpoint_executed") is False + and quarantine.get("sql_executed") is False + and quarantine.get("database_written") is False + and quarantine.get("dry_run_executor_invocation_allowed") is False + and quarantine.get("runner_invocation_allowed") is False + and quarantine.get("ready_for_dry_run_executor_invocation_now") is False + and quarantine.get("ready_for_actual_dry_run_execution_now") is False + and quarantine.get("endpoint_execution_allowed") is False + and quarantine.get("sql_execution_allowed") is False + and quarantine.get("database_write_allowed") is False + and quarantine.get("database_apply_authorized") is False + and quarantine.get("executes_database_apply") is False + and quarantine.get("executes_endpoint") is False + and quarantine.get("executes_sql") is False + and quarantine.get("writes_database") is False + and quarantine.get("captures_stdout") is False + and quarantine.get("captures_stderr") is False + and quarantine.get("stdout_included") is False + and quarantine.get("stderr_included") is False + ) + dry_run_execution_envelope_freeze_proof_bound = ( + controlled_executor_quarantine_proof_ready + and bool(dry_run_execution_envelope_freeze_proof.get("freeze_proof_id")) + and dry_run_execution_envelope_freeze_proof.get( + "source_controlled_executor_quarantine_proof_closeout_id" + ) + == closeout_id + and dry_run_execution_envelope_freeze_proof.get( + "source_controlled_executor_quarantine_proof_id" + ) + == quarantine.get("quarantine_proof_id") + and dry_run_execution_envelope_freeze_proof.get("required_command_shape_hash") + == quarantine.get("required_command_shape_hash") + and int( + dry_run_execution_envelope_freeze_proof.get( + "dry_run_execution_envelope_freeze_proof_field_count" + ) + or 0 + ) + == len(dry_run_execution_envelope_freeze_proof_fields) + ) + dry_run_execution_envelope_freeze_proof_blocks_execution = ( + dry_run_execution_envelope_freeze_proof.get("freeze_mode") + == "dry_run_execution_envelope_freeze_proof_preview_only" + and dry_run_execution_envelope_freeze_proof.get("execution_envelope_frozen") + is True + and dry_run_execution_envelope_freeze_proof.get( + "execution_envelope_mutation_allowed" + ) + is False + and dry_run_execution_envelope_freeze_proof.get("dry_run_executor_invoked") + is False + and dry_run_execution_envelope_freeze_proof.get("runner_invocation_performed") + is False + and dry_run_execution_envelope_freeze_proof.get("endpoint_executed") is False + and dry_run_execution_envelope_freeze_proof.get("sql_executed") is False + and dry_run_execution_envelope_freeze_proof.get("database_written") is False + and dry_run_execution_envelope_freeze_proof.get("execution_receipt_present") + is False + and dry_run_execution_envelope_freeze_proof.get("execution_receipt_required") + is False + and dry_run_execution_envelope_freeze_proof.get( + "dry_run_executor_invocation_allowed" + ) + is False + and dry_run_execution_envelope_freeze_proof.get("runner_invocation_allowed") + is False + and dry_run_execution_envelope_freeze_proof.get( + "ready_for_dry_run_executor_invocation_now" + ) + is False + and dry_run_execution_envelope_freeze_proof.get( + "ready_for_actual_dry_run_execution_now" + ) + is False + and dry_run_execution_envelope_freeze_proof.get("endpoint_execution_allowed") + is False + and dry_run_execution_envelope_freeze_proof.get("sql_execution_allowed") + is False + and dry_run_execution_envelope_freeze_proof.get("database_write_allowed") + is False + and dry_run_execution_envelope_freeze_proof.get("database_apply_authorized") + is False + and dry_run_execution_envelope_freeze_proof.get("executes_database_apply") + is False + and dry_run_execution_envelope_freeze_proof.get("executes_endpoint") is False + and dry_run_execution_envelope_freeze_proof.get("executes_sql") is False + and dry_run_execution_envelope_freeze_proof.get("writes_database") is False + and dry_run_execution_envelope_freeze_proof.get("captures_stdout") is False + and dry_run_execution_envelope_freeze_proof.get("captures_stderr") is False + and dry_run_execution_envelope_freeze_proof.get("stdout_included") is False + and dry_run_execution_envelope_freeze_proof.get("stderr_included") is False + ) + previous_closeouts_carried_forward = ( + quarantine_closeout.get("final_no_runner_execution_proof_closeout_only") + is True + and quarantine_closeout.get("controlled_executor_quarantine_proof_only") + is True + and quarantine_closeout.get("database_apply_authorized") is False + and handoff_closeout.get("no_execution_receipt_handoff_closeout_only") is True + and boundary_closeout.get("runner_invocation_boundary_closeout_only") is True + and guard_closeout.get("execution_preflight_guard_closeout_only") is True + and package_closeout.get("no_write_invocation_package_closeout_only") + is True + and invocation_closeout.get("invocation_receipt_closeout_only") is True + and readiness_closeout.get("apply_executor_readiness_closeout_only") is True + and pre_apply_closeout.get("pre_apply_replay_closeout_only") is True + and final_guard.get("guard_status") + == "final_dry_run_executor_guard_preview_ready" + and no_apply_enforcement.get("enforcement_status") + == "no_apply_enforcement_preview_ready" + and controlled_executor_quarantine_proof_no_execute + ) + target_hash_locked = ( + quarantine_closeout.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(quarantine_closeout.get("expected_sha256")) + and bool(quarantine_closeout.get("actual_sha256")) + and quarantine_closeout.get("expected_sha256") + == quarantine_closeout.get("actual_sha256") + and quarantine_closeout.get("hash_matches") is True + and quarantine_closeout.get("target_migration_hash_locked") is True + ) + rollback_and_verifier_bound = ( + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False + ) + quarantine_contract_blocks_database_apply = ( + quarantine_contract.get("executes_database_apply") is False + and quarantine_contract.get("executes_endpoint") is False + and quarantine_contract.get("executes_sql") is False + and quarantine_contract.get("database_apply_authorized") is False + and quarantine_contract.get("ready_for_database_apply_now") is False + and quarantine_contract.get("ready_for_dry_run_executor_invocation_now") + is False + and quarantine_contract.get("ready_for_actual_dry_run_execution_now") + is False + and quarantine_contract.get("signs_database_apply_authorization") is False + and quarantine_contract.get("writes_database") is False + and quarantine_contract.get("executes_in_preview") is False + ) + side_effect_free = ( + summary.get("reads_secret_count", 0) == 0 + and summary.get("executes_script_count", 0) == 0 + and summary.get("executes_migration_count", 0) == 0 + and summary.get("executes_endpoint_count", 0) == 0 + and summary.get("executes_sql_count", 0) == 0 + and summary.get("writes_database_count", 0) == 0 + and summary.get("signs_database_apply_authorization_count", 0) == 0 + and summary.get("dry_run_executor_invoked_count", 0) == 0 + and summary.get("runner_invocation_performed_count", 0) == 0 + and summary.get("endpoint_executed_count", 0) == 0 + and summary.get("sql_executed_count", 0) == 0 + and summary.get("database_written_count", 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("executes_database_apply") is False + and controlled_executor_quarantine_proof_no_execute + and dry_run_execution_envelope_freeze_proof_blocks_execution + ) + checks = [ + _controlled_dry_run_controlled_executor_quarantine_proof_closeout_check( + "final_no_runner_execution_proof_closeout_ready", + quarantine_closeout_ready, + { + "result": quarantine_closeout_result.get("result"), + "ready_count": summary.get( + "controlled_dry_run_final_no_runner_execution_proof_closeout_ready_count" + ), + "pass_count": summary.get( + "controlled_dry_run_final_no_runner_execution_proof_closeout_pass_count" + ), + "check_count": summary.get( + "controlled_dry_run_final_no_runner_execution_proof_closeout_check_count" + ), + }, + "wait_for_final_no_runner_execution_proof_closeout_ready", + ), + _controlled_dry_run_controlled_executor_quarantine_proof_closeout_check( + "source_chain_ids_match", + source_chain_ids_match, + { + "final_no_runner_execution_proof_closeout_id": source_closeout_id, + "controlled_executor_quarantine_proof_id": quarantine.get( + "quarantine_proof_id" + ), + "freeze_proof_id": dry_run_execution_envelope_freeze_proof.get( + "freeze_proof_id" + ), + }, + "wait_for_source_chain_ids_match", + ), + _controlled_dry_run_controlled_executor_quarantine_proof_closeout_check( + "controlled_executor_quarantine_proof_ready", + controlled_executor_quarantine_proof_ready, + { + "quarantine_proof_id": quarantine.get("quarantine_proof_id"), + "quarantine_status": quarantine.get("quarantine_status"), + "field_count": quarantine.get( + "controlled_executor_quarantine_proof_field_count" + ), + }, + "wait_for_controlled_executor_quarantine_proof_ready", + ), + _controlled_dry_run_controlled_executor_quarantine_proof_closeout_check( + "controlled_executor_quarantine_proof_no_execute", + controlled_executor_quarantine_proof_no_execute, + { + "quarantine_mode": quarantine.get("quarantine_mode"), + "dry_run_executor_invoked": quarantine.get("dry_run_executor_invoked"), + "runner_invocation_performed": quarantine.get( + "runner_invocation_performed" + ), + }, + "abort_if_controlled_executor_quarantine_proof_reports_execution", + ), + _controlled_dry_run_controlled_executor_quarantine_proof_closeout_check( + "dry_run_execution_envelope_freeze_proof_bound", + dry_run_execution_envelope_freeze_proof_bound, + { + "freeze_proof_id": dry_run_execution_envelope_freeze_proof.get( + "freeze_proof_id" + ), + "source_controlled_executor_quarantine_proof_id": ( + dry_run_execution_envelope_freeze_proof.get( + "source_controlled_executor_quarantine_proof_id" + ) + ), + "field_count": dry_run_execution_envelope_freeze_proof.get( + "dry_run_execution_envelope_freeze_proof_field_count" + ), + }, + "wait_for_dry_run_execution_envelope_freeze_proof_binding", + ), + _controlled_dry_run_controlled_executor_quarantine_proof_closeout_check( + "dry_run_execution_envelope_freeze_proof_blocks_execution", + dry_run_execution_envelope_freeze_proof_blocks_execution, + { + "freeze_mode": dry_run_execution_envelope_freeze_proof.get( + "freeze_mode" + ), + "execution_envelope_frozen": ( + dry_run_execution_envelope_freeze_proof.get( + "execution_envelope_frozen" + ) + ), + "execution_envelope_mutation_allowed": ( + dry_run_execution_envelope_freeze_proof.get( + "execution_envelope_mutation_allowed" + ) + ), + }, + "abort_if_execution_envelope_allows_execution", + ), + _controlled_dry_run_controlled_executor_quarantine_proof_closeout_check( + "previous_closeouts_carried_forward", + previous_closeouts_carried_forward, + { + "final_no_runner_execution_proof_closeout_only": ( + quarantine_closeout.get( + "final_no_runner_execution_proof_closeout_only" + ) + ), + "controlled_executor_quarantine_proof_only": ( + quarantine_closeout.get("controlled_executor_quarantine_proof_only") + ), + "quarantine_mode": quarantine.get("quarantine_mode"), + }, + "wait_for_previous_closeouts_carry_forward", + ), + _controlled_dry_run_controlled_executor_quarantine_proof_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": quarantine_closeout.get("target_file"), + "hash_matches": quarantine_closeout.get("hash_matches"), + "expected_sha256_present": bool( + quarantine_closeout.get("expected_sha256") + ), + "actual_sha256_present": bool( + quarantine_closeout.get("actual_sha256") + ), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_controlled_executor_quarantine_proof_closeout_check( + "rollback_and_post_apply_verifier_bound", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "database_apply_authorized": verifier_binding.get( + "database_apply_authorized" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_controlled_executor_quarantine_proof_closeout_check( + "final_no_runner_execution_proof_closeout_contract_blocks_database_apply", + quarantine_contract_blocks_database_apply, + { + "permits_future_database_apply_controlled_dry_run_controlled_executor_quarantine_proof": ( + quarantine_contract.get( + "permits_future_database_apply_controlled_dry_run_controlled_executor_quarantine_proof" + ) + ), + "database_apply_authorized": quarantine_contract.get( + "database_apply_authorized" + ), + "writes_database": quarantine_contract.get("writes_database"), + }, + "abort_if_final_no_runner_execution_proof_closeout_contract_authorizes_database_apply", + ), + _controlled_dry_run_controlled_executor_quarantine_proof_closeout_check( + "preview_has_no_side_effects_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + "dry_run_executor_invoked_count": summary.get( + "dry_run_executor_invoked_count", 0 + ), + }, + "abort_on_preview_side_effect_execution_or_signing", + ), + _controlled_dry_run_controlled_executor_quarantine_proof_closeout_check( + "manual_review_not_required_for_safe_preview", + quarantine_contract.get("manual_review_mode") == "exception_only" + and safety.get("manual_review_mode") == "exception_only", + { + "contract_manual_review_mode": quarantine_contract.get( + "manual_review_mode" + ), + "safety_manual_review_mode": safety.get("manual_review_mode"), + }, + "keep_manual_review_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_CONTROLLED_EXECUTOR_QUARANTINE_PROOF_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_FINAL_NO_RUNNER_EXECUTION_PROOF_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_execution_envelope_freeze_proof = { + "controlled_executor_quarantine_proof_closeout_id": closeout_id, + "dry_run_execution_envelope_freeze_proof_id": freeze_id, + "source_final_no_runner_execution_proof_closeout_id": source_closeout_id, + "source_controlled_executor_quarantine_proof_id": quarantine.get( + "quarantine_proof_id" + ), + "source_final_no_runner_execution_proof_id": final_proof.get("proof_id"), + "source_no_execution_receipt_handoff_closeout_id": handoff_closeout.get( + "no_execution_receipt_handoff_closeout_id" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_execution_envelope_freeze_proof": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_execution_envelope_freeze_proof_closeout": ( + closeout_ready + ), + "controlled_executor_quarantine_proof_closeout_ready": closeout_ready, + "dry_run_execution_envelope_freeze_proof_bound": closeout_ready, + "controlled_executor_quarantine_bound": True, + "executor_quarantine_enforced": True, + "execution_envelope_frozen": True, + "execution_envelope_mutation_allowed": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "dry_run_execution_performed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "reads_secret_in_preview": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_controlled_executor_quarantine_proof_closeout = { + "controlled_executor_quarantine_proof_closeout_id": closeout_id, + "authorization_material_type": ( + "controlled_dry_run_controlled_executor_quarantine_proof_closeout" + ), + "source_final_no_runner_execution_proof_closeout_id": source_closeout_id, + "source_controlled_executor_quarantine_proof_id": quarantine.get( + "quarantine_proof_id" + ), + "source_final_no_runner_execution_proof_id": final_proof.get("proof_id"), + "source_no_execution_receipt_handoff_closeout_id": handoff_closeout.get( + "no_execution_receipt_handoff_closeout_id" + ), + "source_no_execution_receipt_handoff_id": handoff.get("handoff_id"), + "source_runner_invocation_boundary_closeout_id": boundary_closeout.get( + "runner_invocation_boundary_closeout_id" + ), + "source_runner_invocation_boundary_id": boundary.get("boundary_id"), + "required_command_shape_hash": quarantine.get("required_command_shape_hash"), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_controlled_executor_quarantine_proof_closeout": ( + closeout_ready + ), + "controlled_executor_quarantine_proof_closeout_fields": ( + quarantine_closeout_fields + ), + "controlled_executor_quarantine_proof_closeout_field_count": len( + quarantine_closeout_fields + ), + "controlled_executor_quarantine_proof_closeout_acceptance_gates": ( + quarantine_closeout_acceptance_gates + ), + "controlled_executor_quarantine_proof_closeout_acceptance_gate_count": len( + quarantine_closeout_acceptance_gates + ), + "dry_run_execution_envelope_freeze_proof": ( + dry_run_execution_envelope_freeze_proof + ), + "dry_run_execution_envelope_freeze_proof_count": 1, + "dry_run_execution_envelope_freeze_proof_field_count": len( + dry_run_execution_envelope_freeze_proof_fields + ), + "controlled_executor_quarantine_proof": quarantine, + "controlled_executor_quarantine_proof_count": 1, + "final_no_runner_execution_proof_closeout": quarantine_closeout, + "final_no_runner_execution_proof_closeout_count": 1, + "final_no_runner_execution_proof": final_proof, + "final_no_runner_execution_proof_count": 1, + "no_execution_receipt_handoff_closeout": handoff_closeout, + "no_execution_receipt_handoff_closeout_count": 1, + "no_execution_receipt_handoff": handoff, + "no_execution_receipt_handoff_count": 1, + "runner_invocation_boundary_closeout": boundary_closeout, + "runner_invocation_boundary_closeout_count": 1, + "runner_invocation_boundary": boundary, + "runner_invocation_boundary_count": 1, + "target_file": quarantine_closeout.get("target_file"), + "expected_sha256": quarantine_closeout.get("expected_sha256"), + "actual_sha256": quarantine_closeout.get("actual_sha256"), + "hash_matches": quarantine_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "controlled_executor_quarantine_proof_closeout_only": True, + "dry_run_execution_envelope_freeze_proof_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "controlled_executor_quarantine_bound": True, + "executor_quarantine_enforced": True, + "execution_envelope_frozen": True, + "execution_envelope_mutation_allowed": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + } + controlled_dry_run_controlled_executor_quarantine_proof_closeout_contract = { + "mode": "controlled_dry_run_controlled_executor_quarantine_proof_closeout_and_execution_envelope_freeze_proof_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-controlled-executor-quarantine-proof-closeout" + ), + "source_final_no_runner_execution_proof_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-final-no-runner-execution-proof-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_execution_envelope_freeze_proof": ( + closeout_ready + ), + "ready_for_database_apply_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": False, + "signs_database_apply_authorization": False, + "writes_database": False, + "executes_in_preview": False, + "secret_material_required_in_preview": False, + "manual_review_mode": "exception_only", + } + output_summary = dict(summary) + output_summary.update( + { + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_check_count": len( + checks + ), + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_pass_count": ( + passed_count + ), + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_count": 1, + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_field_count": len( + quarantine_closeout_fields + ), + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_acceptance_gate_count": len( + quarantine_closeout_acceptance_gates + ), + "dry_run_execution_envelope_freeze_proof_count": 1, + "dry_run_execution_envelope_freeze_proof_field_count": len( + dry_run_execution_envelope_freeze_proof_fields + ), + "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, + "dry_run_executor_invoked_count": 0, + "runner_invocation_performed_count": 0, + "endpoint_executed_count": 0, + "sql_executed_count": 0, + "database_written_count": 0, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + } + ) + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_CONTROLLED_EXECUTOR_QUARANTINE_PROOF_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(quarantine_closeout_result.get("success")), + "generated_at": quarantine_closeout_result.get("generated_at"), + "source_policy": quarantine_closeout_result.get("policy"), + "stats": quarantine_closeout_result.get("stats") or {}, + "summary": output_summary, + "future_database_apply_controlled_dry_run_execution_envelope_freeze_proof": ( + future_database_apply_controlled_dry_run_execution_envelope_freeze_proof + ), + "controlled_dry_run_controlled_executor_quarantine_proof_closeout": ( + controlled_dry_run_controlled_executor_quarantine_proof_closeout + ), + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_contract": ( + controlled_dry_run_controlled_executor_quarantine_proof_closeout_contract + ), + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_checks": ( + checks + ), + "source_controlled_dry_run_final_no_runner_execution_proof_closeout_summary": ( + summary + ), + "source_controlled_dry_run_final_no_runner_execution_proof_closeout_contract": ( + quarantine_contract + ), + "source_controlled_dry_run_final_no_runner_execution_proof_closeout": ( + quarantine_closeout + ), + "source_database_apply_controlled_dry_run_controlled_executor_quarantine_proof": ( + future_quarantine + ), + "safety": { + "read_only_db_apply_controlled_dry_run_controlled_executor_quarantine_proof_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future execution envelope freeze proof closeout.", + "Keep actual dry-run executor invocation disabled; this proof freezes the preview envelope before any executor lane can run.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_execution_envelope_freeze_proof_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out the frozen execution envelope and hand it to verifier preview.""" + freeze_closeout_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_controlled_executor_quarantine_proof_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_freeze = ( + freeze_closeout_result.get( + "future_database_apply_controlled_dry_run_execution_envelope_freeze_proof" + ) + or {} + ) + freeze_closeout = ( + freeze_closeout_result.get( + "controlled_dry_run_controlled_executor_quarantine_proof_closeout" + ) + or {} + ) + freeze_contract = ( + freeze_closeout_result.get( + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_contract" + ) + or {} + ) + summary = freeze_closeout_result.get("summary") or {} + safety = freeze_closeout_result.get("safety") or {} + freeze_proof = freeze_closeout.get("dry_run_execution_envelope_freeze_proof") or {} + quarantine = freeze_closeout.get("controlled_executor_quarantine_proof") or {} + source_quarantine_closeout = ( + freeze_closeout.get("final_no_runner_execution_proof_closeout") or {} + ) + final_proof = freeze_closeout.get("final_no_runner_execution_proof") or {} + handoff_closeout = ( + freeze_closeout.get("no_execution_receipt_handoff_closeout") or {} + ) + handoff = freeze_closeout.get("no_execution_receipt_handoff") or {} + boundary_closeout = ( + freeze_closeout.get("runner_invocation_boundary_closeout") or {} + ) + rollback_binding = freeze_closeout.get("rollback_binding") or {} + verifier_binding = freeze_closeout.get("post_apply_verifier_binding") or {} + source_closeout_id = freeze_closeout.get( + "controlled_executor_quarantine_proof_closeout_id" + ) + closeout_id = ( + _db_apply_controlled_dry_run_execution_envelope_freeze_proof_closeout_id( + freeze_closeout_result + ) + ) + handoff_id = f"{closeout_id}-frozen-envelope-verifier-handoff" + freeze_closeout_fields = [ + "execution_envelope_freeze_proof_closeout_id", + "source_controlled_executor_quarantine_proof_closeout_id", + "source_dry_run_execution_envelope_freeze_proof_id", + "source_controlled_executor_quarantine_proof_id", + "frozen_envelope_verifier_handoff_id", + "required_command_shape_hash", + "target_migration_file", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "execution_envelope_frozen", + "verifier_invocation_allowed", + "abort_conditions", + ] + freeze_closeout_acceptance_gates = [ + "controlled_executor_quarantine_proof_closeout_ready", + "source_chain_ids_match", + "dry_run_execution_envelope_freeze_proof_ready", + "dry_run_execution_envelope_freeze_proof_no_execute", + "frozen_envelope_verifier_handoff_bound", + "frozen_envelope_verifier_handoff_blocks_execution", + "previous_closeouts_carried_forward", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "no_secret_signature_or_database_apply", + ] + frozen_envelope_verifier_handoff_fields = [ + "handoff_id", + "source_execution_envelope_freeze_proof_closeout_id", + "source_dry_run_execution_envelope_freeze_proof_id", + "source_controlled_executor_quarantine_proof_closeout_id", + "source_controlled_executor_quarantine_proof_id", + "verifier_handoff_mode", + "execution_envelope_frozen", + "verifier_invocation_allowed", + "dry_run_executor_invocation_allowed", + "endpoint_execution_allowed", + "sql_execution_allowed", + "database_apply_authorized", + ] + abort_conditions = [ + "abort_if_controlled_executor_quarantine_proof_closeout_not_ready", + "abort_if_source_chain_ids_do_not_match", + "abort_if_dry_run_execution_envelope_freeze_proof_missing", + "abort_if_dry_run_execution_envelope_freeze_proof_reports_execution", + "abort_if_frozen_envelope_verifier_handoff_missing", + "abort_if_verifier_handoff_allows_execution", + "abort_if_endpoint_or_sql_execution_is_allowed", + "abort_if_database_write_or_apply_is_allowed", + "abort_if_target_migration_hash_changes", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_any_secret_signature_or_apply_material_is_present", + ] + frozen_envelope_verifier_handoff = { + "handoff_id": handoff_id, + "source_execution_envelope_freeze_proof_closeout_id": closeout_id, + "source_dry_run_execution_envelope_freeze_proof_id": freeze_proof.get( + "freeze_proof_id" + ), + "source_controlled_executor_quarantine_proof_closeout_id": source_closeout_id, + "source_controlled_executor_quarantine_proof_id": quarantine.get( + "quarantine_proof_id" + ), + "source_final_no_runner_execution_proof_closeout_id": ( + source_quarantine_closeout.get("final_no_runner_execution_proof_closeout_id") + ), + "source_final_no_runner_execution_proof_id": final_proof.get("proof_id"), + "source_no_execution_receipt_handoff_closeout_id": handoff_closeout.get( + "no_execution_receipt_handoff_closeout_id" + ), + "source_no_execution_receipt_handoff_id": handoff.get("handoff_id"), + "source_runner_invocation_boundary_closeout_id": boundary_closeout.get( + "runner_invocation_boundary_closeout_id" + ), + "required_command_shape_hash": freeze_proof.get("required_command_shape_hash"), + "handoff_status": "frozen_envelope_verifier_handoff_preview_ready", + "verifier_handoff_mode": "frozen_envelope_verifier_handoff_preview_only", + "execution_envelope_frozen": True, + "execution_envelope_mutation_allowed": False, + "verifier_handoff_bound": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "verifier_receipt_required": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "execution_receipt_required": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "ready_for_frozen_envelope_verifier_handoff_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "frozen_envelope_verifier_handoff_field_count": len( + frozen_envelope_verifier_handoff_fields + ), + "frozen_envelope_verifier_handoff_fields": ( + frozen_envelope_verifier_handoff_fields + ), + } + freeze_closeout_ready = ( + freeze_closeout_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_CONTROLLED_EXECUTOR_QUARANTINE_PROOF_CLOSEOUT_READY" + and summary.get( + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_ready_count" + ) + == 1 + and summary.get( + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_pass_count" + ) + == summary.get( + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_check_count" + ) + ) + source_chain_ids_match = ( + bool(source_closeout_id) + and source_closeout_id + == future_freeze.get("controlled_executor_quarantine_proof_closeout_id") + == freeze_proof.get("source_controlled_executor_quarantine_proof_closeout_id") + == frozen_envelope_verifier_handoff.get( + "source_controlled_executor_quarantine_proof_closeout_id" + ) + and freeze_proof.get("freeze_proof_id") + == future_freeze.get("dry_run_execution_envelope_freeze_proof_id") + == frozen_envelope_verifier_handoff.get( + "source_dry_run_execution_envelope_freeze_proof_id" + ) + and quarantine.get("quarantine_proof_id") + == future_freeze.get("source_controlled_executor_quarantine_proof_id") + == freeze_proof.get("source_controlled_executor_quarantine_proof_id") + == frozen_envelope_verifier_handoff.get( + "source_controlled_executor_quarantine_proof_id" + ) + and final_proof.get("proof_id") + == future_freeze.get("source_final_no_runner_execution_proof_id") + == freeze_proof.get("source_final_no_runner_execution_proof_id") + == frozen_envelope_verifier_handoff.get( + "source_final_no_runner_execution_proof_id" + ) + and handoff_closeout.get("no_execution_receipt_handoff_closeout_id") + == future_freeze.get("source_no_execution_receipt_handoff_closeout_id") + == freeze_proof.get("source_no_execution_receipt_handoff_closeout_id") + == frozen_envelope_verifier_handoff.get( + "source_no_execution_receipt_handoff_closeout_id" + ) + ) + dry_run_execution_envelope_freeze_proof_ready = ( + freeze_closeout_ready + and freeze_proof.get("freeze_status") + == "dry_run_execution_envelope_freeze_proof_preview_ready" + and freeze_proof.get("freeze_proof_id") + == future_freeze.get("dry_run_execution_envelope_freeze_proof_id") + and int(freeze_proof.get("dry_run_execution_envelope_freeze_proof_field_count") or 0) + == 12 + and summary.get("dry_run_execution_envelope_freeze_proof_count") == 1 + ) + dry_run_execution_envelope_freeze_proof_no_execute = ( + freeze_proof.get("freeze_mode") + == "dry_run_execution_envelope_freeze_proof_preview_only" + and freeze_proof.get("execution_envelope_frozen") is True + and freeze_proof.get("execution_envelope_mutation_allowed") is False + and freeze_proof.get("dry_run_executor_invoked") is False + and freeze_proof.get("runner_invocation_performed") is False + and freeze_proof.get("endpoint_executed") is False + and freeze_proof.get("sql_executed") is False + and freeze_proof.get("database_written") is False + and freeze_proof.get("execution_receipt_present") is False + and freeze_proof.get("execution_receipt_required") is False + and freeze_proof.get("dry_run_executor_invocation_allowed") is False + and freeze_proof.get("runner_invocation_allowed") is False + and freeze_proof.get("ready_for_dry_run_executor_invocation_now") is False + and freeze_proof.get("ready_for_actual_dry_run_execution_now") is False + and freeze_proof.get("endpoint_execution_allowed") is False + and freeze_proof.get("sql_execution_allowed") is False + and freeze_proof.get("database_write_allowed") is False + and freeze_proof.get("database_apply_authorized") is False + and freeze_proof.get("executes_database_apply") is False + and freeze_proof.get("executes_endpoint") is False + and freeze_proof.get("executes_sql") is False + and freeze_proof.get("writes_database") is False + and freeze_proof.get("captures_stdout") is False + and freeze_proof.get("captures_stderr") is False + and freeze_proof.get("stdout_included") is False + and freeze_proof.get("stderr_included") is False + ) + frozen_envelope_verifier_handoff_bound = ( + dry_run_execution_envelope_freeze_proof_ready + and bool(frozen_envelope_verifier_handoff.get("handoff_id")) + and frozen_envelope_verifier_handoff.get( + "source_execution_envelope_freeze_proof_closeout_id" + ) + == closeout_id + and frozen_envelope_verifier_handoff.get( + "source_dry_run_execution_envelope_freeze_proof_id" + ) + == freeze_proof.get("freeze_proof_id") + and frozen_envelope_verifier_handoff.get("required_command_shape_hash") + == freeze_proof.get("required_command_shape_hash") + and int( + frozen_envelope_verifier_handoff.get( + "frozen_envelope_verifier_handoff_field_count" + ) + or 0 + ) + == len(frozen_envelope_verifier_handoff_fields) + ) + frozen_envelope_verifier_handoff_blocks_execution = ( + frozen_envelope_verifier_handoff.get("verifier_handoff_mode") + == "frozen_envelope_verifier_handoff_preview_only" + and frozen_envelope_verifier_handoff.get("execution_envelope_frozen") is True + and frozen_envelope_verifier_handoff.get( + "execution_envelope_mutation_allowed" + ) + is False + and frozen_envelope_verifier_handoff.get("verifier_handoff_bound") is True + and frozen_envelope_verifier_handoff.get("verifier_invocation_allowed") + is False + and frozen_envelope_verifier_handoff.get("verifier_invoked") is False + and frozen_envelope_verifier_handoff.get("verifier_receipt_present") is False + and frozen_envelope_verifier_handoff.get("verifier_receipt_required") is False + and frozen_envelope_verifier_handoff.get("dry_run_executor_invoked") is False + and frozen_envelope_verifier_handoff.get("runner_invocation_performed") + is False + and frozen_envelope_verifier_handoff.get("endpoint_executed") is False + and frozen_envelope_verifier_handoff.get("sql_executed") is False + and frozen_envelope_verifier_handoff.get("database_written") is False + and frozen_envelope_verifier_handoff.get("execution_receipt_present") is False + and frozen_envelope_verifier_handoff.get("execution_receipt_required") is False + and frozen_envelope_verifier_handoff.get("dry_run_executor_invocation_allowed") + is False + and frozen_envelope_verifier_handoff.get("runner_invocation_allowed") is False + and frozen_envelope_verifier_handoff.get("ready_for_verifier_invocation_now") + is False + and frozen_envelope_verifier_handoff.get( + "ready_for_dry_run_executor_invocation_now" + ) + is False + and frozen_envelope_verifier_handoff.get("ready_for_actual_dry_run_execution_now") + is False + and frozen_envelope_verifier_handoff.get("endpoint_execution_allowed") is False + and frozen_envelope_verifier_handoff.get("sql_execution_allowed") is False + and frozen_envelope_verifier_handoff.get("database_write_allowed") is False + and frozen_envelope_verifier_handoff.get("database_apply_authorized") is False + and frozen_envelope_verifier_handoff.get("executes_database_apply") is False + and frozen_envelope_verifier_handoff.get("executes_endpoint") is False + and frozen_envelope_verifier_handoff.get("executes_sql") is False + and frozen_envelope_verifier_handoff.get("writes_database") is False + and frozen_envelope_verifier_handoff.get("captures_stdout") is False + and frozen_envelope_verifier_handoff.get("captures_stderr") is False + and frozen_envelope_verifier_handoff.get("stdout_included") is False + and frozen_envelope_verifier_handoff.get("stderr_included") is False + ) + previous_closeouts_carried_forward = ( + freeze_closeout.get("controlled_executor_quarantine_proof_closeout_only") + is True + and freeze_closeout.get("dry_run_execution_envelope_freeze_proof_only") + is True + and freeze_closeout.get("database_apply_authorized") is False + and source_quarantine_closeout.get("final_no_runner_execution_proof_closeout_only") + is True + and source_quarantine_closeout.get("controlled_executor_quarantine_proof_only") + is True + and handoff_closeout.get("no_execution_receipt_handoff_closeout_only") is True + and boundary_closeout.get("runner_invocation_boundary_closeout_only") is True + and dry_run_execution_envelope_freeze_proof_no_execute + ) + target_hash_locked = ( + freeze_closeout.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(freeze_closeout.get("expected_sha256")) + and bool(freeze_closeout.get("actual_sha256")) + and freeze_closeout.get("expected_sha256") + == freeze_closeout.get("actual_sha256") + and freeze_closeout.get("hash_matches") is True + and freeze_closeout.get("target_migration_hash_locked") is True + ) + rollback_and_verifier_bound = ( + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False + ) + freeze_contract_blocks_database_apply = ( + freeze_contract.get("executes_database_apply") is False + and freeze_contract.get("executes_endpoint") is False + and freeze_contract.get("executes_sql") is False + and freeze_contract.get("database_apply_authorized") is False + and freeze_contract.get("ready_for_database_apply_now") is False + and freeze_contract.get("ready_for_dry_run_executor_invocation_now") is False + and freeze_contract.get("ready_for_actual_dry_run_execution_now") is False + and freeze_contract.get("signs_database_apply_authorization") is False + and freeze_contract.get("writes_database") is False + and freeze_contract.get("executes_in_preview") is False + ) + side_effect_free = ( + summary.get("reads_secret_count", 0) == 0 + and summary.get("executes_script_count", 0) == 0 + and summary.get("executes_migration_count", 0) == 0 + and summary.get("executes_endpoint_count", 0) == 0 + and summary.get("executes_sql_count", 0) == 0 + and summary.get("writes_database_count", 0) == 0 + and summary.get("signs_database_apply_authorization_count", 0) == 0 + and summary.get("dry_run_executor_invoked_count", 0) == 0 + and summary.get("runner_invocation_performed_count", 0) == 0 + and summary.get("endpoint_executed_count", 0) == 0 + and summary.get("sql_executed_count", 0) == 0 + and summary.get("database_written_count", 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("executes_database_apply") is False + and dry_run_execution_envelope_freeze_proof_no_execute + and frozen_envelope_verifier_handoff_blocks_execution + ) + checks = [ + _controlled_dry_run_execution_envelope_freeze_proof_closeout_check( + "controlled_executor_quarantine_proof_closeout_ready", + freeze_closeout_ready, + { + "result": freeze_closeout_result.get("result"), + "ready_count": summary.get( + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_ready_count" + ), + "pass_count": summary.get( + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_pass_count" + ), + "check_count": summary.get( + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_check_count" + ), + }, + "wait_for_controlled_executor_quarantine_proof_closeout_ready", + ), + _controlled_dry_run_execution_envelope_freeze_proof_closeout_check( + "source_chain_ids_match", + source_chain_ids_match, + { + "controlled_executor_quarantine_proof_closeout_id": source_closeout_id, + "freeze_proof_id": freeze_proof.get("freeze_proof_id"), + "handoff_id": frozen_envelope_verifier_handoff.get("handoff_id"), + }, + "wait_for_source_chain_ids_match", + ), + _controlled_dry_run_execution_envelope_freeze_proof_closeout_check( + "dry_run_execution_envelope_freeze_proof_ready", + dry_run_execution_envelope_freeze_proof_ready, + { + "freeze_proof_id": freeze_proof.get("freeze_proof_id"), + "freeze_status": freeze_proof.get("freeze_status"), + "field_count": freeze_proof.get( + "dry_run_execution_envelope_freeze_proof_field_count" + ), + }, + "wait_for_dry_run_execution_envelope_freeze_proof_ready", + ), + _controlled_dry_run_execution_envelope_freeze_proof_closeout_check( + "dry_run_execution_envelope_freeze_proof_no_execute", + dry_run_execution_envelope_freeze_proof_no_execute, + { + "freeze_mode": freeze_proof.get("freeze_mode"), + "execution_envelope_frozen": freeze_proof.get( + "execution_envelope_frozen" + ), + "execution_envelope_mutation_allowed": freeze_proof.get( + "execution_envelope_mutation_allowed" + ), + }, + "abort_if_dry_run_execution_envelope_freeze_proof_reports_execution", + ), + _controlled_dry_run_execution_envelope_freeze_proof_closeout_check( + "frozen_envelope_verifier_handoff_bound", + frozen_envelope_verifier_handoff_bound, + { + "handoff_id": frozen_envelope_verifier_handoff.get("handoff_id"), + "source_dry_run_execution_envelope_freeze_proof_id": ( + frozen_envelope_verifier_handoff.get( + "source_dry_run_execution_envelope_freeze_proof_id" + ) + ), + "field_count": frozen_envelope_verifier_handoff.get( + "frozen_envelope_verifier_handoff_field_count" + ), + }, + "wait_for_frozen_envelope_verifier_handoff_binding", + ), + _controlled_dry_run_execution_envelope_freeze_proof_closeout_check( + "frozen_envelope_verifier_handoff_blocks_execution", + frozen_envelope_verifier_handoff_blocks_execution, + { + "verifier_handoff_mode": frozen_envelope_verifier_handoff.get( + "verifier_handoff_mode" + ), + "verifier_invocation_allowed": frozen_envelope_verifier_handoff.get( + "verifier_invocation_allowed" + ), + "verifier_invoked": frozen_envelope_verifier_handoff.get( + "verifier_invoked" + ), + }, + "abort_if_frozen_envelope_verifier_handoff_allows_execution", + ), + _controlled_dry_run_execution_envelope_freeze_proof_closeout_check( + "previous_closeouts_carried_forward", + previous_closeouts_carried_forward, + { + "controlled_executor_quarantine_proof_closeout_only": ( + freeze_closeout.get( + "controlled_executor_quarantine_proof_closeout_only" + ) + ), + "dry_run_execution_envelope_freeze_proof_only": ( + freeze_closeout.get("dry_run_execution_envelope_freeze_proof_only") + ), + "freeze_mode": freeze_proof.get("freeze_mode"), + }, + "wait_for_previous_closeouts_carry_forward", + ), + _controlled_dry_run_execution_envelope_freeze_proof_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": freeze_closeout.get("target_file"), + "hash_matches": freeze_closeout.get("hash_matches"), + "expected_sha256_present": bool(freeze_closeout.get("expected_sha256")), + "actual_sha256_present": bool(freeze_closeout.get("actual_sha256")), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_execution_envelope_freeze_proof_closeout_check( + "rollback_and_post_apply_verifier_bound", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "database_apply_authorized": verifier_binding.get( + "database_apply_authorized" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_execution_envelope_freeze_proof_closeout_check( + "controlled_executor_quarantine_proof_closeout_contract_blocks_database_apply", + freeze_contract_blocks_database_apply, + { + "permits_future_database_apply_controlled_dry_run_execution_envelope_freeze_proof": ( + freeze_contract.get( + "permits_future_database_apply_controlled_dry_run_execution_envelope_freeze_proof" + ) + ), + "database_apply_authorized": freeze_contract.get( + "database_apply_authorized" + ), + "writes_database": freeze_contract.get("writes_database"), + }, + "abort_if_controlled_executor_quarantine_proof_closeout_contract_authorizes_database_apply", + ), + _controlled_dry_run_execution_envelope_freeze_proof_closeout_check( + "preview_has_no_side_effects_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + "dry_run_executor_invoked_count": summary.get( + "dry_run_executor_invoked_count", 0 + ), + }, + "abort_on_preview_side_effect_execution_or_signing", + ), + _controlled_dry_run_execution_envelope_freeze_proof_closeout_check( + "manual_review_not_required_for_safe_preview", + freeze_contract.get("manual_review_mode") == "exception_only" + and safety.get("manual_review_mode") == "exception_only", + { + "contract_manual_review_mode": freeze_contract.get( + "manual_review_mode" + ), + "safety_manual_review_mode": safety.get("manual_review_mode"), + }, + "keep_manual_review_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_EXECUTION_ENVELOPE_FREEZE_PROOF_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_CONTROLLED_EXECUTOR_QUARANTINE_PROOF_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_frozen_envelope_verifier_handoff = { + "execution_envelope_freeze_proof_closeout_id": closeout_id, + "frozen_envelope_verifier_handoff_id": handoff_id, + "source_controlled_executor_quarantine_proof_closeout_id": source_closeout_id, + "source_dry_run_execution_envelope_freeze_proof_id": freeze_proof.get( + "freeze_proof_id" + ), + "source_controlled_executor_quarantine_proof_id": quarantine.get( + "quarantine_proof_id" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_frozen_envelope_verifier_handoff": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_frozen_envelope_verifier_handoff_closeout": ( + closeout_ready + ), + "execution_envelope_freeze_proof_closeout_ready": closeout_ready, + "frozen_envelope_verifier_handoff_bound": closeout_ready, + "controlled_executor_quarantine_bound": True, + "executor_quarantine_enforced": True, + "execution_envelope_frozen": True, + "execution_envelope_mutation_allowed": False, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "dry_run_execution_performed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "reads_secret_in_preview": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_execution_envelope_freeze_proof_closeout = { + "execution_envelope_freeze_proof_closeout_id": closeout_id, + "authorization_material_type": ( + "controlled_dry_run_execution_envelope_freeze_proof_closeout" + ), + "source_controlled_executor_quarantine_proof_closeout_id": source_closeout_id, + "source_dry_run_execution_envelope_freeze_proof_id": freeze_proof.get( + "freeze_proof_id" + ), + "source_controlled_executor_quarantine_proof_id": quarantine.get( + "quarantine_proof_id" + ), + "source_final_no_runner_execution_proof_closeout_id": ( + source_quarantine_closeout.get("final_no_runner_execution_proof_closeout_id") + ), + "source_final_no_runner_execution_proof_id": final_proof.get("proof_id"), + "source_no_execution_receipt_handoff_closeout_id": handoff_closeout.get( + "no_execution_receipt_handoff_closeout_id" + ), + "source_no_execution_receipt_handoff_id": handoff.get("handoff_id"), + "source_runner_invocation_boundary_closeout_id": boundary_closeout.get( + "runner_invocation_boundary_closeout_id" + ), + "required_command_shape_hash": freeze_proof.get("required_command_shape_hash"), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_execution_envelope_freeze_proof_closeout": ( + closeout_ready + ), + "execution_envelope_freeze_proof_closeout_fields": freeze_closeout_fields, + "execution_envelope_freeze_proof_closeout_field_count": len( + freeze_closeout_fields + ), + "execution_envelope_freeze_proof_closeout_acceptance_gates": ( + freeze_closeout_acceptance_gates + ), + "execution_envelope_freeze_proof_closeout_acceptance_gate_count": len( + freeze_closeout_acceptance_gates + ), + "frozen_envelope_verifier_handoff": frozen_envelope_verifier_handoff, + "frozen_envelope_verifier_handoff_count": 1, + "frozen_envelope_verifier_handoff_field_count": len( + frozen_envelope_verifier_handoff_fields + ), + "dry_run_execution_envelope_freeze_proof": freeze_proof, + "dry_run_execution_envelope_freeze_proof_count": 1, + "controlled_executor_quarantine_proof_closeout": freeze_closeout, + "controlled_executor_quarantine_proof_closeout_count": 1, + "controlled_executor_quarantine_proof": quarantine, + "controlled_executor_quarantine_proof_count": 1, + "final_no_runner_execution_proof_closeout": source_quarantine_closeout, + "final_no_runner_execution_proof_closeout_count": 1, + "final_no_runner_execution_proof": final_proof, + "final_no_runner_execution_proof_count": 1, + "no_execution_receipt_handoff_closeout": handoff_closeout, + "no_execution_receipt_handoff_closeout_count": 1, + "target_file": freeze_closeout.get("target_file"), + "expected_sha256": freeze_closeout.get("expected_sha256"), + "actual_sha256": freeze_closeout.get("actual_sha256"), + "hash_matches": freeze_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "execution_envelope_freeze_proof_closeout_only": True, + "frozen_envelope_verifier_handoff_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "controlled_executor_quarantine_bound": True, + "executor_quarantine_enforced": True, + "execution_envelope_frozen": True, + "execution_envelope_mutation_allowed": False, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + } + controlled_dry_run_execution_envelope_freeze_proof_closeout_contract = { + "mode": "controlled_dry_run_execution_envelope_freeze_proof_closeout_and_frozen_envelope_verifier_handoff_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-execution-envelope-freeze-proof-closeout" + ), + "source_controlled_executor_quarantine_proof_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-controlled-executor-quarantine-proof-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_frozen_envelope_verifier_handoff": ( + closeout_ready + ), + "ready_for_database_apply_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": False, + "signs_database_apply_authorization": False, + "writes_database": False, + "executes_in_preview": False, + "secret_material_required_in_preview": False, + "manual_review_mode": "exception_only", + } + output_summary = dict(summary) + output_summary.update( + { + "controlled_dry_run_execution_envelope_freeze_proof_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_execution_envelope_freeze_proof_closeout_check_count": len( + checks + ), + "controlled_dry_run_execution_envelope_freeze_proof_closeout_pass_count": ( + passed_count + ), + "controlled_dry_run_execution_envelope_freeze_proof_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_execution_envelope_freeze_proof_closeout_count": 1, + "controlled_dry_run_execution_envelope_freeze_proof_closeout_field_count": len( + freeze_closeout_fields + ), + "controlled_dry_run_execution_envelope_freeze_proof_closeout_acceptance_gate_count": len( + freeze_closeout_acceptance_gates + ), + "frozen_envelope_verifier_handoff_count": 1, + "frozen_envelope_verifier_handoff_field_count": len( + frozen_envelope_verifier_handoff_fields + ), + "verifier_invoked_count": 0, + "verifier_receipt_present_count": 0, + "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, + "dry_run_executor_invoked_count": 0, + "runner_invocation_performed_count": 0, + "endpoint_executed_count": 0, + "sql_executed_count": 0, + "database_written_count": 0, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + } + ) + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_EXECUTION_ENVELOPE_FREEZE_PROOF_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(freeze_closeout_result.get("success")), + "generated_at": freeze_closeout_result.get("generated_at"), + "source_policy": freeze_closeout_result.get("policy"), + "stats": freeze_closeout_result.get("stats") or {}, + "summary": output_summary, + "future_database_apply_controlled_dry_run_frozen_envelope_verifier_handoff": ( + future_database_apply_controlled_dry_run_frozen_envelope_verifier_handoff + ), + "controlled_dry_run_execution_envelope_freeze_proof_closeout": ( + controlled_dry_run_execution_envelope_freeze_proof_closeout + ), + "controlled_dry_run_execution_envelope_freeze_proof_closeout_contract": ( + controlled_dry_run_execution_envelope_freeze_proof_closeout_contract + ), + "controlled_dry_run_execution_envelope_freeze_proof_closeout_checks": ( + checks + ), + "source_controlled_dry_run_controlled_executor_quarantine_proof_closeout_summary": ( + summary + ), + "source_controlled_dry_run_controlled_executor_quarantine_proof_closeout_contract": ( + freeze_contract + ), + "source_controlled_dry_run_controlled_executor_quarantine_proof_closeout": ( + freeze_closeout + ), + "source_database_apply_controlled_dry_run_execution_envelope_freeze_proof": ( + future_freeze + ), + "safety": { + "read_only_db_apply_controlled_dry_run_execution_envelope_freeze_proof_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future frozen envelope verifier handoff closeout.", + "Keep verifier invocation disabled until a later lane explicitly proves the verifier boundary.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_frozen_envelope_verifier_handoff_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out the verifier handoff while keeping verifier invocation locked.""" + handoff_closeout_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_execution_envelope_freeze_proof_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_handoff = ( + handoff_closeout_result.get( + "future_database_apply_controlled_dry_run_frozen_envelope_verifier_handoff" + ) + or {} + ) + handoff_closeout = ( + handoff_closeout_result.get( + "controlled_dry_run_execution_envelope_freeze_proof_closeout" + ) + or {} + ) + handoff_contract = ( + handoff_closeout_result.get( + "controlled_dry_run_execution_envelope_freeze_proof_closeout_contract" + ) + or {} + ) + summary = handoff_closeout_result.get("summary") or {} + safety = handoff_closeout_result.get("safety") or {} + frozen_handoff = handoff_closeout.get("frozen_envelope_verifier_handoff") or {} + freeze_proof = ( + handoff_closeout.get("dry_run_execution_envelope_freeze_proof") or {} + ) + source_freeze_closeout = ( + handoff_closeout.get("controlled_executor_quarantine_proof_closeout") or {} + ) + quarantine = handoff_closeout.get("controlled_executor_quarantine_proof") or {} + source_quarantine_closeout = ( + handoff_closeout.get("final_no_runner_execution_proof_closeout") or {} + ) + final_proof = handoff_closeout.get("final_no_runner_execution_proof") or {} + handoff_receipt_closeout = ( + handoff_closeout.get("no_execution_receipt_handoff_closeout") or {} + ) + rollback_binding = handoff_closeout.get("rollback_binding") or {} + verifier_binding = handoff_closeout.get("post_apply_verifier_binding") or {} + source_closeout_id = handoff_closeout.get( + "execution_envelope_freeze_proof_closeout_id" + ) + closeout_id = ( + _db_apply_controlled_dry_run_frozen_envelope_verifier_handoff_closeout_id( + handoff_closeout_result + ) + ) + lock_id = f"{closeout_id}-verifier-invocation-lock-proof" + handoff_closeout_fields = [ + "frozen_envelope_verifier_handoff_closeout_id", + "source_execution_envelope_freeze_proof_closeout_id", + "source_frozen_envelope_verifier_handoff_id", + "source_dry_run_execution_envelope_freeze_proof_id", + "verifier_invocation_lock_proof_id", + "required_command_shape_hash", + "target_migration_file", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "verifier_invocation_locked", + "verifier_invocation_allowed", + "abort_conditions", + ] + handoff_closeout_acceptance_gates = [ + "execution_envelope_freeze_proof_closeout_ready", + "source_chain_ids_match", + "frozen_envelope_verifier_handoff_ready", + "frozen_envelope_verifier_handoff_no_execute", + "verifier_invocation_lock_proof_bound", + "verifier_invocation_lock_proof_blocks_execution", + "previous_closeouts_carried_forward", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "no_secret_signature_or_database_apply", + ] + verifier_invocation_lock_proof_fields = [ + "lock_proof_id", + "source_frozen_envelope_verifier_handoff_closeout_id", + "source_frozen_envelope_verifier_handoff_id", + "source_execution_envelope_freeze_proof_closeout_id", + "source_dry_run_execution_envelope_freeze_proof_id", + "lock_mode", + "verifier_invocation_locked", + "verifier_invocation_allowed", + "dry_run_executor_invocation_allowed", + "endpoint_execution_allowed", + "sql_execution_allowed", + "database_apply_authorized", + ] + abort_conditions = [ + "abort_if_execution_envelope_freeze_proof_closeout_not_ready", + "abort_if_source_chain_ids_do_not_match", + "abort_if_frozen_envelope_verifier_handoff_missing", + "abort_if_frozen_envelope_verifier_handoff_reports_execution", + "abort_if_verifier_invocation_lock_proof_missing", + "abort_if_verifier_invocation_is_allowed", + "abort_if_endpoint_or_sql_execution_is_allowed", + "abort_if_database_write_or_apply_is_allowed", + "abort_if_target_migration_hash_changes", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_any_secret_signature_or_apply_material_is_present", + ] + verifier_invocation_lock_proof = { + "lock_proof_id": lock_id, + "source_frozen_envelope_verifier_handoff_closeout_id": closeout_id, + "source_frozen_envelope_verifier_handoff_id": frozen_handoff.get( + "handoff_id" + ), + "source_execution_envelope_freeze_proof_closeout_id": source_closeout_id, + "source_dry_run_execution_envelope_freeze_proof_id": freeze_proof.get( + "freeze_proof_id" + ), + "source_controlled_executor_quarantine_proof_closeout_id": ( + source_freeze_closeout.get("controlled_executor_quarantine_proof_closeout_id") + ), + "source_controlled_executor_quarantine_proof_id": quarantine.get( + "quarantine_proof_id" + ), + "source_final_no_runner_execution_proof_closeout_id": ( + source_quarantine_closeout.get("final_no_runner_execution_proof_closeout_id") + ), + "source_final_no_runner_execution_proof_id": final_proof.get("proof_id"), + "source_no_execution_receipt_handoff_closeout_id": ( + handoff_receipt_closeout.get("no_execution_receipt_handoff_closeout_id") + ), + "required_command_shape_hash": freeze_proof.get("required_command_shape_hash"), + "lock_status": "verifier_invocation_lock_proof_preview_ready", + "lock_mode": "verifier_invocation_lock_proof_preview_only", + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "verifier_receipt_required": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "execution_receipt_required": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "ready_for_database_apply_now": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "database_apply_authorized": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "verifier_invocation_lock_proof_field_count": len( + verifier_invocation_lock_proof_fields + ), + "verifier_invocation_lock_proof_fields": verifier_invocation_lock_proof_fields, + } + handoff_closeout_ready = ( + handoff_closeout_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_EXECUTION_ENVELOPE_FREEZE_PROOF_CLOSEOUT_READY" + and summary.get( + "controlled_dry_run_execution_envelope_freeze_proof_closeout_ready_count" + ) + == 1 + and summary.get( + "controlled_dry_run_execution_envelope_freeze_proof_closeout_pass_count" + ) + == summary.get( + "controlled_dry_run_execution_envelope_freeze_proof_closeout_check_count" + ) + ) + source_chain_ids_match = ( + bool(source_closeout_id) + and source_closeout_id + == future_handoff.get("execution_envelope_freeze_proof_closeout_id") + == frozen_handoff.get("source_execution_envelope_freeze_proof_closeout_id") + == verifier_invocation_lock_proof.get( + "source_execution_envelope_freeze_proof_closeout_id" + ) + and frozen_handoff.get("handoff_id") + == future_handoff.get("frozen_envelope_verifier_handoff_id") + == verifier_invocation_lock_proof.get( + "source_frozen_envelope_verifier_handoff_id" + ) + and freeze_proof.get("freeze_proof_id") + == future_handoff.get("source_dry_run_execution_envelope_freeze_proof_id") + == frozen_handoff.get("source_dry_run_execution_envelope_freeze_proof_id") + == verifier_invocation_lock_proof.get( + "source_dry_run_execution_envelope_freeze_proof_id" + ) + and quarantine.get("quarantine_proof_id") + == future_handoff.get("source_controlled_executor_quarantine_proof_id") + == frozen_handoff.get("source_controlled_executor_quarantine_proof_id") + == verifier_invocation_lock_proof.get( + "source_controlled_executor_quarantine_proof_id" + ) + and final_proof.get("proof_id") + == frozen_handoff.get("source_final_no_runner_execution_proof_id") + == verifier_invocation_lock_proof.get( + "source_final_no_runner_execution_proof_id" + ) + and handoff_receipt_closeout.get("no_execution_receipt_handoff_closeout_id") + == frozen_handoff.get("source_no_execution_receipt_handoff_closeout_id") + == verifier_invocation_lock_proof.get( + "source_no_execution_receipt_handoff_closeout_id" + ) + ) + frozen_envelope_verifier_handoff_ready = ( + handoff_closeout_ready + and frozen_handoff.get("handoff_status") + == "frozen_envelope_verifier_handoff_preview_ready" + and frozen_handoff.get("handoff_id") + == future_handoff.get("frozen_envelope_verifier_handoff_id") + and int( + frozen_handoff.get("frozen_envelope_verifier_handoff_field_count") + or 0 + ) + == 12 + and summary.get("frozen_envelope_verifier_handoff_count") == 1 + ) + frozen_envelope_verifier_handoff_no_execute = ( + frozen_handoff.get("verifier_handoff_mode") + == "frozen_envelope_verifier_handoff_preview_only" + and frozen_handoff.get("execution_envelope_frozen") is True + and frozen_handoff.get("execution_envelope_mutation_allowed") is False + and frozen_handoff.get("verifier_invocation_allowed") is False + and frozen_handoff.get("verifier_invoked") is False + and frozen_handoff.get("verifier_receipt_present") is False + and frozen_handoff.get("verifier_receipt_required") is False + and frozen_handoff.get("dry_run_executor_invoked") is False + and frozen_handoff.get("runner_invocation_performed") is False + and frozen_handoff.get("endpoint_executed") is False + and frozen_handoff.get("sql_executed") is False + and frozen_handoff.get("database_written") is False + and frozen_handoff.get("dry_run_executor_invocation_allowed") is False + and frozen_handoff.get("runner_invocation_allowed") is False + and frozen_handoff.get("ready_for_verifier_invocation_now") is False + and frozen_handoff.get("ready_for_dry_run_executor_invocation_now") is False + and frozen_handoff.get("ready_for_actual_dry_run_execution_now") is False + and frozen_handoff.get("endpoint_execution_allowed") is False + and frozen_handoff.get("sql_execution_allowed") is False + and frozen_handoff.get("database_write_allowed") is False + and frozen_handoff.get("database_apply_authorized") is False + and frozen_handoff.get("executes_database_apply") is False + and frozen_handoff.get("executes_endpoint") is False + and frozen_handoff.get("executes_sql") is False + and frozen_handoff.get("writes_database") is False + and frozen_handoff.get("captures_stdout") is False + and frozen_handoff.get("captures_stderr") is False + and frozen_handoff.get("stdout_included") is False + and frozen_handoff.get("stderr_included") is False + ) + verifier_invocation_lock_proof_bound = ( + frozen_envelope_verifier_handoff_ready + and bool(verifier_invocation_lock_proof.get("lock_proof_id")) + and verifier_invocation_lock_proof.get( + "source_frozen_envelope_verifier_handoff_closeout_id" + ) + == closeout_id + and verifier_invocation_lock_proof.get( + "source_frozen_envelope_verifier_handoff_id" + ) + == frozen_handoff.get("handoff_id") + and verifier_invocation_lock_proof.get("required_command_shape_hash") + == freeze_proof.get("required_command_shape_hash") + and int( + verifier_invocation_lock_proof.get( + "verifier_invocation_lock_proof_field_count" + ) + or 0 + ) + == len(verifier_invocation_lock_proof_fields) + ) + verifier_invocation_lock_proof_blocks_execution = ( + verifier_invocation_lock_proof.get("lock_mode") + == "verifier_invocation_lock_proof_preview_only" + and verifier_invocation_lock_proof.get("verifier_invocation_locked") is True + and verifier_invocation_lock_proof.get("verifier_invocation_allowed") + is False + and verifier_invocation_lock_proof.get("verifier_invoked") is False + and verifier_invocation_lock_proof.get("verifier_receipt_present") is False + and verifier_invocation_lock_proof.get("verifier_receipt_required") is False + and verifier_invocation_lock_proof.get("dry_run_executor_invoked") is False + and verifier_invocation_lock_proof.get("runner_invocation_performed") + is False + and verifier_invocation_lock_proof.get("endpoint_executed") is False + and verifier_invocation_lock_proof.get("sql_executed") is False + and verifier_invocation_lock_proof.get("database_written") is False + and verifier_invocation_lock_proof.get("execution_receipt_present") is False + and verifier_invocation_lock_proof.get("execution_receipt_required") is False + and verifier_invocation_lock_proof.get("dry_run_executor_invocation_allowed") + is False + and verifier_invocation_lock_proof.get("runner_invocation_allowed") is False + and verifier_invocation_lock_proof.get("ready_for_verifier_invocation_now") + is False + and verifier_invocation_lock_proof.get( + "ready_for_dry_run_executor_invocation_now" + ) + is False + and verifier_invocation_lock_proof.get("ready_for_actual_dry_run_execution_now") + is False + and verifier_invocation_lock_proof.get("endpoint_execution_allowed") is False + and verifier_invocation_lock_proof.get("sql_execution_allowed") is False + and verifier_invocation_lock_proof.get("database_write_allowed") is False + and verifier_invocation_lock_proof.get("database_apply_authorized") is False + and verifier_invocation_lock_proof.get("executes_database_apply") is False + and verifier_invocation_lock_proof.get("executes_endpoint") is False + and verifier_invocation_lock_proof.get("executes_sql") is False + and verifier_invocation_lock_proof.get("writes_database") is False + and verifier_invocation_lock_proof.get("captures_stdout") is False + and verifier_invocation_lock_proof.get("captures_stderr") is False + and verifier_invocation_lock_proof.get("stdout_included") is False + and verifier_invocation_lock_proof.get("stderr_included") is False + ) + previous_closeouts_carried_forward = ( + handoff_closeout.get("execution_envelope_freeze_proof_closeout_only") is True + and handoff_closeout.get("frozen_envelope_verifier_handoff_only") is True + and handoff_closeout.get("database_apply_authorized") is False + and source_freeze_closeout.get("controlled_executor_quarantine_proof_closeout_only") + is True + and source_freeze_closeout.get("dry_run_execution_envelope_freeze_proof_only") + is True + and source_quarantine_closeout.get( + "final_no_runner_execution_proof_closeout_only" + ) + is True + and handoff_receipt_closeout.get("no_execution_receipt_handoff_closeout_only") + is True + and frozen_envelope_verifier_handoff_no_execute + ) + target_hash_locked = ( + handoff_closeout.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(handoff_closeout.get("expected_sha256")) + and bool(handoff_closeout.get("actual_sha256")) + and handoff_closeout.get("expected_sha256") + == handoff_closeout.get("actual_sha256") + and handoff_closeout.get("hash_matches") is True + and handoff_closeout.get("target_migration_hash_locked") is True + ) + rollback_and_verifier_bound = ( + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False + ) + handoff_contract_blocks_database_apply = ( + handoff_contract.get("executes_database_apply") is False + and handoff_contract.get("executes_endpoint") is False + and handoff_contract.get("executes_sql") is False + and handoff_contract.get("database_apply_authorized") is False + and handoff_contract.get("ready_for_database_apply_now") is False + and handoff_contract.get("ready_for_verifier_invocation_now") is False + and handoff_contract.get("ready_for_dry_run_executor_invocation_now") is False + and handoff_contract.get("ready_for_actual_dry_run_execution_now") is False + and handoff_contract.get("signs_database_apply_authorization") is False + and handoff_contract.get("writes_database") is False + and handoff_contract.get("executes_in_preview") is False + ) + side_effect_free = ( + summary.get("reads_secret_count", 0) == 0 + and summary.get("executes_script_count", 0) == 0 + and summary.get("executes_migration_count", 0) == 0 + and summary.get("executes_endpoint_count", 0) == 0 + and summary.get("executes_sql_count", 0) == 0 + and summary.get("writes_database_count", 0) == 0 + and summary.get("signs_database_apply_authorization_count", 0) == 0 + and summary.get("verifier_invoked_count", 0) == 0 + and summary.get("verifier_receipt_present_count", 0) == 0 + and summary.get("dry_run_executor_invoked_count", 0) == 0 + and summary.get("runner_invocation_performed_count", 0) == 0 + and summary.get("endpoint_executed_count", 0) == 0 + and summary.get("sql_executed_count", 0) == 0 + and summary.get("database_written_count", 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("executes_database_apply") is False + and frozen_envelope_verifier_handoff_no_execute + and verifier_invocation_lock_proof_blocks_execution + ) + checks = [ + _controlled_dry_run_frozen_envelope_verifier_handoff_closeout_check( + "execution_envelope_freeze_proof_closeout_ready", + handoff_closeout_ready, + { + "result": handoff_closeout_result.get("result"), + "ready_count": summary.get( + "controlled_dry_run_execution_envelope_freeze_proof_closeout_ready_count" + ), + "pass_count": summary.get( + "controlled_dry_run_execution_envelope_freeze_proof_closeout_pass_count" + ), + "check_count": summary.get( + "controlled_dry_run_execution_envelope_freeze_proof_closeout_check_count" + ), + }, + "wait_for_execution_envelope_freeze_proof_closeout_ready", + ), + _controlled_dry_run_frozen_envelope_verifier_handoff_closeout_check( + "source_chain_ids_match", + source_chain_ids_match, + { + "execution_envelope_freeze_proof_closeout_id": source_closeout_id, + "frozen_envelope_verifier_handoff_id": frozen_handoff.get( + "handoff_id" + ), + "verifier_invocation_lock_proof_id": ( + verifier_invocation_lock_proof.get("lock_proof_id") + ), + }, + "wait_for_source_chain_ids_match", + ), + _controlled_dry_run_frozen_envelope_verifier_handoff_closeout_check( + "frozen_envelope_verifier_handoff_ready", + frozen_envelope_verifier_handoff_ready, + { + "handoff_id": frozen_handoff.get("handoff_id"), + "handoff_status": frozen_handoff.get("handoff_status"), + "field_count": frozen_handoff.get( + "frozen_envelope_verifier_handoff_field_count" + ), + }, + "wait_for_frozen_envelope_verifier_handoff_ready", + ), + _controlled_dry_run_frozen_envelope_verifier_handoff_closeout_check( + "frozen_envelope_verifier_handoff_no_execute", + frozen_envelope_verifier_handoff_no_execute, + { + "verifier_handoff_mode": frozen_handoff.get( + "verifier_handoff_mode" + ), + "verifier_invocation_allowed": frozen_handoff.get( + "verifier_invocation_allowed" + ), + "verifier_invoked": frozen_handoff.get("verifier_invoked"), + }, + "abort_if_frozen_envelope_verifier_handoff_reports_execution", + ), + _controlled_dry_run_frozen_envelope_verifier_handoff_closeout_check( + "verifier_invocation_lock_proof_bound", + verifier_invocation_lock_proof_bound, + { + "lock_proof_id": verifier_invocation_lock_proof.get( + "lock_proof_id" + ), + "source_frozen_envelope_verifier_handoff_id": ( + verifier_invocation_lock_proof.get( + "source_frozen_envelope_verifier_handoff_id" + ) + ), + "field_count": verifier_invocation_lock_proof.get( + "verifier_invocation_lock_proof_field_count" + ), + }, + "wait_for_verifier_invocation_lock_proof_binding", + ), + _controlled_dry_run_frozen_envelope_verifier_handoff_closeout_check( + "verifier_invocation_lock_proof_blocks_execution", + verifier_invocation_lock_proof_blocks_execution, + { + "lock_mode": verifier_invocation_lock_proof.get("lock_mode"), + "verifier_invocation_locked": verifier_invocation_lock_proof.get( + "verifier_invocation_locked" + ), + "verifier_invocation_allowed": verifier_invocation_lock_proof.get( + "verifier_invocation_allowed" + ), + }, + "abort_if_verifier_invocation_lock_proof_allows_execution", + ), + _controlled_dry_run_frozen_envelope_verifier_handoff_closeout_check( + "previous_closeouts_carried_forward", + previous_closeouts_carried_forward, + { + "execution_envelope_freeze_proof_closeout_only": ( + handoff_closeout.get( + "execution_envelope_freeze_proof_closeout_only" + ) + ), + "frozen_envelope_verifier_handoff_only": ( + handoff_closeout.get("frozen_envelope_verifier_handoff_only") + ), + "lock_mode": verifier_invocation_lock_proof.get("lock_mode"), + }, + "wait_for_previous_closeouts_carry_forward", + ), + _controlled_dry_run_frozen_envelope_verifier_handoff_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": handoff_closeout.get("target_file"), + "hash_matches": handoff_closeout.get("hash_matches"), + "expected_sha256_present": bool(handoff_closeout.get("expected_sha256")), + "actual_sha256_present": bool(handoff_closeout.get("actual_sha256")), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_frozen_envelope_verifier_handoff_closeout_check( + "rollback_and_post_apply_verifier_bound", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "database_apply_authorized": verifier_binding.get( + "database_apply_authorized" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_frozen_envelope_verifier_handoff_closeout_check( + "execution_envelope_freeze_proof_closeout_contract_blocks_database_apply", + handoff_contract_blocks_database_apply, + { + "permits_future_database_apply_controlled_dry_run_frozen_envelope_verifier_handoff": ( + handoff_contract.get( + "permits_future_database_apply_controlled_dry_run_frozen_envelope_verifier_handoff" + ) + ), + "database_apply_authorized": handoff_contract.get( + "database_apply_authorized" + ), + "writes_database": handoff_contract.get("writes_database"), + }, + "abort_if_execution_envelope_freeze_proof_closeout_contract_authorizes_database_apply", + ), + _controlled_dry_run_frozen_envelope_verifier_handoff_closeout_check( + "preview_has_no_side_effects_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "verifier_invoked_count": summary.get("verifier_invoked_count", 0), + "signs_database_apply_authorization_count": summary.get( + "signs_database_apply_authorization_count", 0 + ), + }, + "abort_on_preview_side_effect_execution_or_signing", + ), + _controlled_dry_run_frozen_envelope_verifier_handoff_closeout_check( + "manual_review_not_required_for_safe_preview", + handoff_contract.get("manual_review_mode") == "exception_only" + and safety.get("manual_review_mode") == "exception_only", + { + "contract_manual_review_mode": handoff_contract.get( + "manual_review_mode" + ), + "safety_manual_review_mode": safety.get("manual_review_mode"), + }, + "keep_manual_review_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_FROZEN_ENVELOPE_VERIFIER_HANDOFF_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_EXECUTION_ENVELOPE_FREEZE_PROOF_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_verifier_invocation_lock_proof = { + "frozen_envelope_verifier_handoff_closeout_id": closeout_id, + "verifier_invocation_lock_proof_id": lock_id, + "source_execution_envelope_freeze_proof_closeout_id": source_closeout_id, + "source_frozen_envelope_verifier_handoff_id": frozen_handoff.get( + "handoff_id" + ), + "source_dry_run_execution_envelope_freeze_proof_id": freeze_proof.get( + "freeze_proof_id" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_verifier_invocation_lock_proof": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_verifier_invocation_lock_proof_closeout": ( + closeout_ready + ), + "frozen_envelope_verifier_handoff_closeout_ready": closeout_ready, + "execution_envelope_freeze_proof_closeout_ready": handoff_closeout_ready, + "frozen_envelope_verifier_handoff_ready": ( + frozen_envelope_verifier_handoff_ready + ), + "verifier_invocation_lock_proof_bound": closeout_ready, + "execution_envelope_frozen": True, + "execution_envelope_mutation_allowed": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_frozen_envelope_verifier_handoff_closeout = { + "frozen_envelope_verifier_handoff_closeout_id": closeout_id, + "authorization_material_type": ( + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout" + ), + "source_execution_envelope_freeze_proof_closeout_id": source_closeout_id, + "source_frozen_envelope_verifier_handoff_id": frozen_handoff.get( + "handoff_id" + ), + "source_dry_run_execution_envelope_freeze_proof_id": freeze_proof.get( + "freeze_proof_id" + ), + "source_controlled_executor_quarantine_proof_closeout_id": ( + source_freeze_closeout.get("controlled_executor_quarantine_proof_closeout_id") + ), + "source_controlled_executor_quarantine_proof_id": quarantine.get( + "quarantine_proof_id" + ), + "source_final_no_runner_execution_proof_closeout_id": ( + source_quarantine_closeout.get("final_no_runner_execution_proof_closeout_id") + ), + "source_final_no_runner_execution_proof_id": final_proof.get("proof_id"), + "source_no_execution_receipt_handoff_closeout_id": ( + handoff_receipt_closeout.get("no_execution_receipt_handoff_closeout_id") + ), + "required_command_shape_hash": freeze_proof.get("required_command_shape_hash"), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_verifier_invocation_lock_proof": ( + closeout_ready + ), + "frozen_envelope_verifier_handoff_closeout_fields": ( + handoff_closeout_fields + ), + "frozen_envelope_verifier_handoff_closeout_field_count": len( + handoff_closeout_fields + ), + "frozen_envelope_verifier_handoff_closeout_acceptance_gates": ( + handoff_closeout_acceptance_gates + ), + "frozen_envelope_verifier_handoff_closeout_acceptance_gate_count": len( + handoff_closeout_acceptance_gates + ), + "verifier_invocation_lock_proof": verifier_invocation_lock_proof, + "verifier_invocation_lock_proof_count": 1, + "verifier_invocation_lock_proof_field_count": len( + verifier_invocation_lock_proof_fields + ), + "frozen_envelope_verifier_handoff": frozen_handoff, + "frozen_envelope_verifier_handoff_count": 1, + "frozen_envelope_verifier_handoff_field_count": int( + frozen_handoff.get("frozen_envelope_verifier_handoff_field_count") or 0 + ), + "dry_run_execution_envelope_freeze_proof": freeze_proof, + "dry_run_execution_envelope_freeze_proof_count": 1, + "execution_envelope_freeze_proof_closeout": handoff_closeout, + "execution_envelope_freeze_proof_closeout_count": 1, + "controlled_executor_quarantine_proof_closeout": source_freeze_closeout, + "controlled_executor_quarantine_proof_closeout_count": 1, + "controlled_executor_quarantine_proof": quarantine, + "controlled_executor_quarantine_proof_count": 1, + "final_no_runner_execution_proof_closeout": source_quarantine_closeout, + "final_no_runner_execution_proof_closeout_count": 1, + "final_no_runner_execution_proof": final_proof, + "final_no_runner_execution_proof_count": 1, + "no_execution_receipt_handoff_closeout": handoff_receipt_closeout, + "no_execution_receipt_handoff_closeout_count": 1, + "target_file": handoff_closeout.get("target_file"), + "expected_sha256": handoff_closeout.get("expected_sha256"), + "actual_sha256": handoff_closeout.get("actual_sha256"), + "hash_matches": handoff_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "frozen_envelope_verifier_handoff_closeout_only": True, + "verifier_invocation_lock_proof_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "controlled_executor_quarantine_bound": True, + "executor_quarantine_enforced": True, + "execution_envelope_frozen": True, + "execution_envelope_mutation_allowed": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + } + controlled_dry_run_frozen_envelope_verifier_handoff_closeout_contract = { + "mode": "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_and_verifier_invocation_lock_proof_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-frozen-envelope-verifier-handoff-closeout" + ), + "source_execution_envelope_freeze_proof_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-execution-envelope-freeze-proof-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_verifier_invocation_lock_proof": ( + closeout_ready + ), + "verifier_invocation_locked": True, + "ready_for_database_apply_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": False, + "signs_database_apply_authorization": False, + "writes_database": False, + "executes_in_preview": False, + "secret_material_required_in_preview": False, + "manual_review_mode": "exception_only", + } + output_summary = dict(summary) + output_summary.update( + { + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_check_count": len( + checks + ), + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_pass_count": ( + passed_count + ), + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_count": 1, + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_field_count": len( + handoff_closeout_fields + ), + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_acceptance_gate_count": len( + handoff_closeout_acceptance_gates + ), + "verifier_invocation_lock_proof_count": 1, + "verifier_invocation_lock_proof_field_count": len( + verifier_invocation_lock_proof_fields + ), + "verifier_invocation_locked_count": 1, + "verifier_invoked_count": 0, + "verifier_receipt_present_count": 0, + "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, + "dry_run_executor_invoked_count": 0, + "runner_invocation_performed_count": 0, + "endpoint_executed_count": 0, + "sql_executed_count": 0, + "database_written_count": 0, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + } + ) + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_FROZEN_ENVELOPE_VERIFIER_HANDOFF_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(handoff_closeout_result.get("success")), + "generated_at": handoff_closeout_result.get("generated_at"), + "source_policy": handoff_closeout_result.get("policy"), + "stats": handoff_closeout_result.get("stats") or {}, + "summary": output_summary, + "future_database_apply_controlled_dry_run_verifier_invocation_lock_proof": ( + future_database_apply_controlled_dry_run_verifier_invocation_lock_proof + ), + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout": ( + controlled_dry_run_frozen_envelope_verifier_handoff_closeout + ), + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_contract": ( + controlled_dry_run_frozen_envelope_verifier_handoff_closeout_contract + ), + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_checks": checks, + "source_controlled_dry_run_execution_envelope_freeze_proof_closeout_summary": ( + summary + ), + "source_controlled_dry_run_execution_envelope_freeze_proof_closeout_contract": ( + handoff_contract + ), + "source_controlled_dry_run_execution_envelope_freeze_proof_closeout": ( + handoff_closeout + ), + "source_database_apply_controlled_dry_run_frozen_envelope_verifier_handoff": ( + future_handoff + ), + "safety": { + "read_only_db_apply_controlled_dry_run_frozen_envelope_verifier_handoff_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future verifier invocation lock proof closeout.", + "Keep verifier invocation disabled until a later no-execution receipt proof closes this lock boundary.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, verifier invocation, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_verifier_invocation_lock_proof_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out the verifier invocation lock and prove no verifier receipt exists.""" + lock_closeout_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_frozen_envelope_verifier_handoff_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_lock = ( + lock_closeout_result.get( + "future_database_apply_controlled_dry_run_verifier_invocation_lock_proof" + ) + or {} + ) + lock_closeout = ( + lock_closeout_result.get( + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout" + ) + or {} + ) + lock_contract = ( + lock_closeout_result.get( + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_contract" + ) + or {} + ) + summary = lock_closeout_result.get("summary") or {} + safety = lock_closeout_result.get("safety") or {} + lock_proof = lock_closeout.get("verifier_invocation_lock_proof") or {} + frozen_handoff = lock_closeout.get("frozen_envelope_verifier_handoff") or {} + source_handoff_closeout = ( + lock_closeout.get("execution_envelope_freeze_proof_closeout") or {} + ) + freeze_proof = ( + lock_closeout.get("dry_run_execution_envelope_freeze_proof") or {} + ) + source_freeze_closeout = ( + lock_closeout.get("controlled_executor_quarantine_proof_closeout") or {} + ) + quarantine = lock_closeout.get("controlled_executor_quarantine_proof") or {} + source_quarantine_closeout = ( + lock_closeout.get("final_no_runner_execution_proof_closeout") or {} + ) + final_proof = lock_closeout.get("final_no_runner_execution_proof") or {} + handoff_receipt_closeout = ( + lock_closeout.get("no_execution_receipt_handoff_closeout") or {} + ) + rollback_binding = lock_closeout.get("rollback_binding") or {} + verifier_binding = lock_closeout.get("post_apply_verifier_binding") or {} + source_closeout_id = lock_closeout.get( + "frozen_envelope_verifier_handoff_closeout_id" + ) + closeout_id = ( + _db_apply_controlled_dry_run_verifier_invocation_lock_proof_closeout_id( + lock_closeout_result + ) + ) + receipt_id = f"{closeout_id}-verifier-no-execution-receipt-proof" + lock_closeout_fields = [ + "verifier_invocation_lock_proof_closeout_id", + "source_frozen_envelope_verifier_handoff_closeout_id", + "source_verifier_invocation_lock_proof_id", + "source_frozen_envelope_verifier_handoff_id", + "verifier_no_execution_receipt_proof_id", + "required_command_shape_hash", + "target_migration_file", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "verifier_invocation_locked", + "verifier_invoked", + "abort_conditions", + ] + lock_closeout_acceptance_gates = [ + "frozen_envelope_verifier_handoff_closeout_ready", + "source_chain_ids_match", + "verifier_invocation_lock_proof_ready", + "verifier_invocation_lock_proof_no_execute", + "verifier_no_execution_receipt_proof_bound", + "verifier_no_execution_receipt_proof_blocks_execution", + "previous_closeouts_carried_forward", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "no_secret_signature_or_database_apply", + ] + verifier_no_execution_receipt_proof_fields = [ + "receipt_proof_id", + "source_verifier_invocation_lock_proof_closeout_id", + "source_verifier_invocation_lock_proof_id", + "source_frozen_envelope_verifier_handoff_closeout_id", + "source_frozen_envelope_verifier_handoff_id", + "receipt_mode", + "verifier_invocation_locked", + "verifier_invoked", + "verifier_receipt_present", + "endpoint_execution_allowed", + "sql_execution_allowed", + "database_apply_authorized", + ] + abort_conditions = [ + "abort_if_frozen_envelope_verifier_handoff_closeout_not_ready", + "abort_if_source_chain_ids_do_not_match", + "abort_if_verifier_invocation_lock_proof_missing", + "abort_if_verifier_invocation_lock_proof_reports_execution", + "abort_if_verifier_no_execution_receipt_proof_missing", + "abort_if_verifier_receipt_is_present", + "abort_if_endpoint_or_sql_execution_is_allowed", + "abort_if_database_write_or_apply_is_allowed", + "abort_if_target_migration_hash_changes", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_any_secret_signature_or_apply_material_is_present", + ] + verifier_no_execution_receipt_proof = { + "receipt_proof_id": receipt_id, + "source_verifier_invocation_lock_proof_closeout_id": closeout_id, + "source_verifier_invocation_lock_proof_id": lock_proof.get("lock_proof_id"), + "source_frozen_envelope_verifier_handoff_closeout_id": source_closeout_id, + "source_frozen_envelope_verifier_handoff_id": frozen_handoff.get( + "handoff_id" + ), + "source_execution_envelope_freeze_proof_closeout_id": ( + source_handoff_closeout.get("execution_envelope_freeze_proof_closeout_id") + ), + "source_dry_run_execution_envelope_freeze_proof_id": freeze_proof.get( + "freeze_proof_id" + ), + "source_controlled_executor_quarantine_proof_closeout_id": ( + source_freeze_closeout.get("controlled_executor_quarantine_proof_closeout_id") + ), + "source_controlled_executor_quarantine_proof_id": quarantine.get( + "quarantine_proof_id" + ), + "source_final_no_runner_execution_proof_closeout_id": ( + source_quarantine_closeout.get("final_no_runner_execution_proof_closeout_id") + ), + "source_final_no_runner_execution_proof_id": final_proof.get("proof_id"), + "source_no_execution_receipt_handoff_closeout_id": ( + handoff_receipt_closeout.get("no_execution_receipt_handoff_closeout_id") + ), + "required_command_shape_hash": freeze_proof.get("required_command_shape_hash"), + "receipt_status": "verifier_no_execution_receipt_proof_preview_ready", + "receipt_mode": "verifier_no_execution_receipt_proof_preview_only", + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "verifier_receipt_required": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "execution_receipt_required": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "ready_for_database_apply_now": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "database_apply_authorized": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "verifier_no_execution_receipt_proof_field_count": len( + verifier_no_execution_receipt_proof_fields + ), + "verifier_no_execution_receipt_proof_fields": ( + verifier_no_execution_receipt_proof_fields + ), + } + lock_closeout_ready = ( + lock_closeout_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_FROZEN_ENVELOPE_VERIFIER_HANDOFF_CLOSEOUT_READY" + and summary.get( + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_ready_count" + ) + == 1 + and summary.get( + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_pass_count" + ) + == summary.get( + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_check_count" + ) + ) + source_chain_ids_match = ( + bool(source_closeout_id) + and source_closeout_id + == future_lock.get("frozen_envelope_verifier_handoff_closeout_id") + == lock_proof.get("source_frozen_envelope_verifier_handoff_closeout_id") + == verifier_no_execution_receipt_proof.get( + "source_frozen_envelope_verifier_handoff_closeout_id" + ) + and lock_proof.get("lock_proof_id") + == future_lock.get("verifier_invocation_lock_proof_id") + == verifier_no_execution_receipt_proof.get( + "source_verifier_invocation_lock_proof_id" + ) + and frozen_handoff.get("handoff_id") + == future_lock.get("source_frozen_envelope_verifier_handoff_id") + == lock_proof.get("source_frozen_envelope_verifier_handoff_id") + == verifier_no_execution_receipt_proof.get( + "source_frozen_envelope_verifier_handoff_id" + ) + and source_handoff_closeout.get("execution_envelope_freeze_proof_closeout_id") + == future_lock.get("source_execution_envelope_freeze_proof_closeout_id") + == lock_proof.get("source_execution_envelope_freeze_proof_closeout_id") + == verifier_no_execution_receipt_proof.get( + "source_execution_envelope_freeze_proof_closeout_id" + ) + and freeze_proof.get("freeze_proof_id") + == future_lock.get("source_dry_run_execution_envelope_freeze_proof_id") + == lock_proof.get("source_dry_run_execution_envelope_freeze_proof_id") + == verifier_no_execution_receipt_proof.get( + "source_dry_run_execution_envelope_freeze_proof_id" + ) + and quarantine.get("quarantine_proof_id") + == lock_proof.get("source_controlled_executor_quarantine_proof_id") + == verifier_no_execution_receipt_proof.get( + "source_controlled_executor_quarantine_proof_id" + ) + and final_proof.get("proof_id") + == lock_proof.get("source_final_no_runner_execution_proof_id") + == verifier_no_execution_receipt_proof.get( + "source_final_no_runner_execution_proof_id" + ) + and handoff_receipt_closeout.get("no_execution_receipt_handoff_closeout_id") + == lock_proof.get("source_no_execution_receipt_handoff_closeout_id") + == verifier_no_execution_receipt_proof.get( + "source_no_execution_receipt_handoff_closeout_id" + ) + ) + verifier_invocation_lock_proof_ready = ( + lock_closeout_ready + and lock_proof.get("lock_status") + == "verifier_invocation_lock_proof_preview_ready" + and lock_proof.get("lock_proof_id") + == future_lock.get("verifier_invocation_lock_proof_id") + and int(lock_proof.get("verifier_invocation_lock_proof_field_count") or 0) + == 12 + and summary.get("verifier_invocation_lock_proof_count") == 1 + ) + verifier_invocation_lock_proof_no_execute = ( + lock_proof.get("lock_mode") == "verifier_invocation_lock_proof_preview_only" + and lock_proof.get("verifier_invocation_locked") is True + and lock_proof.get("verifier_invocation_allowed") is False + and lock_proof.get("verifier_invoked") is False + and lock_proof.get("verifier_receipt_present") is False + and lock_proof.get("verifier_receipt_required") is False + and lock_proof.get("dry_run_executor_invoked") is False + and lock_proof.get("runner_invocation_performed") is False + and lock_proof.get("endpoint_executed") is False + and lock_proof.get("sql_executed") is False + and lock_proof.get("database_written") is False + and lock_proof.get("execution_receipt_present") is False + and lock_proof.get("execution_receipt_required") is False + and lock_proof.get("dry_run_executor_invocation_allowed") is False + and lock_proof.get("runner_invocation_allowed") is False + and lock_proof.get("ready_for_verifier_invocation_now") is False + and lock_proof.get("ready_for_dry_run_executor_invocation_now") is False + and lock_proof.get("ready_for_actual_dry_run_execution_now") is False + and lock_proof.get("endpoint_execution_allowed") is False + and lock_proof.get("sql_execution_allowed") is False + and lock_proof.get("database_write_allowed") is False + and lock_proof.get("database_apply_authorized") is False + and lock_proof.get("executes_database_apply") is False + and lock_proof.get("executes_endpoint") is False + and lock_proof.get("executes_sql") is False + and lock_proof.get("writes_database") is False + and lock_proof.get("captures_stdout") is False + and lock_proof.get("captures_stderr") is False + and lock_proof.get("stdout_included") is False + and lock_proof.get("stderr_included") is False + ) + verifier_no_execution_receipt_proof_bound = ( + verifier_invocation_lock_proof_ready + and bool(verifier_no_execution_receipt_proof.get("receipt_proof_id")) + and verifier_no_execution_receipt_proof.get( + "source_verifier_invocation_lock_proof_closeout_id" + ) + == closeout_id + and verifier_no_execution_receipt_proof.get( + "source_verifier_invocation_lock_proof_id" + ) + == lock_proof.get("lock_proof_id") + and verifier_no_execution_receipt_proof.get("required_command_shape_hash") + == freeze_proof.get("required_command_shape_hash") + and int( + verifier_no_execution_receipt_proof.get( + "verifier_no_execution_receipt_proof_field_count" + ) + or 0 + ) + == len(verifier_no_execution_receipt_proof_fields) + ) + verifier_no_execution_receipt_proof_blocks_execution = ( + verifier_no_execution_receipt_proof.get("receipt_mode") + == "verifier_no_execution_receipt_proof_preview_only" + and verifier_no_execution_receipt_proof.get("verifier_invocation_locked") + is True + and verifier_no_execution_receipt_proof.get("verifier_invocation_allowed") + is False + and verifier_no_execution_receipt_proof.get("verifier_invoked") is False + and verifier_no_execution_receipt_proof.get("verifier_receipt_present") + is False + and verifier_no_execution_receipt_proof.get("verifier_receipt_required") + is False + and verifier_no_execution_receipt_proof.get("dry_run_executor_invoked") + is False + and verifier_no_execution_receipt_proof.get("runner_invocation_performed") + is False + and verifier_no_execution_receipt_proof.get("endpoint_executed") is False + and verifier_no_execution_receipt_proof.get("sql_executed") is False + and verifier_no_execution_receipt_proof.get("database_written") is False + and verifier_no_execution_receipt_proof.get("execution_receipt_present") + is False + and verifier_no_execution_receipt_proof.get("execution_receipt_required") + is False + and verifier_no_execution_receipt_proof.get( + "dry_run_executor_invocation_allowed" + ) + is False + and verifier_no_execution_receipt_proof.get("runner_invocation_allowed") + is False + and verifier_no_execution_receipt_proof.get( + "ready_for_verifier_invocation_now" + ) + is False + and verifier_no_execution_receipt_proof.get( + "ready_for_dry_run_executor_invocation_now" + ) + is False + and verifier_no_execution_receipt_proof.get( + "ready_for_actual_dry_run_execution_now" + ) + is False + and verifier_no_execution_receipt_proof.get("endpoint_execution_allowed") + is False + and verifier_no_execution_receipt_proof.get("sql_execution_allowed") is False + and verifier_no_execution_receipt_proof.get("database_write_allowed") is False + and verifier_no_execution_receipt_proof.get("database_apply_authorized") + is False + and verifier_no_execution_receipt_proof.get("executes_database_apply") + is False + and verifier_no_execution_receipt_proof.get("executes_endpoint") is False + and verifier_no_execution_receipt_proof.get("executes_sql") is False + and verifier_no_execution_receipt_proof.get("writes_database") is False + and verifier_no_execution_receipt_proof.get("captures_stdout") is False + and verifier_no_execution_receipt_proof.get("captures_stderr") is False + and verifier_no_execution_receipt_proof.get("stdout_included") is False + and verifier_no_execution_receipt_proof.get("stderr_included") is False + ) + previous_closeouts_carried_forward = ( + lock_closeout.get("frozen_envelope_verifier_handoff_closeout_only") is True + and lock_closeout.get("verifier_invocation_lock_proof_only") is True + and lock_closeout.get("database_apply_authorized") is False + and source_handoff_closeout.get("execution_envelope_freeze_proof_closeout_only") + is True + and source_handoff_closeout.get("frozen_envelope_verifier_handoff_only") + is True + and source_freeze_closeout.get("controlled_executor_quarantine_proof_closeout_only") + is True + and handoff_receipt_closeout.get("no_execution_receipt_handoff_closeout_only") + is True + and verifier_invocation_lock_proof_no_execute + ) + target_hash_locked = ( + lock_closeout.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(lock_closeout.get("expected_sha256")) + and bool(lock_closeout.get("actual_sha256")) + and lock_closeout.get("expected_sha256") == lock_closeout.get("actual_sha256") + and lock_closeout.get("hash_matches") is True + and lock_closeout.get("target_migration_hash_locked") is True + ) + rollback_and_verifier_bound = ( + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False + ) + lock_contract_blocks_database_apply = ( + lock_contract.get("executes_database_apply") is False + and lock_contract.get("executes_endpoint") is False + and lock_contract.get("executes_sql") is False + and lock_contract.get("database_apply_authorized") is False + and lock_contract.get("ready_for_database_apply_now") is False + and lock_contract.get("ready_for_verifier_invocation_now") is False + and lock_contract.get("ready_for_dry_run_executor_invocation_now") is False + and lock_contract.get("ready_for_actual_dry_run_execution_now") is False + and lock_contract.get("signs_database_apply_authorization") is False + and lock_contract.get("writes_database") is False + and lock_contract.get("executes_in_preview") is False + ) + side_effect_free = ( + summary.get("reads_secret_count", 0) == 0 + and summary.get("executes_script_count", 0) == 0 + and summary.get("executes_migration_count", 0) == 0 + and summary.get("executes_endpoint_count", 0) == 0 + and summary.get("executes_sql_count", 0) == 0 + and summary.get("writes_database_count", 0) == 0 + and summary.get("signs_database_apply_authorization_count", 0) == 0 + and summary.get("verifier_invoked_count", 0) == 0 + and summary.get("verifier_receipt_present_count", 0) == 0 + and summary.get("dry_run_executor_invoked_count", 0) == 0 + and summary.get("runner_invocation_performed_count", 0) == 0 + and summary.get("endpoint_executed_count", 0) == 0 + and summary.get("sql_executed_count", 0) == 0 + and summary.get("database_written_count", 0) == 0 + and safety.get("reads_secret_in_preview") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("executes_database_apply") is False + and verifier_invocation_lock_proof_no_execute + and verifier_no_execution_receipt_proof_blocks_execution + ) + checks = [ + _controlled_dry_run_verifier_invocation_lock_proof_closeout_check( + "frozen_envelope_verifier_handoff_closeout_ready", + lock_closeout_ready, + { + "result": lock_closeout_result.get("result"), + "ready_count": summary.get( + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_ready_count" + ), + "pass_count": summary.get( + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_pass_count" + ), + "check_count": summary.get( + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_check_count" + ), + }, + "wait_for_frozen_envelope_verifier_handoff_closeout_ready", + ), + _controlled_dry_run_verifier_invocation_lock_proof_closeout_check( + "source_chain_ids_match", + source_chain_ids_match, + { + "frozen_envelope_verifier_handoff_closeout_id": source_closeout_id, + "verifier_invocation_lock_proof_id": lock_proof.get( + "lock_proof_id" + ), + "verifier_no_execution_receipt_proof_id": ( + verifier_no_execution_receipt_proof.get("receipt_proof_id") + ), + }, + "wait_for_source_chain_ids_match", + ), + _controlled_dry_run_verifier_invocation_lock_proof_closeout_check( + "verifier_invocation_lock_proof_ready", + verifier_invocation_lock_proof_ready, + { + "lock_proof_id": lock_proof.get("lock_proof_id"), + "lock_status": lock_proof.get("lock_status"), + "field_count": lock_proof.get( + "verifier_invocation_lock_proof_field_count" + ), + }, + "wait_for_verifier_invocation_lock_proof_ready", + ), + _controlled_dry_run_verifier_invocation_lock_proof_closeout_check( + "verifier_invocation_lock_proof_no_execute", + verifier_invocation_lock_proof_no_execute, + { + "lock_mode": lock_proof.get("lock_mode"), + "verifier_invocation_locked": lock_proof.get( + "verifier_invocation_locked" + ), + "verifier_invoked": lock_proof.get("verifier_invoked"), + }, + "abort_if_verifier_invocation_lock_proof_reports_execution", + ), + _controlled_dry_run_verifier_invocation_lock_proof_closeout_check( + "verifier_no_execution_receipt_proof_bound", + verifier_no_execution_receipt_proof_bound, + { + "receipt_proof_id": verifier_no_execution_receipt_proof.get( + "receipt_proof_id" + ), + "source_verifier_invocation_lock_proof_id": ( + verifier_no_execution_receipt_proof.get( + "source_verifier_invocation_lock_proof_id" + ) + ), + "field_count": verifier_no_execution_receipt_proof.get( + "verifier_no_execution_receipt_proof_field_count" + ), + }, + "wait_for_verifier_no_execution_receipt_proof_binding", + ), + _controlled_dry_run_verifier_invocation_lock_proof_closeout_check( + "verifier_no_execution_receipt_proof_blocks_execution", + verifier_no_execution_receipt_proof_blocks_execution, + { + "receipt_mode": verifier_no_execution_receipt_proof.get( + "receipt_mode" + ), + "verifier_invoked": verifier_no_execution_receipt_proof.get( + "verifier_invoked" + ), + "verifier_receipt_present": verifier_no_execution_receipt_proof.get( + "verifier_receipt_present" + ), + }, + "abort_if_verifier_no_execution_receipt_proof_allows_execution", + ), + _controlled_dry_run_verifier_invocation_lock_proof_closeout_check( + "previous_closeouts_carried_forward", + previous_closeouts_carried_forward, + { + "frozen_envelope_verifier_handoff_closeout_only": ( + lock_closeout.get("frozen_envelope_verifier_handoff_closeout_only") + ), + "verifier_invocation_lock_proof_only": ( + lock_closeout.get("verifier_invocation_lock_proof_only") + ), + "receipt_mode": verifier_no_execution_receipt_proof.get( + "receipt_mode" + ), + }, + "wait_for_previous_closeouts_carry_forward", + ), + _controlled_dry_run_verifier_invocation_lock_proof_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": lock_closeout.get("target_file"), + "hash_matches": lock_closeout.get("hash_matches"), + "expected_sha256_present": bool(lock_closeout.get("expected_sha256")), + "actual_sha256_present": bool(lock_closeout.get("actual_sha256")), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_verifier_invocation_lock_proof_closeout_check( + "rollback_and_post_apply_verifier_bound", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "database_apply_authorized": verifier_binding.get( + "database_apply_authorized" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_verifier_invocation_lock_proof_closeout_check( + "frozen_envelope_verifier_handoff_closeout_contract_blocks_database_apply", + lock_contract_blocks_database_apply, + { + "permits_future_database_apply_controlled_dry_run_verifier_invocation_lock_proof": ( + lock_contract.get( + "permits_future_database_apply_controlled_dry_run_verifier_invocation_lock_proof" + ) + ), + "database_apply_authorized": lock_contract.get( + "database_apply_authorized" + ), + "writes_database": lock_contract.get("writes_database"), + }, + "abort_if_frozen_envelope_verifier_handoff_closeout_contract_authorizes_database_apply", + ), + _controlled_dry_run_verifier_invocation_lock_proof_closeout_check( + "preview_has_no_side_effects_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "verifier_invoked_count": summary.get("verifier_invoked_count", 0), + "verifier_receipt_present_count": summary.get( + "verifier_receipt_present_count", 0 + ), + }, + "abort_on_preview_side_effect_execution_or_signing", + ), + _controlled_dry_run_verifier_invocation_lock_proof_closeout_check( + "manual_review_not_required_for_safe_preview", + lock_contract.get("manual_review_mode") == "exception_only" + and safety.get("manual_review_mode") == "exception_only", + { + "contract_manual_review_mode": lock_contract.get( + "manual_review_mode" + ), + "safety_manual_review_mode": safety.get("manual_review_mode"), + }, + "keep_manual_review_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_VERIFIER_INVOCATION_LOCK_PROOF_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_FROZEN_ENVELOPE_VERIFIER_HANDOFF_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof = { + "verifier_invocation_lock_proof_closeout_id": closeout_id, + "verifier_no_execution_receipt_proof_id": receipt_id, + "source_frozen_envelope_verifier_handoff_closeout_id": source_closeout_id, + "source_verifier_invocation_lock_proof_id": lock_proof.get("lock_proof_id"), + "source_frozen_envelope_verifier_handoff_id": frozen_handoff.get( + "handoff_id" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof_closeout": ( + closeout_ready + ), + "verifier_invocation_lock_proof_closeout_ready": closeout_ready, + "frozen_envelope_verifier_handoff_closeout_ready": lock_closeout_ready, + "verifier_invocation_lock_proof_ready": verifier_invocation_lock_proof_ready, + "verifier_no_execution_receipt_proof_bound": closeout_ready, + "execution_envelope_frozen": True, + "execution_envelope_mutation_allowed": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_verifier_invocation_lock_proof_closeout = { + "verifier_invocation_lock_proof_closeout_id": closeout_id, + "authorization_material_type": ( + "controlled_dry_run_verifier_invocation_lock_proof_closeout" + ), + "source_frozen_envelope_verifier_handoff_closeout_id": source_closeout_id, + "source_verifier_invocation_lock_proof_id": lock_proof.get("lock_proof_id"), + "source_frozen_envelope_verifier_handoff_id": frozen_handoff.get( + "handoff_id" + ), + "source_execution_envelope_freeze_proof_closeout_id": ( + source_handoff_closeout.get("execution_envelope_freeze_proof_closeout_id") + ), + "source_dry_run_execution_envelope_freeze_proof_id": freeze_proof.get( + "freeze_proof_id" + ), + "source_controlled_executor_quarantine_proof_closeout_id": ( + source_freeze_closeout.get("controlled_executor_quarantine_proof_closeout_id") + ), + "source_controlled_executor_quarantine_proof_id": quarantine.get( + "quarantine_proof_id" + ), + "source_final_no_runner_execution_proof_closeout_id": ( + source_quarantine_closeout.get("final_no_runner_execution_proof_closeout_id") + ), + "source_final_no_runner_execution_proof_id": final_proof.get("proof_id"), + "source_no_execution_receipt_handoff_closeout_id": ( + handoff_receipt_closeout.get("no_execution_receipt_handoff_closeout_id") + ), + "required_command_shape_hash": freeze_proof.get("required_command_shape_hash"), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof": ( + closeout_ready + ), + "verifier_invocation_lock_proof_closeout_fields": lock_closeout_fields, + "verifier_invocation_lock_proof_closeout_field_count": len( + lock_closeout_fields + ), + "verifier_invocation_lock_proof_closeout_acceptance_gates": ( + lock_closeout_acceptance_gates + ), + "verifier_invocation_lock_proof_closeout_acceptance_gate_count": len( + lock_closeout_acceptance_gates + ), + "verifier_no_execution_receipt_proof": verifier_no_execution_receipt_proof, + "verifier_no_execution_receipt_proof_count": 1, + "verifier_no_execution_receipt_proof_field_count": len( + verifier_no_execution_receipt_proof_fields + ), + "verifier_invocation_lock_proof": lock_proof, + "verifier_invocation_lock_proof_count": 1, + "verifier_invocation_lock_proof_field_count": int( + lock_proof.get("verifier_invocation_lock_proof_field_count") or 0 + ), + "frozen_envelope_verifier_handoff_closeout": lock_closeout, + "frozen_envelope_verifier_handoff_closeout_count": 1, + "frozen_envelope_verifier_handoff": frozen_handoff, + "frozen_envelope_verifier_handoff_count": 1, + "dry_run_execution_envelope_freeze_proof": freeze_proof, + "dry_run_execution_envelope_freeze_proof_count": 1, + "execution_envelope_freeze_proof_closeout": source_handoff_closeout, + "execution_envelope_freeze_proof_closeout_count": 1, + "controlled_executor_quarantine_proof_closeout": source_freeze_closeout, + "controlled_executor_quarantine_proof_closeout_count": 1, + "controlled_executor_quarantine_proof": quarantine, + "controlled_executor_quarantine_proof_count": 1, + "final_no_runner_execution_proof_closeout": source_quarantine_closeout, + "final_no_runner_execution_proof_closeout_count": 1, + "final_no_runner_execution_proof": final_proof, + "final_no_runner_execution_proof_count": 1, + "no_execution_receipt_handoff_closeout": handoff_receipt_closeout, + "no_execution_receipt_handoff_closeout_count": 1, + "target_file": lock_closeout.get("target_file"), + "expected_sha256": lock_closeout.get("expected_sha256"), + "actual_sha256": lock_closeout.get("actual_sha256"), + "hash_matches": lock_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "verifier_invocation_lock_proof_closeout_only": True, + "verifier_no_execution_receipt_proof_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "controlled_executor_quarantine_bound": True, + "executor_quarantine_enforced": True, + "execution_envelope_frozen": True, + "execution_envelope_mutation_allowed": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + } + controlled_dry_run_verifier_invocation_lock_proof_closeout_contract = { + "mode": "controlled_dry_run_verifier_invocation_lock_proof_closeout_and_verifier_no_execution_receipt_proof_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-verifier-invocation-lock-proof-closeout" + ), + "source_frozen_envelope_verifier_handoff_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-frozen-envelope-verifier-handoff-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof": ( + closeout_ready + ), + "verifier_invocation_locked": True, + "verifier_invoked": False, + "verifier_receipt_present": False, + "ready_for_database_apply_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "persists_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": False, + "signs_database_apply_authorization": False, + "writes_database": False, + "executes_in_preview": False, + "secret_material_required_in_preview": False, + "manual_review_mode": "exception_only", + } + output_summary = dict(summary) + output_summary.update( + { + "controlled_dry_run_verifier_invocation_lock_proof_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_verifier_invocation_lock_proof_closeout_check_count": len( + checks + ), + "controlled_dry_run_verifier_invocation_lock_proof_closeout_pass_count": ( + passed_count + ), + "controlled_dry_run_verifier_invocation_lock_proof_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_verifier_invocation_lock_proof_closeout_count": 1, + "controlled_dry_run_verifier_invocation_lock_proof_closeout_field_count": len( + lock_closeout_fields + ), + "controlled_dry_run_verifier_invocation_lock_proof_closeout_acceptance_gate_count": len( + lock_closeout_acceptance_gates + ), + "verifier_no_execution_receipt_proof_count": 1, + "verifier_no_execution_receipt_proof_field_count": len( + verifier_no_execution_receipt_proof_fields + ), + "verifier_invocation_locked_count": 1, + "verifier_invoked_count": 0, + "verifier_receipt_present_count": 0, + "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, + "dry_run_executor_invoked_count": 0, + "runner_invocation_performed_count": 0, + "endpoint_executed_count": 0, + "sql_executed_count": 0, + "database_written_count": 0, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + } + ) + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_VERIFIER_INVOCATION_LOCK_PROOF_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(lock_closeout_result.get("success")), + "generated_at": lock_closeout_result.get("generated_at"), + "source_policy": lock_closeout_result.get("policy"), + "stats": lock_closeout_result.get("stats") or {}, + "summary": output_summary, + "future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof": ( + future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof + ), + "controlled_dry_run_verifier_invocation_lock_proof_closeout": ( + controlled_dry_run_verifier_invocation_lock_proof_closeout + ), + "controlled_dry_run_verifier_invocation_lock_proof_closeout_contract": ( + controlled_dry_run_verifier_invocation_lock_proof_closeout_contract + ), + "controlled_dry_run_verifier_invocation_lock_proof_closeout_checks": checks, + "source_controlled_dry_run_frozen_envelope_verifier_handoff_closeout_summary": ( + summary + ), + "source_controlled_dry_run_frozen_envelope_verifier_handoff_closeout_contract": ( + lock_contract + ), + "source_controlled_dry_run_frozen_envelope_verifier_handoff_closeout": ( + lock_closeout + ), + "source_database_apply_controlled_dry_run_verifier_invocation_lock_proof": ( + future_lock + ), + "safety": { + "read_only_db_apply_controlled_dry_run_verifier_invocation_lock_proof_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future verifier no-execution receipt proof closeout.", + "Keep verifier invocation and verifier receipt persistence disabled until a later lane explicitly proves that boundary.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, verifier invocation, verifier receipt persistence, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_verifier_no_execution_receipt_proof_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out the no-execution receipt proof and keep receipt persistence locked.""" + receipt_closeout_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_verifier_invocation_lock_proof_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_receipt = ( + receipt_closeout_result.get( + "future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof" + ) + or {} + ) + receipt_closeout = ( + receipt_closeout_result.get( + "controlled_dry_run_verifier_invocation_lock_proof_closeout" + ) + or {} + ) + receipt_contract = ( + receipt_closeout_result.get( + "controlled_dry_run_verifier_invocation_lock_proof_closeout_contract" + ) + or {} + ) + summary = receipt_closeout_result.get("summary") or {} + safety = receipt_closeout_result.get("safety") or {} + receipt_proof = receipt_closeout.get("verifier_no_execution_receipt_proof") or {} + lock_proof = receipt_closeout.get("verifier_invocation_lock_proof") or {} + previous_lock_closeout = ( + receipt_closeout.get("frozen_envelope_verifier_handoff_closeout") or {} + ) + rollback_binding = receipt_closeout.get("rollback_binding") or {} + verifier_binding = receipt_closeout.get("post_apply_verifier_binding") or {} + source_closeout_id = receipt_closeout.get( + "verifier_invocation_lock_proof_closeout_id" + ) + source_receipt_id = receipt_proof.get("receipt_proof_id") + closeout_id = ( + _db_apply_controlled_dry_run_verifier_no_execution_receipt_proof_closeout_id( + receipt_closeout_result + ) + ) + guard_id = f"{closeout_id}-verifier-receipt-persistence-guard-proof" + closeout_fields = [ + "verifier_no_execution_receipt_proof_closeout_id", + "source_verifier_invocation_lock_proof_closeout_id", + "source_verifier_no_execution_receipt_proof_id", + "source_verifier_invocation_lock_proof_id", + "verifier_receipt_persistence_guard_proof_id", + "required_command_shape_hash", + "target_migration_file", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "verifier_receipt_persistence_locked", + "verifier_receipt_persisted", + "abort_conditions", + ] + acceptance_gates = [ + "verifier_invocation_lock_proof_closeout_ready", + "source_chain_ids_match", + "verifier_no_execution_receipt_proof_ready", + "verifier_no_execution_receipt_proof_no_execute", + "verifier_receipt_persistence_guard_proof_bound", + "verifier_receipt_persistence_guard_proof_blocks_persistence", + "previous_closeouts_carried_forward", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "no_secret_signature_persistence_or_database_apply", + ] + persistence_guard_fields = [ + "guard_proof_id", + "source_verifier_no_execution_receipt_proof_closeout_id", + "source_verifier_invocation_lock_proof_closeout_id", + "source_verifier_no_execution_receipt_proof_id", + "source_verifier_invocation_lock_proof_id", + "guard_mode", + "verifier_receipt_persistence_locked", + "verifier_receipt_persisted", + "verifier_receipt_persistence_allowed", + "endpoint_execution_allowed", + "sql_execution_allowed", + "database_apply_authorized", + ] + abort_conditions = [ + "abort_if_verifier_invocation_lock_proof_closeout_not_ready", + "abort_if_source_chain_ids_do_not_match", + "abort_if_verifier_no_execution_receipt_proof_missing", + "abort_if_verifier_no_execution_receipt_proof_reports_execution", + "abort_if_verifier_receipt_persistence_guard_missing", + "abort_if_verifier_receipt_persistence_is_allowed", + "abort_if_verifier_receipt_is_persisted", + "abort_if_endpoint_or_sql_execution_is_allowed", + "abort_if_database_write_or_apply_is_allowed", + "abort_if_target_migration_hash_changes", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_any_secret_signature_persistence_or_apply_material_is_present", + ] + persistence_guard_proof = { + "guard_proof_id": guard_id, + "source_verifier_no_execution_receipt_proof_closeout_id": closeout_id, + "source_verifier_invocation_lock_proof_closeout_id": source_closeout_id, + "source_verifier_no_execution_receipt_proof_id": source_receipt_id, + "source_verifier_invocation_lock_proof_id": lock_proof.get("lock_proof_id"), + "source_frozen_envelope_verifier_handoff_closeout_id": ( + receipt_proof.get("source_frozen_envelope_verifier_handoff_closeout_id") + ), + "source_frozen_envelope_verifier_handoff_id": ( + receipt_proof.get("source_frozen_envelope_verifier_handoff_id") + ), + "required_command_shape_hash": receipt_proof.get( + "required_command_shape_hash" + ), + "guard_status": "verifier_receipt_persistence_guard_proof_preview_ready", + "guard_mode": "verifier_receipt_persistence_guard_proof_preview_only", + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "verifier_receipt_present": False, + "verifier_receipt_required": False, + "persists_verifier_receipt": False, + "persistence_receipt_present": False, + "persistence_receipt_required": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_database_apply_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "database_apply_authorized": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "verifier_receipt_persistence_guard_proof_field_count": len( + persistence_guard_fields + ), + "verifier_receipt_persistence_guard_proof_fields": ( + persistence_guard_fields + ), + } + receipt_closeout_ready = ( + receipt_closeout_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_VERIFIER_INVOCATION_LOCK_PROOF_CLOSEOUT_READY" + and summary.get( + "controlled_dry_run_verifier_invocation_lock_proof_closeout_ready_count" + ) + == 1 + and summary.get( + "controlled_dry_run_verifier_invocation_lock_proof_closeout_pass_count" + ) + == summary.get( + "controlled_dry_run_verifier_invocation_lock_proof_closeout_check_count" + ) + ) + source_chain_ids_match = ( + bool(source_closeout_id) + and source_closeout_id + == future_receipt.get("verifier_invocation_lock_proof_closeout_id") + == receipt_proof.get("source_verifier_invocation_lock_proof_closeout_id") + == persistence_guard_proof.get( + "source_verifier_invocation_lock_proof_closeout_id" + ) + and source_receipt_id + == future_receipt.get("verifier_no_execution_receipt_proof_id") + == persistence_guard_proof.get( + "source_verifier_no_execution_receipt_proof_id" + ) + and lock_proof.get("lock_proof_id") + == receipt_proof.get("source_verifier_invocation_lock_proof_id") + == persistence_guard_proof.get( + "source_verifier_invocation_lock_proof_id" + ) + and receipt_proof.get("required_command_shape_hash") + == lock_proof.get("required_command_shape_hash") + == persistence_guard_proof.get("required_command_shape_hash") + ) + receipt_proof_ready = ( + receipt_closeout_ready + and receipt_proof.get("receipt_status") + == "verifier_no_execution_receipt_proof_preview_ready" + and receipt_proof.get("receipt_proof_id") + == future_receipt.get("verifier_no_execution_receipt_proof_id") + and int( + receipt_proof.get("verifier_no_execution_receipt_proof_field_count") + or 0 + ) + == 12 + and summary.get("verifier_no_execution_receipt_proof_count") == 1 + ) + receipt_proof_no_execute = ( + receipt_proof.get("receipt_mode") + == "verifier_no_execution_receipt_proof_preview_only" + and receipt_proof.get("verifier_invocation_locked") is True + and receipt_proof.get("verifier_invocation_allowed") is False + and receipt_proof.get("verifier_invoked") is False + and receipt_proof.get("verifier_receipt_present") is False + and receipt_proof.get("verifier_receipt_required") is False + and receipt_proof.get("dry_run_executor_invoked") is False + and receipt_proof.get("runner_invocation_performed") is False + and receipt_proof.get("endpoint_executed") is False + and receipt_proof.get("sql_executed") is False + and receipt_proof.get("database_written") is False + and receipt_proof.get("endpoint_execution_allowed") is False + and receipt_proof.get("sql_execution_allowed") is False + and receipt_proof.get("database_write_allowed") is False + and receipt_proof.get("database_apply_authorized") is False + and receipt_proof.get("executes_database_apply") is False + and receipt_proof.get("executes_endpoint") is False + and receipt_proof.get("executes_sql") is False + and receipt_proof.get("writes_database") is False + and receipt_proof.get("stdout_included") is False + and receipt_proof.get("stderr_included") is False + ) + persistence_guard_bound = ( + receipt_proof_ready + and bool(persistence_guard_proof.get("guard_proof_id")) + and persistence_guard_proof.get( + "source_verifier_no_execution_receipt_proof_closeout_id" + ) + == closeout_id + and persistence_guard_proof.get( + "source_verifier_invocation_lock_proof_closeout_id" + ) + == source_closeout_id + and persistence_guard_proof.get( + "source_verifier_no_execution_receipt_proof_id" + ) + == source_receipt_id + and persistence_guard_proof.get("required_command_shape_hash") + == receipt_proof.get("required_command_shape_hash") + and int( + persistence_guard_proof.get( + "verifier_receipt_persistence_guard_proof_field_count" + ) + or 0 + ) + == len(persistence_guard_fields) + ) + persistence_guard_blocks_persistence = ( + persistence_guard_proof.get("guard_mode") + == "verifier_receipt_persistence_guard_proof_preview_only" + and persistence_guard_proof.get("verifier_receipt_persistence_locked") + is True + and persistence_guard_proof.get("verifier_receipt_persistence_allowed") + is False + and persistence_guard_proof.get("verifier_receipt_persisted") is False + and persistence_guard_proof.get("persists_verifier_receipt") is False + and persistence_guard_proof.get("persistence_receipt_present") is False + and persistence_guard_proof.get("verifier_invocation_allowed") is False + and persistence_guard_proof.get("verifier_invoked") is False + and persistence_guard_proof.get("dry_run_executor_invoked") is False + and persistence_guard_proof.get("runner_invocation_performed") is False + and persistence_guard_proof.get("endpoint_executed") is False + and persistence_guard_proof.get("sql_executed") is False + and persistence_guard_proof.get("database_written") is False + and persistence_guard_proof.get("endpoint_execution_allowed") is False + and persistence_guard_proof.get("sql_execution_allowed") is False + and persistence_guard_proof.get("database_write_allowed") is False + and persistence_guard_proof.get("database_apply_authorized") is False + and persistence_guard_proof.get("executes_database_apply") is False + and persistence_guard_proof.get("executes_endpoint") is False + and persistence_guard_proof.get("executes_sql") is False + and persistence_guard_proof.get("writes_database") is False + and persistence_guard_proof.get("stdout_included") is False + and persistence_guard_proof.get("stderr_included") is False + ) + previous_closeouts_carried_forward = ( + receipt_closeout.get("verifier_invocation_lock_proof_closeout_only") is True + and receipt_closeout.get("verifier_no_execution_receipt_proof_only") + is True + and previous_lock_closeout.get("frozen_envelope_verifier_handoff_closeout_only") + is True + and receipt_proof_no_execute + ) + target_hash_locked = ( + receipt_closeout.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(receipt_closeout.get("expected_sha256")) + and bool(receipt_closeout.get("actual_sha256")) + and receipt_closeout.get("expected_sha256") + == receipt_closeout.get("actual_sha256") + and receipt_closeout.get("hash_matches") is True + and receipt_closeout.get("target_migration_hash_locked") is True + ) + rollback_and_verifier_bound = ( + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False + ) + contract_blocks_persistence_and_apply = ( + receipt_contract.get( + "permits_future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof" + ) + is True + and receipt_contract.get("persists_verifier_receipt") is False + and receipt_contract.get("executes_database_apply") is False + and receipt_contract.get("database_apply_authorized") is False + and receipt_contract.get("writes_database") is False + ) + side_effect_free = ( + summary.get("reads_secret_count", 0) == 0 + and summary.get("executes_endpoint_count", 0) == 0 + and summary.get("executes_sql_count", 0) == 0 + and summary.get("writes_database_count", 0) == 0 + and summary.get("signs_database_apply_authorization_count", 0) == 0 + and summary.get("verifier_invoked_count", 0) == 0 + and summary.get("verifier_receipt_present_count", 0) == 0 + and safety.get("persists_verifier_receipt") is False + ) + checks = [ + _controlled_dry_run_verifier_no_execution_receipt_proof_closeout_check( + "verifier_invocation_lock_proof_closeout_ready", + receipt_closeout_ready, + { + "result": receipt_closeout_result.get("result"), + "ready_count": summary.get( + "controlled_dry_run_verifier_invocation_lock_proof_closeout_ready_count" + ), + }, + "wait_for_verifier_invocation_lock_proof_closeout", + ), + _controlled_dry_run_verifier_no_execution_receipt_proof_closeout_check( + "source_chain_ids_match", + source_chain_ids_match, + { + "source_closeout_id": source_closeout_id, + "source_receipt_id": source_receipt_id, + "lock_proof_id": lock_proof.get("lock_proof_id"), + }, + "wait_for_source_chain_alignment", + ), + _controlled_dry_run_verifier_no_execution_receipt_proof_closeout_check( + "verifier_no_execution_receipt_proof_ready", + receipt_proof_ready, + { + "receipt_proof_id": source_receipt_id, + "receipt_status": receipt_proof.get("receipt_status"), + "field_count": receipt_proof.get( + "verifier_no_execution_receipt_proof_field_count" + ), + }, + "wait_for_verifier_no_execution_receipt_proof", + ), + _controlled_dry_run_verifier_no_execution_receipt_proof_closeout_check( + "verifier_no_execution_receipt_proof_no_execute", + receipt_proof_no_execute, + { + "receipt_mode": receipt_proof.get("receipt_mode"), + "verifier_invoked": receipt_proof.get("verifier_invoked"), + "database_written": receipt_proof.get("database_written"), + }, + "abort_if_verifier_no_execution_receipt_proof_reports_execution", + ), + _controlled_dry_run_verifier_no_execution_receipt_proof_closeout_check( + "verifier_receipt_persistence_guard_proof_bound", + persistence_guard_bound, + { + "guard_proof_id": persistence_guard_proof.get("guard_proof_id"), + "source_receipt_id": persistence_guard_proof.get( + "source_verifier_no_execution_receipt_proof_id" + ), + "field_count": persistence_guard_proof.get( + "verifier_receipt_persistence_guard_proof_field_count" + ), + }, + "wait_for_verifier_receipt_persistence_guard_proof", + ), + _controlled_dry_run_verifier_no_execution_receipt_proof_closeout_check( + "verifier_receipt_persistence_guard_proof_blocks_persistence", + persistence_guard_blocks_persistence, + { + "guard_mode": persistence_guard_proof.get("guard_mode"), + "persistence_allowed": persistence_guard_proof.get( + "verifier_receipt_persistence_allowed" + ), + "persisted": persistence_guard_proof.get( + "verifier_receipt_persisted" + ), + }, + "abort_if_verifier_receipt_persistence_guard_allows_persistence", + ), + _controlled_dry_run_verifier_no_execution_receipt_proof_closeout_check( + "previous_closeouts_carried_forward", + previous_closeouts_carried_forward, + { + "verifier_invocation_lock_proof_closeout_only": ( + receipt_closeout.get( + "verifier_invocation_lock_proof_closeout_only" + ) + ), + "verifier_no_execution_receipt_proof_only": ( + receipt_closeout.get("verifier_no_execution_receipt_proof_only") + ), + }, + "wait_for_previous_closeouts_carry_forward", + ), + _controlled_dry_run_verifier_no_execution_receipt_proof_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": receipt_closeout.get("target_file"), + "hash_matches": receipt_closeout.get("hash_matches"), + "expected_sha256_present": bool( + receipt_closeout.get("expected_sha256") + ), + "actual_sha256_present": bool(receipt_closeout.get("actual_sha256")), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_verifier_no_execution_receipt_proof_closeout_check( + "rollback_and_post_apply_verifier_bound", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_verifier_no_execution_receipt_proof_closeout_check( + "verifier_invocation_lock_proof_closeout_contract_blocks_persistence_and_database_apply", + contract_blocks_persistence_and_apply, + { + "permits_future_no_execution_receipt_proof": receipt_contract.get( + "permits_future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof" + ), + "persists_verifier_receipt": receipt_contract.get( + "persists_verifier_receipt" + ), + "database_apply_authorized": receipt_contract.get( + "database_apply_authorized" + ), + }, + "abort_if_source_contract_allows_persistence_or_database_apply", + ), + _controlled_dry_run_verifier_no_execution_receipt_proof_closeout_check( + "preview_has_no_side_effects_no_persistence_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "verifier_invoked_count": summary.get("verifier_invoked_count", 0), + "verifier_receipt_present_count": summary.get( + "verifier_receipt_present_count", 0 + ), + }, + "abort_on_preview_persistence_side_effect_execution_or_signing", + ), + _controlled_dry_run_verifier_no_execution_receipt_proof_closeout_check( + "manual_review_not_required_for_safe_preview", + receipt_contract.get("manual_review_mode") == "exception_only" + and safety.get("manual_review_mode") == "exception_only", + { + "contract_manual_review_mode": receipt_contract.get( + "manual_review_mode" + ), + "safety_manual_review_mode": safety.get("manual_review_mode"), + }, + "keep_manual_review_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_VERIFIER_NO_EXECUTION_RECEIPT_PROOF_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_VERIFIER_INVOCATION_LOCK_PROOF_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof = { + "verifier_no_execution_receipt_proof_closeout_id": closeout_id, + "verifier_receipt_persistence_guard_proof_id": guard_id, + "source_verifier_invocation_lock_proof_closeout_id": source_closeout_id, + "source_verifier_no_execution_receipt_proof_id": source_receipt_id, + "source_verifier_invocation_lock_proof_id": lock_proof.get("lock_proof_id"), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout": ( + closeout_ready + ), + "verifier_no_execution_receipt_proof_closeout_ready": closeout_ready, + "verifier_invocation_lock_proof_closeout_ready": receipt_closeout_ready, + "verifier_no_execution_receipt_proof_ready": receipt_proof_ready, + "verifier_receipt_persistence_guard_proof_bound": closeout_ready, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_verifier_no_execution_receipt_proof_closeout = { + "verifier_no_execution_receipt_proof_closeout_id": closeout_id, + "authorization_material_type": ( + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout" + ), + "source_verifier_invocation_lock_proof_closeout_id": source_closeout_id, + "source_verifier_no_execution_receipt_proof_id": source_receipt_id, + "source_verifier_invocation_lock_proof_id": lock_proof.get("lock_proof_id"), + "required_command_shape_hash": receipt_proof.get( + "required_command_shape_hash" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof": ( + closeout_ready + ), + "verifier_no_execution_receipt_proof_closeout_fields": closeout_fields, + "verifier_no_execution_receipt_proof_closeout_field_count": len( + closeout_fields + ), + "verifier_no_execution_receipt_proof_closeout_acceptance_gates": ( + acceptance_gates + ), + "verifier_no_execution_receipt_proof_closeout_acceptance_gate_count": len( + acceptance_gates + ), + "verifier_receipt_persistence_guard_proof": persistence_guard_proof, + "verifier_receipt_persistence_guard_proof_count": 1, + "verifier_receipt_persistence_guard_proof_field_count": len( + persistence_guard_fields + ), + "verifier_no_execution_receipt_proof": receipt_proof, + "verifier_no_execution_receipt_proof_count": 1, + "verifier_invocation_lock_proof_closeout": receipt_closeout, + "verifier_invocation_lock_proof_closeout_count": 1, + "verifier_invocation_lock_proof": lock_proof, + "verifier_invocation_lock_proof_count": 1, + "target_file": receipt_closeout.get("target_file"), + "expected_sha256": receipt_closeout.get("expected_sha256"), + "actual_sha256": receipt_closeout.get("actual_sha256"), + "hash_matches": receipt_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "verifier_no_execution_receipt_proof_closeout_only": True, + "verifier_receipt_persistence_guard_proof_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + } + controlled_dry_run_verifier_no_execution_receipt_proof_closeout_contract = { + "mode": "controlled_dry_run_verifier_no_execution_receipt_proof_closeout_and_verifier_receipt_persistence_guard_proof_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-verifier-no-execution-receipt-proof-closeout" + ), + "source_verifier_invocation_lock_proof_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-verifier-invocation-lock-proof-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof": ( + closeout_ready + ), + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_invocation_locked": True, + "verifier_invoked": False, + "verifier_receipt_present": False, + "ready_for_database_apply_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": False, + "signs_database_apply_authorization": False, + "writes_database": False, + "executes_in_preview": False, + "secret_material_required_in_preview": False, + "manual_review_mode": "exception_only", + } + output_summary = dict(summary) + output_summary.update( + { + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout_check_count": len( + checks + ), + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout_pass_count": ( + passed_count + ), + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout_count": 1, + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout_field_count": len( + closeout_fields + ), + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout_acceptance_gate_count": len( + acceptance_gates + ), + "verifier_receipt_persistence_guard_proof_count": 1, + "verifier_receipt_persistence_guard_proof_field_count": len( + persistence_guard_fields + ), + "verifier_receipt_persistence_locked_count": 1, + "verifier_receipt_persistence_allowed_count": 0, + "verifier_receipt_persisted_count": 0, + "persists_verifier_receipt_count": 0, + "verifier_invocation_locked_count": 1, + "verifier_invoked_count": 0, + "verifier_receipt_present_count": 0, + "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, + "dry_run_executor_invoked_count": 0, + "runner_invocation_performed_count": 0, + "endpoint_executed_count": 0, + "sql_executed_count": 0, + "database_written_count": 0, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + } + ) + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_VERIFIER_NO_EXECUTION_RECEIPT_PROOF_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(receipt_closeout_result.get("success")), + "generated_at": receipt_closeout_result.get("generated_at"), + "source_policy": receipt_closeout_result.get("policy"), + "stats": receipt_closeout_result.get("stats") or {}, + "summary": output_summary, + "future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof": ( + future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof + ), + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout": ( + controlled_dry_run_verifier_no_execution_receipt_proof_closeout + ), + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout_contract": ( + controlled_dry_run_verifier_no_execution_receipt_proof_closeout_contract + ), + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout_checks": ( + checks + ), + "source_controlled_dry_run_verifier_invocation_lock_proof_closeout_summary": ( + summary + ), + "source_controlled_dry_run_verifier_invocation_lock_proof_closeout_contract": ( + receipt_contract + ), + "source_controlled_dry_run_verifier_invocation_lock_proof_closeout": ( + receipt_closeout + ), + "source_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof": ( + future_receipt + ), + "safety": { + "read_only_db_apply_controlled_dry_run_verifier_no_execution_receipt_proof_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future verifier receipt persistence guard proof closeout.", + "Keep verifier receipt persistence disabled until a later persistence guard closeout proves the storage boundary.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, verifier invocation, verifier receipt persistence, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out the persistence guard proof and prove storage remains locked.""" + storage_closeout_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_verifier_no_execution_receipt_proof_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_guard = ( + storage_closeout_result.get( + "future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof" + ) + or {} + ) + guard_closeout = ( + storage_closeout_result.get( + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout" + ) + or {} + ) + guard_contract = ( + storage_closeout_result.get( + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout_contract" + ) + or {} + ) + summary = storage_closeout_result.get("summary") or {} + safety = storage_closeout_result.get("safety") or {} + guard_proof = guard_closeout.get("verifier_receipt_persistence_guard_proof") or {} + receipt_proof = guard_closeout.get("verifier_no_execution_receipt_proof") or {} + previous_receipt_closeout = ( + guard_closeout.get("verifier_invocation_lock_proof_closeout") or {} + ) + rollback_binding = guard_closeout.get("rollback_binding") or {} + verifier_binding = guard_closeout.get("post_apply_verifier_binding") or {} + source_closeout_id = guard_closeout.get( + "verifier_no_execution_receipt_proof_closeout_id" + ) + source_guard_id = guard_proof.get("guard_proof_id") + source_receipt_id = guard_proof.get("source_verifier_no_execution_receipt_proof_id") + closeout_id = ( + _db_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_id( + storage_closeout_result + ) + ) + storage_boundary_id = f"{closeout_id}-receipt-persistence-storage-boundary-proof" + closeout_fields = [ + "verifier_receipt_persistence_guard_proof_closeout_id", + "source_verifier_no_execution_receipt_proof_closeout_id", + "source_verifier_receipt_persistence_guard_proof_id", + "source_verifier_no_execution_receipt_proof_id", + "receipt_persistence_storage_boundary_proof_id", + "required_command_shape_hash", + "target_migration_file", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "receipt_persistence_storage_boundary_locked", + "receipt_persistence_storage_written", + "abort_conditions", + ] + acceptance_gates = [ + "verifier_no_execution_receipt_proof_closeout_ready", + "source_chain_ids_match", + "verifier_receipt_persistence_guard_proof_ready", + "verifier_receipt_persistence_guard_proof_no_persistence", + "receipt_persistence_storage_boundary_proof_bound", + "receipt_persistence_storage_boundary_proof_blocks_storage", + "previous_closeouts_carried_forward", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "no_secret_signature_storage_persistence_or_database_apply", + ] + storage_boundary_fields = [ + "storage_boundary_proof_id", + "source_verifier_receipt_persistence_guard_proof_closeout_id", + "source_verifier_no_execution_receipt_proof_closeout_id", + "source_verifier_receipt_persistence_guard_proof_id", + "source_verifier_no_execution_receipt_proof_id", + "storage_boundary_mode", + "receipt_persistence_storage_boundary_locked", + "receipt_persistence_storage_write_allowed", + "receipt_persistence_storage_written", + "verifier_receipt_persistence_allowed", + "sql_execution_allowed", + "database_apply_authorized", + ] + abort_conditions = [ + "abort_if_verifier_no_execution_receipt_proof_closeout_not_ready", + "abort_if_source_chain_ids_do_not_match", + "abort_if_verifier_receipt_persistence_guard_proof_missing", + "abort_if_verifier_receipt_persistence_guard_allows_persistence", + "abort_if_receipt_persistence_storage_boundary_missing", + "abort_if_receipt_persistence_storage_write_is_allowed", + "abort_if_receipt_persistence_storage_written", + "abort_if_endpoint_or_sql_execution_is_allowed", + "abort_if_database_write_or_apply_is_allowed", + "abort_if_target_migration_hash_changes", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_any_secret_signature_storage_persistence_or_apply_material_is_present", + ] + storage_boundary_proof = { + "storage_boundary_proof_id": storage_boundary_id, + "source_verifier_receipt_persistence_guard_proof_closeout_id": closeout_id, + "source_verifier_no_execution_receipt_proof_closeout_id": source_closeout_id, + "source_verifier_receipt_persistence_guard_proof_id": source_guard_id, + "source_verifier_no_execution_receipt_proof_id": source_receipt_id, + "source_verifier_invocation_lock_proof_closeout_id": ( + guard_proof.get("source_verifier_invocation_lock_proof_closeout_id") + ), + "source_verifier_invocation_lock_proof_id": ( + guard_proof.get("source_verifier_invocation_lock_proof_id") + ), + "required_command_shape_hash": guard_proof.get( + "required_command_shape_hash" + ), + "storage_boundary_status": "receipt_persistence_storage_boundary_proof_preview_ready", + "storage_boundary_mode": "receipt_persistence_storage_boundary_proof_preview_only", + "receipt_persistence_storage_boundary_locked": True, + "receipt_persistence_storage_write_allowed": False, + "receipt_persistence_storage_written": False, + "receipt_persistence_storage_receipt_present": False, + "receipt_persistence_storage_receipt_required": False, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_receipt_present": False, + "verifier_receipt_required": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "ready_for_receipt_persistence_storage_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_database_apply_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "database_apply_authorized": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "receipt_persistence_storage_boundary_proof_field_count": len( + storage_boundary_fields + ), + "receipt_persistence_storage_boundary_proof_fields": ( + storage_boundary_fields + ), + } + guard_closeout_ready = ( + storage_closeout_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_VERIFIER_NO_EXECUTION_RECEIPT_PROOF_CLOSEOUT_READY" + and summary.get( + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout_ready_count" + ) + == 1 + and summary.get( + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout_pass_count" + ) + == summary.get( + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout_check_count" + ) + ) + source_chain_ids_match = ( + bool(source_closeout_id) + and source_closeout_id + == future_guard.get("verifier_no_execution_receipt_proof_closeout_id") + == guard_proof.get("source_verifier_no_execution_receipt_proof_closeout_id") + == storage_boundary_proof.get( + "source_verifier_no_execution_receipt_proof_closeout_id" + ) + and source_guard_id + == future_guard.get("verifier_receipt_persistence_guard_proof_id") + == storage_boundary_proof.get( + "source_verifier_receipt_persistence_guard_proof_id" + ) + and source_receipt_id + == guard_proof.get("source_verifier_no_execution_receipt_proof_id") + == receipt_proof.get("receipt_proof_id") + == storage_boundary_proof.get( + "source_verifier_no_execution_receipt_proof_id" + ) + and guard_proof.get("required_command_shape_hash") + == receipt_proof.get("required_command_shape_hash") + == storage_boundary_proof.get("required_command_shape_hash") + ) + guard_proof_ready = ( + guard_closeout_ready + and guard_proof.get("guard_status") + == "verifier_receipt_persistence_guard_proof_preview_ready" + and guard_proof.get("guard_proof_id") + == future_guard.get("verifier_receipt_persistence_guard_proof_id") + and int( + guard_proof.get( + "verifier_receipt_persistence_guard_proof_field_count" + ) + or 0 + ) + == 12 + and summary.get("verifier_receipt_persistence_guard_proof_count") == 1 + ) + guard_proof_no_persistence = ( + guard_proof.get("guard_mode") + == "verifier_receipt_persistence_guard_proof_preview_only" + and guard_proof.get("verifier_receipt_persistence_locked") is True + and guard_proof.get("verifier_receipt_persistence_allowed") is False + and guard_proof.get("verifier_receipt_persisted") is False + and guard_proof.get("persists_verifier_receipt") is False + and guard_proof.get("persistence_receipt_present") is False + and guard_proof.get("verifier_invocation_allowed") is False + and guard_proof.get("verifier_invoked") is False + and guard_proof.get("dry_run_executor_invoked") is False + and guard_proof.get("runner_invocation_performed") is False + and guard_proof.get("endpoint_executed") is False + and guard_proof.get("sql_executed") is False + and guard_proof.get("database_written") is False + and guard_proof.get("endpoint_execution_allowed") is False + and guard_proof.get("sql_execution_allowed") is False + and guard_proof.get("database_write_allowed") is False + and guard_proof.get("database_apply_authorized") is False + and guard_proof.get("executes_database_apply") is False + and guard_proof.get("executes_endpoint") is False + and guard_proof.get("executes_sql") is False + and guard_proof.get("writes_database") is False + and guard_proof.get("stdout_included") is False + and guard_proof.get("stderr_included") is False + ) + storage_boundary_bound = ( + guard_proof_ready + and bool(storage_boundary_proof.get("storage_boundary_proof_id")) + and storage_boundary_proof.get( + "source_verifier_receipt_persistence_guard_proof_closeout_id" + ) + == closeout_id + and storage_boundary_proof.get( + "source_verifier_no_execution_receipt_proof_closeout_id" + ) + == source_closeout_id + and storage_boundary_proof.get( + "source_verifier_receipt_persistence_guard_proof_id" + ) + == source_guard_id + and storage_boundary_proof.get("required_command_shape_hash") + == guard_proof.get("required_command_shape_hash") + and int( + storage_boundary_proof.get( + "receipt_persistence_storage_boundary_proof_field_count" + ) + or 0 + ) + == len(storage_boundary_fields) + ) + storage_boundary_blocks_storage = ( + storage_boundary_proof.get("storage_boundary_mode") + == "receipt_persistence_storage_boundary_proof_preview_only" + and storage_boundary_proof.get("receipt_persistence_storage_boundary_locked") + is True + and storage_boundary_proof.get("receipt_persistence_storage_write_allowed") + is False + and storage_boundary_proof.get("receipt_persistence_storage_written") + is False + and storage_boundary_proof.get("verifier_receipt_persistence_allowed") + is False + and storage_boundary_proof.get("verifier_receipt_persisted") is False + and storage_boundary_proof.get("persists_verifier_receipt") is False + and storage_boundary_proof.get("verifier_invoked") is False + and storage_boundary_proof.get("dry_run_executor_invoked") is False + and storage_boundary_proof.get("runner_invocation_performed") is False + and storage_boundary_proof.get("endpoint_executed") is False + and storage_boundary_proof.get("sql_executed") is False + and storage_boundary_proof.get("database_written") is False + and storage_boundary_proof.get("endpoint_execution_allowed") is False + and storage_boundary_proof.get("sql_execution_allowed") is False + and storage_boundary_proof.get("database_write_allowed") is False + and storage_boundary_proof.get("database_apply_authorized") is False + and storage_boundary_proof.get("executes_database_apply") is False + and storage_boundary_proof.get("executes_endpoint") is False + and storage_boundary_proof.get("executes_sql") is False + and storage_boundary_proof.get("writes_database") is False + and storage_boundary_proof.get("stdout_included") is False + and storage_boundary_proof.get("stderr_included") is False + ) + previous_closeouts_carried_forward = ( + guard_closeout.get("verifier_no_execution_receipt_proof_closeout_only") + is True + and guard_closeout.get("verifier_receipt_persistence_guard_proof_only") + is True + and previous_receipt_closeout.get( + "verifier_invocation_lock_proof_closeout_only" + ) + is True + and guard_proof_no_persistence + ) + target_hash_locked = ( + guard_closeout.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(guard_closeout.get("expected_sha256")) + and bool(guard_closeout.get("actual_sha256")) + and guard_closeout.get("expected_sha256") + == guard_closeout.get("actual_sha256") + and guard_closeout.get("hash_matches") is True + and guard_closeout.get("target_migration_hash_locked") is True + ) + rollback_and_verifier_bound = ( + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False + ) + contract_blocks_storage_persistence_and_apply = ( + guard_contract.get( + "permits_future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof" + ) + is True + and guard_contract.get("persists_verifier_receipt") is False + and guard_contract.get("executes_database_apply") is False + and guard_contract.get("database_apply_authorized") is False + and guard_contract.get("writes_database") is False + ) + side_effect_free = ( + summary.get("reads_secret_count", 0) == 0 + and summary.get("executes_endpoint_count", 0) == 0 + and summary.get("executes_sql_count", 0) == 0 + and summary.get("writes_database_count", 0) == 0 + and summary.get("signs_database_apply_authorization_count", 0) == 0 + and summary.get("persists_verifier_receipt_count", 0) == 0 + and summary.get("verifier_receipt_persisted_count", 0) == 0 + and safety.get("persists_verifier_receipt") is False + ) + checks = [ + _controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_check( + "verifier_no_execution_receipt_proof_closeout_ready", + guard_closeout_ready, + { + "result": storage_closeout_result.get("result"), + "ready_count": summary.get( + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout_ready_count" + ), + }, + "wait_for_verifier_no_execution_receipt_proof_closeout", + ), + _controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_check( + "source_chain_ids_match", + source_chain_ids_match, + { + "source_closeout_id": source_closeout_id, + "source_guard_id": source_guard_id, + "source_receipt_id": source_receipt_id, + }, + "wait_for_source_chain_alignment", + ), + _controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_check( + "verifier_receipt_persistence_guard_proof_ready", + guard_proof_ready, + { + "guard_proof_id": source_guard_id, + "guard_status": guard_proof.get("guard_status"), + "field_count": guard_proof.get( + "verifier_receipt_persistence_guard_proof_field_count" + ), + }, + "wait_for_verifier_receipt_persistence_guard_proof", + ), + _controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_check( + "verifier_receipt_persistence_guard_proof_no_persistence", + guard_proof_no_persistence, + { + "guard_mode": guard_proof.get("guard_mode"), + "persistence_allowed": guard_proof.get( + "verifier_receipt_persistence_allowed" + ), + "persisted": guard_proof.get("verifier_receipt_persisted"), + }, + "abort_if_verifier_receipt_persistence_guard_reports_persistence", + ), + _controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_check( + "receipt_persistence_storage_boundary_proof_bound", + storage_boundary_bound, + { + "storage_boundary_proof_id": storage_boundary_proof.get( + "storage_boundary_proof_id" + ), + "source_guard_id": storage_boundary_proof.get( + "source_verifier_receipt_persistence_guard_proof_id" + ), + "field_count": storage_boundary_proof.get( + "receipt_persistence_storage_boundary_proof_field_count" + ), + }, + "wait_for_receipt_persistence_storage_boundary_proof", + ), + _controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_check( + "receipt_persistence_storage_boundary_proof_blocks_storage", + storage_boundary_blocks_storage, + { + "storage_boundary_mode": storage_boundary_proof.get( + "storage_boundary_mode" + ), + "storage_write_allowed": storage_boundary_proof.get( + "receipt_persistence_storage_write_allowed" + ), + "storage_written": storage_boundary_proof.get( + "receipt_persistence_storage_written" + ), + }, + "abort_if_receipt_persistence_storage_boundary_allows_storage", + ), + _controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_check( + "previous_closeouts_carried_forward", + previous_closeouts_carried_forward, + { + "verifier_no_execution_receipt_proof_closeout_only": ( + guard_closeout.get( + "verifier_no_execution_receipt_proof_closeout_only" + ) + ), + "verifier_receipt_persistence_guard_proof_only": ( + guard_closeout.get( + "verifier_receipt_persistence_guard_proof_only" + ) + ), + }, + "wait_for_previous_closeouts_carry_forward", + ), + _controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": guard_closeout.get("target_file"), + "hash_matches": guard_closeout.get("hash_matches"), + "expected_sha256_present": bool(guard_closeout.get("expected_sha256")), + "actual_sha256_present": bool(guard_closeout.get("actual_sha256")), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_check( + "rollback_and_post_apply_verifier_bound", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_check( + "verifier_no_execution_receipt_proof_closeout_contract_blocks_storage_persistence_and_database_apply", + contract_blocks_storage_persistence_and_apply, + { + "permits_future_guard_proof": guard_contract.get( + "permits_future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof" + ), + "persists_verifier_receipt": guard_contract.get( + "persists_verifier_receipt" + ), + "database_apply_authorized": guard_contract.get( + "database_apply_authorized" + ), + }, + "abort_if_source_contract_allows_storage_persistence_or_database_apply", + ), + _controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_check( + "preview_has_no_side_effects_no_storage_no_persistence_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "persists_verifier_receipt_count": summary.get( + "persists_verifier_receipt_count", 0 + ), + }, + "abort_on_preview_storage_persistence_side_effect_execution_or_signing", + ), + _controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_check( + "manual_review_not_required_for_safe_preview", + guard_contract.get("manual_review_mode") == "exception_only" + and safety.get("manual_review_mode") == "exception_only", + { + "contract_manual_review_mode": guard_contract.get( + "manual_review_mode" + ), + "safety_manual_review_mode": safety.get("manual_review_mode"), + }, + "keep_manual_review_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_VERIFIER_RECEIPT_PERSISTENCE_GUARD_PROOF_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_VERIFIER_NO_EXECUTION_RECEIPT_PROOF_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof = { + "verifier_receipt_persistence_guard_proof_closeout_id": closeout_id, + "receipt_persistence_storage_boundary_proof_id": storage_boundary_id, + "source_verifier_no_execution_receipt_proof_closeout_id": source_closeout_id, + "source_verifier_receipt_persistence_guard_proof_id": source_guard_id, + "source_verifier_no_execution_receipt_proof_id": source_receipt_id, + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout": ( + closeout_ready + ), + "verifier_receipt_persistence_guard_proof_closeout_ready": closeout_ready, + "verifier_no_execution_receipt_proof_closeout_ready": guard_closeout_ready, + "verifier_receipt_persistence_guard_proof_ready": guard_proof_ready, + "receipt_persistence_storage_boundary_proof_bound": closeout_ready, + "receipt_persistence_storage_boundary_locked": True, + "receipt_persistence_storage_write_allowed": False, + "receipt_persistence_storage_written": False, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_receipt_persistence_storage_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout = { + "verifier_receipt_persistence_guard_proof_closeout_id": closeout_id, + "authorization_material_type": ( + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout" + ), + "source_verifier_no_execution_receipt_proof_closeout_id": source_closeout_id, + "source_verifier_receipt_persistence_guard_proof_id": source_guard_id, + "source_verifier_no_execution_receipt_proof_id": source_receipt_id, + "required_command_shape_hash": guard_proof.get( + "required_command_shape_hash" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof": ( + closeout_ready + ), + "verifier_receipt_persistence_guard_proof_closeout_fields": closeout_fields, + "verifier_receipt_persistence_guard_proof_closeout_field_count": len( + closeout_fields + ), + "verifier_receipt_persistence_guard_proof_closeout_acceptance_gates": ( + acceptance_gates + ), + "verifier_receipt_persistence_guard_proof_closeout_acceptance_gate_count": len( + acceptance_gates + ), + "receipt_persistence_storage_boundary_proof": storage_boundary_proof, + "receipt_persistence_storage_boundary_proof_count": 1, + "receipt_persistence_storage_boundary_proof_field_count": len( + storage_boundary_fields + ), + "verifier_receipt_persistence_guard_proof": guard_proof, + "verifier_receipt_persistence_guard_proof_count": 1, + "verifier_no_execution_receipt_proof_closeout": guard_closeout, + "verifier_no_execution_receipt_proof_closeout_count": 1, + "verifier_no_execution_receipt_proof": receipt_proof, + "verifier_no_execution_receipt_proof_count": 1, + "target_file": guard_closeout.get("target_file"), + "expected_sha256": guard_closeout.get("expected_sha256"), + "actual_sha256": guard_closeout.get("actual_sha256"), + "hash_matches": guard_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "verifier_receipt_persistence_guard_proof_closeout_only": True, + "receipt_persistence_storage_boundary_proof_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "receipt_persistence_storage_boundary_locked": True, + "receipt_persistence_storage_write_allowed": False, + "receipt_persistence_storage_written": False, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_receipt_persistence_storage_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + } + controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_contract = { + "mode": "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_and_receipt_persistence_storage_boundary_proof_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-verifier-receipt-persistence-guard-proof-closeout" + ), + "source_verifier_no_execution_receipt_proof_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-verifier-no-execution-receipt-proof-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof": ( + closeout_ready + ), + "receipt_persistence_storage_boundary_locked": True, + "receipt_persistence_storage_write_allowed": False, + "receipt_persistence_storage_written": False, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_invocation_locked": True, + "verifier_invoked": False, + "verifier_receipt_present": False, + "ready_for_database_apply_now": False, + "ready_for_receipt_persistence_storage_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": False, + "signs_database_apply_authorization": False, + "writes_database": False, + "executes_in_preview": False, + "secret_material_required_in_preview": False, + "manual_review_mode": "exception_only", + } + output_summary = dict(summary) + output_summary.update( + { + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_check_count": len( + checks + ), + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_pass_count": ( + passed_count + ), + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_count": 1, + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_field_count": len( + closeout_fields + ), + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_acceptance_gate_count": len( + acceptance_gates + ), + "receipt_persistence_storage_boundary_proof_count": 1, + "receipt_persistence_storage_boundary_proof_field_count": len( + storage_boundary_fields + ), + "receipt_persistence_storage_boundary_locked_count": 1, + "receipt_persistence_storage_write_allowed_count": 0, + "receipt_persistence_storage_written_count": 0, + "verifier_receipt_persistence_allowed_count": 0, + "verifier_receipt_persisted_count": 0, + "persists_verifier_receipt_count": 0, + "verifier_invoked_count": 0, + "verifier_receipt_present_count": 0, + "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, + "dry_run_executor_invoked_count": 0, + "runner_invocation_performed_count": 0, + "endpoint_executed_count": 0, + "sql_executed_count": 0, + "database_written_count": 0, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + } + ) + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_VERIFIER_RECEIPT_PERSISTENCE_GUARD_PROOF_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(storage_closeout_result.get("success")), + "generated_at": storage_closeout_result.get("generated_at"), + "source_policy": storage_closeout_result.get("policy"), + "stats": storage_closeout_result.get("stats") or {}, + "summary": output_summary, + "future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof": ( + future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof + ), + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout": ( + controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout + ), + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_contract": ( + controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_contract + ), + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_checks": ( + checks + ), + "source_controlled_dry_run_verifier_no_execution_receipt_proof_closeout_summary": ( + summary + ), + "source_controlled_dry_run_verifier_no_execution_receipt_proof_closeout_contract": ( + guard_contract + ), + "source_controlled_dry_run_verifier_no_execution_receipt_proof_closeout": ( + guard_closeout + ), + "source_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof": ( + future_guard + ), + "safety": { + "read_only_db_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future receipt persistence storage boundary proof closeout.", + "Keep receipt persistence storage disabled until a later storage boundary closeout proves the write boundary.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, verifier invocation, verifier receipt persistence, receipt storage, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out the storage boundary proof and add a no-write ledger proof.""" + ledger_closeout_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_storage = ( + ledger_closeout_result.get( + "future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof" + ) + or {} + ) + storage_closeout = ( + ledger_closeout_result.get( + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout" + ) + or {} + ) + storage_contract = ( + ledger_closeout_result.get( + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_contract" + ) + or {} + ) + summary = ledger_closeout_result.get("summary") or {} + safety = ledger_closeout_result.get("safety") or {} + storage_proof = ( + storage_closeout.get("receipt_persistence_storage_boundary_proof") or {} + ) + guard_proof = ( + storage_closeout.get("verifier_receipt_persistence_guard_proof") or {} + ) + previous_guard_closeout = ( + storage_closeout.get("verifier_no_execution_receipt_proof_closeout") or {} + ) + rollback_binding = storage_closeout.get("rollback_binding") or {} + verifier_binding = storage_closeout.get("post_apply_verifier_binding") or {} + source_closeout_id = storage_closeout.get( + "verifier_receipt_persistence_guard_proof_closeout_id" + ) + source_storage_id = storage_proof.get("storage_boundary_proof_id") + source_guard_id = storage_proof.get( + "source_verifier_receipt_persistence_guard_proof_id" + ) + closeout_id = ( + _db_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_id( + ledger_closeout_result + ) + ) + ledger_id = f"{closeout_id}-storage-boundary-no-write-ledger-proof" + closeout_fields = [ + "receipt_persistence_storage_boundary_proof_closeout_id", + "source_verifier_receipt_persistence_guard_proof_closeout_id", + "source_receipt_persistence_storage_boundary_proof_id", + "source_verifier_receipt_persistence_guard_proof_id", + "storage_boundary_no_write_ledger_proof_id", + "required_command_shape_hash", + "target_migration_file", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "storage_boundary_write_locked", + "storage_boundary_written", + "abort_conditions", + ] + acceptance_gates = [ + "verifier_receipt_persistence_guard_proof_closeout_ready", + "source_chain_ids_match", + "receipt_persistence_storage_boundary_proof_ready", + "receipt_persistence_storage_boundary_proof_no_write", + "storage_boundary_no_write_ledger_proof_bound", + "storage_boundary_no_write_ledger_proof_blocks_write", + "previous_closeouts_carried_forward", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "no_secret_signature_ledger_storage_persistence_or_database_apply", + ] + no_write_ledger_fields = [ + "ledger_proof_id", + "source_receipt_persistence_storage_boundary_proof_closeout_id", + "source_verifier_receipt_persistence_guard_proof_closeout_id", + "source_receipt_persistence_storage_boundary_proof_id", + "source_verifier_receipt_persistence_guard_proof_id", + "ledger_mode", + "storage_boundary_write_locked", + "storage_boundary_write_allowed", + "storage_boundary_written", + "ledger_write_allowed", + "sql_execution_allowed", + "database_apply_authorized", + ] + abort_conditions = [ + "abort_if_verifier_receipt_persistence_guard_proof_closeout_not_ready", + "abort_if_source_chain_ids_do_not_match", + "abort_if_receipt_persistence_storage_boundary_proof_missing", + "abort_if_receipt_persistence_storage_boundary_allows_write", + "abort_if_storage_boundary_no_write_ledger_missing", + "abort_if_storage_boundary_no_write_ledger_allows_write", + "abort_if_storage_boundary_or_ledger_written", + "abort_if_endpoint_or_sql_execution_is_allowed", + "abort_if_database_write_or_apply_is_allowed", + "abort_if_target_migration_hash_changes", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_any_secret_signature_ledger_storage_persistence_or_apply_material_is_present", + ] + no_write_ledger_proof = { + "ledger_proof_id": ledger_id, + "source_receipt_persistence_storage_boundary_proof_closeout_id": closeout_id, + "source_verifier_receipt_persistence_guard_proof_closeout_id": ( + source_closeout_id + ), + "source_receipt_persistence_storage_boundary_proof_id": source_storage_id, + "source_verifier_receipt_persistence_guard_proof_id": source_guard_id, + "source_verifier_no_execution_receipt_proof_closeout_id": ( + storage_proof.get("source_verifier_no_execution_receipt_proof_closeout_id") + ), + "source_verifier_no_execution_receipt_proof_id": ( + storage_proof.get("source_verifier_no_execution_receipt_proof_id") + ), + "required_command_shape_hash": storage_proof.get( + "required_command_shape_hash" + ), + "ledger_status": "storage_boundary_no_write_ledger_proof_preview_ready", + "ledger_mode": "storage_boundary_no_write_ledger_proof_preview_only", + "storage_boundary_write_locked": True, + "storage_boundary_write_allowed": False, + "storage_boundary_written": False, + "ledger_write_allowed": False, + "ledger_written": False, + "ledger_receipt_present": False, + "ledger_receipt_required": False, + "receipt_persistence_storage_boundary_locked": True, + "receipt_persistence_storage_write_allowed": False, + "receipt_persistence_storage_written": False, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_receipt_present": False, + "verifier_receipt_required": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "ready_for_storage_boundary_ledger_write_now": False, + "ready_for_receipt_persistence_storage_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_database_apply_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "database_apply_authorized": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "storage_boundary_no_write_ledger_proof_field_count": len( + no_write_ledger_fields + ), + "storage_boundary_no_write_ledger_proof_fields": no_write_ledger_fields, + } + storage_closeout_ready = ( + ledger_closeout_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_VERIFIER_RECEIPT_PERSISTENCE_GUARD_PROOF_CLOSEOUT_READY" + and summary.get( + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_ready_count" + ) + == 1 + and summary.get( + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_pass_count" + ) + == summary.get( + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_check_count" + ) + ) + source_chain_ids_match = ( + bool(source_closeout_id) + and source_closeout_id + == future_storage.get("verifier_receipt_persistence_guard_proof_closeout_id") + == storage_proof.get( + "source_verifier_receipt_persistence_guard_proof_closeout_id" + ) + == no_write_ledger_proof.get( + "source_verifier_receipt_persistence_guard_proof_closeout_id" + ) + and source_storage_id + == future_storage.get("receipt_persistence_storage_boundary_proof_id") + == no_write_ledger_proof.get( + "source_receipt_persistence_storage_boundary_proof_id" + ) + and source_guard_id + == storage_proof.get("source_verifier_receipt_persistence_guard_proof_id") + == guard_proof.get("guard_proof_id") + == no_write_ledger_proof.get( + "source_verifier_receipt_persistence_guard_proof_id" + ) + and storage_proof.get("required_command_shape_hash") + == guard_proof.get("required_command_shape_hash") + == no_write_ledger_proof.get("required_command_shape_hash") + ) + storage_boundary_proof_ready = ( + storage_closeout_ready + and storage_proof.get("storage_boundary_status") + == "receipt_persistence_storage_boundary_proof_preview_ready" + and storage_proof.get("storage_boundary_proof_id") + == future_storage.get("receipt_persistence_storage_boundary_proof_id") + and int( + storage_proof.get( + "receipt_persistence_storage_boundary_proof_field_count" + ) + or 0 + ) + == 12 + and summary.get("receipt_persistence_storage_boundary_proof_count") == 1 + ) + storage_boundary_proof_no_write = ( + storage_proof.get("storage_boundary_mode") + == "receipt_persistence_storage_boundary_proof_preview_only" + and storage_proof.get("receipt_persistence_storage_boundary_locked") + is True + and storage_proof.get("receipt_persistence_storage_write_allowed") is False + and storage_proof.get("receipt_persistence_storage_written") is False + and storage_proof.get("verifier_receipt_persistence_allowed") is False + and storage_proof.get("verifier_receipt_persisted") is False + and storage_proof.get("persists_verifier_receipt") is False + and storage_proof.get("verifier_invoked") is False + and storage_proof.get("dry_run_executor_invoked") is False + and storage_proof.get("runner_invocation_performed") is False + and storage_proof.get("endpoint_executed") is False + and storage_proof.get("sql_executed") is False + and storage_proof.get("database_written") is False + and storage_proof.get("endpoint_execution_allowed") is False + and storage_proof.get("sql_execution_allowed") is False + and storage_proof.get("database_write_allowed") is False + and storage_proof.get("database_apply_authorized") is False + and storage_proof.get("executes_database_apply") is False + and storage_proof.get("executes_endpoint") is False + and storage_proof.get("executes_sql") is False + and storage_proof.get("writes_database") is False + and storage_proof.get("stdout_included") is False + and storage_proof.get("stderr_included") is False + ) + no_write_ledger_bound = ( + storage_boundary_proof_ready + and bool(no_write_ledger_proof.get("ledger_proof_id")) + and no_write_ledger_proof.get( + "source_receipt_persistence_storage_boundary_proof_closeout_id" + ) + == closeout_id + and no_write_ledger_proof.get( + "source_verifier_receipt_persistence_guard_proof_closeout_id" + ) + == source_closeout_id + and no_write_ledger_proof.get( + "source_receipt_persistence_storage_boundary_proof_id" + ) + == source_storage_id + and no_write_ledger_proof.get("required_command_shape_hash") + == storage_proof.get("required_command_shape_hash") + and int( + no_write_ledger_proof.get( + "storage_boundary_no_write_ledger_proof_field_count" + ) + or 0 + ) + == len(no_write_ledger_fields) + ) + no_write_ledger_blocks_write = ( + no_write_ledger_proof.get("ledger_mode") + == "storage_boundary_no_write_ledger_proof_preview_only" + and no_write_ledger_proof.get("storage_boundary_write_locked") is True + and no_write_ledger_proof.get("storage_boundary_write_allowed") is False + and no_write_ledger_proof.get("storage_boundary_written") is False + and no_write_ledger_proof.get("ledger_write_allowed") is False + and no_write_ledger_proof.get("ledger_written") is False + and no_write_ledger_proof.get("receipt_persistence_storage_write_allowed") + is False + and no_write_ledger_proof.get("receipt_persistence_storage_written") + is False + and no_write_ledger_proof.get("verifier_receipt_persistence_allowed") + is False + and no_write_ledger_proof.get("verifier_receipt_persisted") is False + and no_write_ledger_proof.get("persists_verifier_receipt") is False + and no_write_ledger_proof.get("verifier_invoked") is False + and no_write_ledger_proof.get("dry_run_executor_invoked") is False + and no_write_ledger_proof.get("runner_invocation_performed") is False + and no_write_ledger_proof.get("endpoint_executed") is False + and no_write_ledger_proof.get("sql_executed") is False + and no_write_ledger_proof.get("database_written") is False + and no_write_ledger_proof.get("endpoint_execution_allowed") is False + and no_write_ledger_proof.get("sql_execution_allowed") is False + and no_write_ledger_proof.get("database_write_allowed") is False + and no_write_ledger_proof.get("database_apply_authorized") is False + and no_write_ledger_proof.get("executes_database_apply") is False + and no_write_ledger_proof.get("executes_endpoint") is False + and no_write_ledger_proof.get("executes_sql") is False + and no_write_ledger_proof.get("writes_database") is False + and no_write_ledger_proof.get("stdout_included") is False + and no_write_ledger_proof.get("stderr_included") is False + ) + previous_closeouts_carried_forward = ( + storage_closeout.get("verifier_receipt_persistence_guard_proof_closeout_only") + is True + and storage_closeout.get("receipt_persistence_storage_boundary_proof_only") + is True + and previous_guard_closeout.get( + "verifier_no_execution_receipt_proof_closeout_only" + ) + is True + and storage_boundary_proof_no_write + ) + target_hash_locked = ( + storage_closeout.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(storage_closeout.get("expected_sha256")) + and bool(storage_closeout.get("actual_sha256")) + and storage_closeout.get("expected_sha256") + == storage_closeout.get("actual_sha256") + and storage_closeout.get("hash_matches") is True + and storage_closeout.get("target_migration_hash_locked") is True + ) + rollback_and_verifier_bound = ( + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False + ) + contract_blocks_ledger_storage_persistence_and_apply = ( + storage_contract.get( + "permits_future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof" + ) + is True + and storage_contract.get("receipt_persistence_storage_write_allowed") + is False + and storage_contract.get("receipt_persistence_storage_written") is False + and storage_contract.get("persists_verifier_receipt") is False + and storage_contract.get("executes_database_apply") is False + and storage_contract.get("database_apply_authorized") is False + and storage_contract.get("writes_database") is False + ) + side_effect_free = ( + summary.get("reads_secret_count", 0) == 0 + and summary.get("executes_endpoint_count", 0) == 0 + and summary.get("executes_sql_count", 0) == 0 + and summary.get("writes_database_count", 0) == 0 + and summary.get("signs_database_apply_authorization_count", 0) == 0 + and summary.get("receipt_persistence_storage_write_allowed_count", 0) == 0 + and summary.get("receipt_persistence_storage_written_count", 0) == 0 + and summary.get("persists_verifier_receipt_count", 0) == 0 + and safety.get("persists_verifier_receipt") is False + ) + checks = [ + _controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_check( + "verifier_receipt_persistence_guard_proof_closeout_ready", + storage_closeout_ready, + { + "result": ledger_closeout_result.get("result"), + "ready_count": summary.get( + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_ready_count" + ), + }, + "wait_for_verifier_receipt_persistence_guard_proof_closeout", + ), + _controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_check( + "source_chain_ids_match", + source_chain_ids_match, + { + "source_closeout_id": source_closeout_id, + "source_storage_id": source_storage_id, + "source_guard_id": source_guard_id, + }, + "wait_for_source_chain_alignment", + ), + _controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_check( + "receipt_persistence_storage_boundary_proof_ready", + storage_boundary_proof_ready, + { + "storage_boundary_proof_id": source_storage_id, + "storage_boundary_status": storage_proof.get( + "storage_boundary_status" + ), + "field_count": storage_proof.get( + "receipt_persistence_storage_boundary_proof_field_count" + ), + }, + "wait_for_receipt_persistence_storage_boundary_proof", + ), + _controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_check( + "receipt_persistence_storage_boundary_proof_no_write", + storage_boundary_proof_no_write, + { + "storage_boundary_mode": storage_proof.get( + "storage_boundary_mode" + ), + "storage_write_allowed": storage_proof.get( + "receipt_persistence_storage_write_allowed" + ), + "storage_written": storage_proof.get( + "receipt_persistence_storage_written" + ), + }, + "abort_if_receipt_persistence_storage_boundary_reports_write", + ), + _controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_check( + "storage_boundary_no_write_ledger_proof_bound", + no_write_ledger_bound, + { + "ledger_proof_id": no_write_ledger_proof.get("ledger_proof_id"), + "source_storage_id": no_write_ledger_proof.get( + "source_receipt_persistence_storage_boundary_proof_id" + ), + "field_count": no_write_ledger_proof.get( + "storage_boundary_no_write_ledger_proof_field_count" + ), + }, + "wait_for_storage_boundary_no_write_ledger_proof", + ), + _controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_check( + "storage_boundary_no_write_ledger_proof_blocks_write", + no_write_ledger_blocks_write, + { + "ledger_mode": no_write_ledger_proof.get("ledger_mode"), + "ledger_write_allowed": no_write_ledger_proof.get( + "ledger_write_allowed" + ), + "ledger_written": no_write_ledger_proof.get("ledger_written"), + }, + "abort_if_storage_boundary_no_write_ledger_allows_write", + ), + _controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_check( + "previous_closeouts_carried_forward", + previous_closeouts_carried_forward, + { + "verifier_receipt_persistence_guard_proof_closeout_only": ( + storage_closeout.get( + "verifier_receipt_persistence_guard_proof_closeout_only" + ) + ), + "receipt_persistence_storage_boundary_proof_only": ( + storage_closeout.get( + "receipt_persistence_storage_boundary_proof_only" + ) + ), + }, + "wait_for_previous_closeouts_carry_forward", + ), + _controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": storage_closeout.get("target_file"), + "hash_matches": storage_closeout.get("hash_matches"), + "expected_sha256_present": bool( + storage_closeout.get("expected_sha256") + ), + "actual_sha256_present": bool(storage_closeout.get("actual_sha256")), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_check( + "rollback_and_post_apply_verifier_bound", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_check( + "verifier_receipt_persistence_guard_proof_closeout_contract_blocks_ledger_storage_persistence_and_database_apply", + contract_blocks_ledger_storage_persistence_and_apply, + { + "permits_future_storage_boundary_proof": storage_contract.get( + "permits_future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof" + ), + "receipt_persistence_storage_write_allowed": storage_contract.get( + "receipt_persistence_storage_write_allowed" + ), + "database_apply_authorized": storage_contract.get( + "database_apply_authorized" + ), + }, + "abort_if_source_contract_allows_ledger_storage_persistence_or_database_apply", + ), + _controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_check( + "preview_has_no_side_effects_no_ledger_no_storage_no_persistence_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "receipt_persistence_storage_written_count": summary.get( + "receipt_persistence_storage_written_count", 0 + ), + }, + "abort_on_preview_ledger_storage_persistence_side_effect_execution_or_signing", + ), + _controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_check( + "manual_review_not_required_for_safe_preview", + storage_contract.get("manual_review_mode") == "exception_only" + and safety.get("manual_review_mode") == "exception_only", + { + "contract_manual_review_mode": storage_contract.get( + "manual_review_mode" + ), + "safety_manual_review_mode": safety.get("manual_review_mode"), + }, + "keep_manual_review_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_RECEIPT_PERSISTENCE_STORAGE_BOUNDARY_PROOF_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_VERIFIER_RECEIPT_PERSISTENCE_GUARD_PROOF_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof = { + "receipt_persistence_storage_boundary_proof_closeout_id": closeout_id, + "storage_boundary_no_write_ledger_proof_id": ledger_id, + "source_verifier_receipt_persistence_guard_proof_closeout_id": ( + source_closeout_id + ), + "source_receipt_persistence_storage_boundary_proof_id": source_storage_id, + "source_verifier_receipt_persistence_guard_proof_id": source_guard_id, + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout": ( + closeout_ready + ), + "receipt_persistence_storage_boundary_proof_closeout_ready": closeout_ready, + "verifier_receipt_persistence_guard_proof_closeout_ready": ( + storage_closeout_ready + ), + "receipt_persistence_storage_boundary_proof_ready": ( + storage_boundary_proof_ready + ), + "storage_boundary_no_write_ledger_proof_bound": closeout_ready, + "storage_boundary_write_locked": True, + "storage_boundary_write_allowed": False, + "storage_boundary_written": False, + "ledger_write_allowed": False, + "ledger_written": False, + "receipt_persistence_storage_write_allowed": False, + "receipt_persistence_storage_written": False, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_storage_boundary_ledger_write_now": False, + "ready_for_receipt_persistence_storage_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout = { + "receipt_persistence_storage_boundary_proof_closeout_id": closeout_id, + "authorization_material_type": ( + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout" + ), + "source_verifier_receipt_persistence_guard_proof_closeout_id": ( + source_closeout_id + ), + "source_receipt_persistence_storage_boundary_proof_id": source_storage_id, + "source_verifier_receipt_persistence_guard_proof_id": source_guard_id, + "required_command_shape_hash": storage_proof.get( + "required_command_shape_hash" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof": ( + closeout_ready + ), + "receipt_persistence_storage_boundary_proof_closeout_fields": ( + closeout_fields + ), + "receipt_persistence_storage_boundary_proof_closeout_field_count": len( + closeout_fields + ), + "receipt_persistence_storage_boundary_proof_closeout_acceptance_gates": ( + acceptance_gates + ), + "receipt_persistence_storage_boundary_proof_closeout_acceptance_gate_count": len( + acceptance_gates + ), + "storage_boundary_no_write_ledger_proof": no_write_ledger_proof, + "storage_boundary_no_write_ledger_proof_count": 1, + "storage_boundary_no_write_ledger_proof_field_count": len( + no_write_ledger_fields + ), + "receipt_persistence_storage_boundary_proof": storage_proof, + "receipt_persistence_storage_boundary_proof_count": 1, + "verifier_receipt_persistence_guard_proof_closeout": storage_closeout, + "verifier_receipt_persistence_guard_proof_closeout_count": 1, + "verifier_receipt_persistence_guard_proof": guard_proof, + "verifier_receipt_persistence_guard_proof_count": 1, + "target_file": storage_closeout.get("target_file"), + "expected_sha256": storage_closeout.get("expected_sha256"), + "actual_sha256": storage_closeout.get("actual_sha256"), + "hash_matches": storage_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "receipt_persistence_storage_boundary_proof_closeout_only": True, + "storage_boundary_no_write_ledger_proof_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "storage_boundary_write_locked": True, + "storage_boundary_write_allowed": False, + "storage_boundary_written": False, + "ledger_write_allowed": False, + "ledger_written": False, + "receipt_persistence_storage_boundary_locked": True, + "receipt_persistence_storage_write_allowed": False, + "receipt_persistence_storage_written": False, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "dry_run_executor_invocation_allowed": False, + "runner_invocation_allowed": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_storage_boundary_ledger_write_now": False, + "ready_for_receipt_persistence_storage_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + } + controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_contract = { + "mode": "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_and_storage_boundary_no_write_ledger_proof_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-receipt-persistence-storage-boundary-proof-closeout" + ), + "source_verifier_receipt_persistence_guard_proof_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-verifier-receipt-persistence-guard-proof-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof": ( + closeout_ready + ), + "storage_boundary_write_locked": True, + "storage_boundary_write_allowed": False, + "storage_boundary_written": False, + "ledger_write_allowed": False, + "ledger_written": False, + "receipt_persistence_storage_write_allowed": False, + "receipt_persistence_storage_written": False, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_invocation_locked": True, + "verifier_invoked": False, + "verifier_receipt_present": False, + "ready_for_database_apply_now": False, + "ready_for_storage_boundary_ledger_write_now": False, + "ready_for_receipt_persistence_storage_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": False, + "signs_database_apply_authorization": False, + "writes_database": False, + "executes_in_preview": False, + "secret_material_required_in_preview": False, + "manual_review_mode": "exception_only", + } + output_summary = dict(summary) + output_summary.update( + { + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_check_count": len( + checks + ), + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_pass_count": ( + passed_count + ), + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_count": 1, + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_field_count": len( + closeout_fields + ), + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_acceptance_gate_count": len( + acceptance_gates + ), + "storage_boundary_no_write_ledger_proof_count": 1, + "storage_boundary_no_write_ledger_proof_field_count": len( + no_write_ledger_fields + ), + "storage_boundary_write_locked_count": 1, + "storage_boundary_write_allowed_count": 0, + "storage_boundary_written_count": 0, + "ledger_write_allowed_count": 0, + "ledger_written_count": 0, + "receipt_persistence_storage_write_allowed_count": 0, + "receipt_persistence_storage_written_count": 0, + "verifier_receipt_persistence_allowed_count": 0, + "verifier_receipt_persisted_count": 0, + "persists_verifier_receipt_count": 0, + "verifier_invoked_count": 0, + "verifier_receipt_present_count": 0, + "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, + "dry_run_executor_invoked_count": 0, + "runner_invocation_performed_count": 0, + "endpoint_executed_count": 0, + "sql_executed_count": 0, + "database_written_count": 0, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + } + ) + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_RECEIPT_PERSISTENCE_STORAGE_BOUNDARY_PROOF_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(ledger_closeout_result.get("success")), + "generated_at": ledger_closeout_result.get("generated_at"), + "source_policy": ledger_closeout_result.get("policy"), + "stats": ledger_closeout_result.get("stats") or {}, + "summary": output_summary, + "future_database_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof": ( + future_database_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof + ), + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout": ( + controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout + ), + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_contract": ( + controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_contract + ), + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_checks": ( + checks + ), + "source_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_summary": ( + summary + ), + "source_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_contract": ( + storage_contract + ), + "source_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout": ( + storage_closeout + ), + "source_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof": ( + future_storage + ), + "safety": { + "read_only_db_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future storage boundary no-write ledger proof closeout.", + "Keep storage-boundary ledger writes disabled until a later no-write ledger closeout proves the ledger boundary.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, ledger writes, verifier invocation, verifier receipt persistence, receipt storage, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out the no-write ledger proof and add a retention proof.""" + retention_closeout_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_ledger = ( + retention_closeout_result.get( + "future_database_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof" + ) + or {} + ) + ledger_closeout = ( + retention_closeout_result.get( + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout" + ) + or {} + ) + ledger_contract = ( + retention_closeout_result.get( + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_contract" + ) + or {} + ) + summary = retention_closeout_result.get("summary") or {} + safety = retention_closeout_result.get("safety") or {} + no_write_ledger_proof = ( + ledger_closeout.get("storage_boundary_no_write_ledger_proof") or {} + ) + storage_proof = ( + ledger_closeout.get("receipt_persistence_storage_boundary_proof") or {} + ) + previous_storage_closeout = ( + ledger_closeout.get("verifier_receipt_persistence_guard_proof_closeout") + or {} + ) + rollback_binding = ledger_closeout.get("rollback_binding") or {} + verifier_binding = ledger_closeout.get("post_apply_verifier_binding") or {} + source_closeout_id = ledger_closeout.get( + "receipt_persistence_storage_boundary_proof_closeout_id" + ) + source_ledger_id = no_write_ledger_proof.get("ledger_proof_id") + source_storage_id = no_write_ledger_proof.get( + "source_receipt_persistence_storage_boundary_proof_id" + ) + source_guard_closeout_id = no_write_ledger_proof.get( + "source_verifier_receipt_persistence_guard_proof_closeout_id" + ) + source_guard_id = no_write_ledger_proof.get( + "source_verifier_receipt_persistence_guard_proof_id" + ) + closeout_id = ( + _db_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_id( + retention_closeout_result + ) + ) + retention_id = f"{closeout_id}-no-write-ledger-retention-proof" + closeout_fields = [ + "storage_boundary_no_write_ledger_proof_closeout_id", + "source_receipt_persistence_storage_boundary_proof_closeout_id", + "source_storage_boundary_no_write_ledger_proof_id", + "source_receipt_persistence_storage_boundary_proof_id", + "source_verifier_receipt_persistence_guard_proof_closeout_id", + "no_write_ledger_retention_proof_id", + "required_command_shape_hash", + "target_migration_file", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "ledger_retention_write_locked", + "abort_conditions", + ] + acceptance_gates = [ + "receipt_persistence_storage_boundary_proof_closeout_ready", + "source_chain_ids_match", + "storage_boundary_no_write_ledger_proof_ready", + "storage_boundary_no_write_ledger_proof_no_write", + "no_write_ledger_retention_proof_bound", + "no_write_ledger_retention_proof_blocks_persistence", + "previous_closeouts_carried_forward", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "no_secret_signature_ledger_storage_retention_persistence_or_database_apply", + ] + retention_fields = [ + "retention_proof_id", + "source_storage_boundary_no_write_ledger_proof_closeout_id", + "source_storage_boundary_no_write_ledger_proof_id", + "source_receipt_persistence_storage_boundary_proof_closeout_id", + "source_receipt_persistence_storage_boundary_proof_id", + "retention_mode", + "ledger_retention_write_locked", + "ledger_retention_write_allowed", + "ledger_retention_written", + "ledger_write_allowed", + "sql_execution_allowed", + "database_apply_authorized", + ] + abort_conditions = [ + "abort_if_receipt_persistence_storage_boundary_proof_closeout_not_ready", + "abort_if_source_chain_ids_do_not_match", + "abort_if_storage_boundary_no_write_ledger_proof_missing", + "abort_if_storage_boundary_no_write_ledger_allows_write", + "abort_if_no_write_ledger_retention_proof_missing", + "abort_if_no_write_ledger_retention_allows_write", + "abort_if_ledger_or_retention_written", + "abort_if_endpoint_or_sql_execution_is_allowed", + "abort_if_database_write_or_apply_is_allowed", + "abort_if_target_migration_hash_changes", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_any_secret_signature_ledger_retention_storage_persistence_or_apply_material_is_present", + ] + retention_proof = { + "retention_proof_id": retention_id, + "source_storage_boundary_no_write_ledger_proof_closeout_id": closeout_id, + "source_storage_boundary_no_write_ledger_proof_id": source_ledger_id, + "source_receipt_persistence_storage_boundary_proof_closeout_id": ( + source_closeout_id + ), + "source_receipt_persistence_storage_boundary_proof_id": source_storage_id, + "source_verifier_receipt_persistence_guard_proof_closeout_id": ( + source_guard_closeout_id + ), + "source_verifier_receipt_persistence_guard_proof_id": source_guard_id, + "required_command_shape_hash": no_write_ledger_proof.get( + "required_command_shape_hash" + ), + "retention_status": "no_write_ledger_retention_proof_preview_ready", + "retention_mode": "no_write_ledger_retention_proof_preview_only", + "ledger_retention_write_locked": True, + "ledger_retention_write_allowed": False, + "ledger_retention_written": False, + "retention_receipt_present": False, + "retention_receipt_required": False, + "storage_boundary_write_locked": True, + "storage_boundary_write_allowed": False, + "storage_boundary_written": False, + "ledger_write_allowed": False, + "ledger_written": False, + "ledger_receipt_present": False, + "ledger_receipt_required": False, + "receipt_persistence_storage_boundary_locked": True, + "receipt_persistence_storage_write_allowed": False, + "receipt_persistence_storage_written": False, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_receipt_present": False, + "verifier_receipt_required": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "ready_for_no_write_ledger_retention_now": False, + "ready_for_storage_boundary_ledger_write_now": False, + "ready_for_receipt_persistence_storage_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_database_apply_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "database_apply_authorized": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "no_write_ledger_retention_proof_field_count": len(retention_fields), + "no_write_ledger_retention_proof_fields": retention_fields, + } + ledger_closeout_ready = ( + retention_closeout_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_RECEIPT_PERSISTENCE_STORAGE_BOUNDARY_PROOF_CLOSEOUT_READY" + and summary.get( + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_ready_count" + ) + == 1 + and summary.get( + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_pass_count" + ) + == summary.get( + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_check_count" + ) + ) + source_chain_ids_match = ( + bool(source_closeout_id) + and source_closeout_id + == future_ledger.get( + "receipt_persistence_storage_boundary_proof_closeout_id" + ) + == no_write_ledger_proof.get( + "source_receipt_persistence_storage_boundary_proof_closeout_id" + ) + == retention_proof.get( + "source_receipt_persistence_storage_boundary_proof_closeout_id" + ) + and source_ledger_id + == future_ledger.get("storage_boundary_no_write_ledger_proof_id") + == retention_proof.get("source_storage_boundary_no_write_ledger_proof_id") + and source_storage_id + == future_ledger.get("source_receipt_persistence_storage_boundary_proof_id") + == storage_proof.get("storage_boundary_proof_id") + == retention_proof.get( + "source_receipt_persistence_storage_boundary_proof_id" + ) + and source_guard_closeout_id + == future_ledger.get( + "source_verifier_receipt_persistence_guard_proof_closeout_id" + ) + == retention_proof.get( + "source_verifier_receipt_persistence_guard_proof_closeout_id" + ) + and source_guard_id + == future_ledger.get("source_verifier_receipt_persistence_guard_proof_id") + == retention_proof.get("source_verifier_receipt_persistence_guard_proof_id") + and no_write_ledger_proof.get("required_command_shape_hash") + == storage_proof.get("required_command_shape_hash") + == retention_proof.get("required_command_shape_hash") + ) + no_write_ledger_ready = ( + ledger_closeout_ready + and no_write_ledger_proof.get("ledger_status") + == "storage_boundary_no_write_ledger_proof_preview_ready" + and no_write_ledger_proof.get("ledger_proof_id") + == future_ledger.get("storage_boundary_no_write_ledger_proof_id") + and int( + no_write_ledger_proof.get( + "storage_boundary_no_write_ledger_proof_field_count" + ) + or 0 + ) + == 12 + and summary.get("storage_boundary_no_write_ledger_proof_count") == 1 + ) + no_write_ledger_no_write = ( + no_write_ledger_proof.get("ledger_mode") + == "storage_boundary_no_write_ledger_proof_preview_only" + and no_write_ledger_proof.get("storage_boundary_write_locked") is True + and no_write_ledger_proof.get("storage_boundary_write_allowed") is False + and no_write_ledger_proof.get("storage_boundary_written") is False + and no_write_ledger_proof.get("ledger_write_allowed") is False + and no_write_ledger_proof.get("ledger_written") is False + and no_write_ledger_proof.get("receipt_persistence_storage_write_allowed") + is False + and no_write_ledger_proof.get("receipt_persistence_storage_written") + is False + and no_write_ledger_proof.get("verifier_receipt_persistence_allowed") + is False + and no_write_ledger_proof.get("verifier_receipt_persisted") is False + and no_write_ledger_proof.get("persists_verifier_receipt") is False + and no_write_ledger_proof.get("verifier_invoked") is False + and no_write_ledger_proof.get("dry_run_executor_invoked") is False + and no_write_ledger_proof.get("runner_invocation_performed") is False + and no_write_ledger_proof.get("endpoint_executed") is False + and no_write_ledger_proof.get("sql_executed") is False + and no_write_ledger_proof.get("database_written") is False + and no_write_ledger_proof.get("endpoint_execution_allowed") is False + and no_write_ledger_proof.get("sql_execution_allowed") is False + and no_write_ledger_proof.get("database_write_allowed") is False + and no_write_ledger_proof.get("database_apply_authorized") is False + and no_write_ledger_proof.get("executes_database_apply") is False + and no_write_ledger_proof.get("executes_endpoint") is False + and no_write_ledger_proof.get("executes_sql") is False + and no_write_ledger_proof.get("writes_database") is False + and no_write_ledger_proof.get("stdout_included") is False + and no_write_ledger_proof.get("stderr_included") is False + ) + retention_bound = ( + no_write_ledger_ready + and bool(retention_proof.get("retention_proof_id")) + and retention_proof.get( + "source_storage_boundary_no_write_ledger_proof_closeout_id" + ) + == closeout_id + and retention_proof.get("source_storage_boundary_no_write_ledger_proof_id") + == source_ledger_id + and retention_proof.get( + "source_receipt_persistence_storage_boundary_proof_closeout_id" + ) + == source_closeout_id + and retention_proof.get("source_receipt_persistence_storage_boundary_proof_id") + == source_storage_id + and retention_proof.get("required_command_shape_hash") + == no_write_ledger_proof.get("required_command_shape_hash") + and int( + retention_proof.get("no_write_ledger_retention_proof_field_count") + or 0 + ) + == len(retention_fields) + ) + retention_blocks_persistence = ( + retention_proof.get("retention_mode") + == "no_write_ledger_retention_proof_preview_only" + and retention_proof.get("ledger_retention_write_locked") is True + and retention_proof.get("ledger_retention_write_allowed") is False + and retention_proof.get("ledger_retention_written") is False + and retention_proof.get("ledger_write_allowed") is False + and retention_proof.get("ledger_written") is False + and retention_proof.get("receipt_persistence_storage_write_allowed") + is False + and retention_proof.get("receipt_persistence_storage_written") is False + and retention_proof.get("verifier_receipt_persistence_allowed") is False + and retention_proof.get("verifier_receipt_persisted") is False + and retention_proof.get("persists_verifier_receipt") is False + and retention_proof.get("verifier_invoked") is False + and retention_proof.get("dry_run_executor_invoked") is False + and retention_proof.get("runner_invocation_performed") is False + and retention_proof.get("endpoint_executed") is False + and retention_proof.get("sql_executed") is False + and retention_proof.get("database_written") is False + and retention_proof.get("endpoint_execution_allowed") is False + and retention_proof.get("sql_execution_allowed") is False + and retention_proof.get("database_write_allowed") is False + and retention_proof.get("database_apply_authorized") is False + and retention_proof.get("executes_database_apply") is False + and retention_proof.get("executes_endpoint") is False + and retention_proof.get("executes_sql") is False + and retention_proof.get("writes_database") is False + and retention_proof.get("stdout_included") is False + and retention_proof.get("stderr_included") is False + ) + previous_closeouts_carried_forward = ( + ledger_closeout.get("receipt_persistence_storage_boundary_proof_closeout_only") + is True + and ledger_closeout.get("storage_boundary_no_write_ledger_proof_only") + is True + and previous_storage_closeout.get( + "verifier_receipt_persistence_guard_proof_closeout_only" + ) + is True + and no_write_ledger_no_write + ) + target_hash_locked = ( + ledger_closeout.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(ledger_closeout.get("expected_sha256")) + and bool(ledger_closeout.get("actual_sha256")) + and ledger_closeout.get("expected_sha256") + == ledger_closeout.get("actual_sha256") + and ledger_closeout.get("hash_matches") is True + and ledger_closeout.get("target_migration_hash_locked") is True + ) + rollback_and_verifier_bound = ( + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False + ) + contract_blocks_retention_storage_persistence_and_apply = ( + ledger_contract.get( + "permits_future_database_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof" + ) + is True + and ledger_contract.get("storage_boundary_write_allowed") is False + and ledger_contract.get("storage_boundary_written") is False + and ledger_contract.get("ledger_write_allowed") is False + and ledger_contract.get("ledger_written") is False + and ledger_contract.get("receipt_persistence_storage_write_allowed") + is False + and ledger_contract.get("receipt_persistence_storage_written") is False + and ledger_contract.get("persists_verifier_receipt") is False + and ledger_contract.get("executes_database_apply") is False + and ledger_contract.get("database_apply_authorized") is False + and ledger_contract.get("writes_database") is False + ) + side_effect_free = ( + summary.get("reads_secret_count", 0) == 0 + and summary.get("executes_endpoint_count", 0) == 0 + and summary.get("executes_sql_count", 0) == 0 + and summary.get("writes_database_count", 0) == 0 + and summary.get("signs_database_apply_authorization_count", 0) == 0 + and summary.get("ledger_write_allowed_count", 0) == 0 + and summary.get("ledger_written_count", 0) == 0 + and summary.get("receipt_persistence_storage_write_allowed_count", 0) == 0 + and summary.get("receipt_persistence_storage_written_count", 0) == 0 + and summary.get("persists_verifier_receipt_count", 0) == 0 + and safety.get("persists_verifier_receipt") is False + ) + checks = [ + _controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_check( + "receipt_persistence_storage_boundary_proof_closeout_ready", + ledger_closeout_ready, + { + "result": retention_closeout_result.get("result"), + "ready_count": summary.get( + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_ready_count" + ), + }, + "wait_for_receipt_persistence_storage_boundary_proof_closeout", + ), + _controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_check( + "source_chain_ids_match", + source_chain_ids_match, + { + "source_closeout_id": source_closeout_id, + "source_ledger_id": source_ledger_id, + "source_storage_id": source_storage_id, + "source_guard_closeout_id": source_guard_closeout_id, + }, + "wait_for_source_chain_alignment", + ), + _controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_check( + "storage_boundary_no_write_ledger_proof_ready", + no_write_ledger_ready, + { + "ledger_proof_id": source_ledger_id, + "ledger_status": no_write_ledger_proof.get("ledger_status"), + "field_count": no_write_ledger_proof.get( + "storage_boundary_no_write_ledger_proof_field_count" + ), + }, + "wait_for_storage_boundary_no_write_ledger_proof", + ), + _controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_check( + "storage_boundary_no_write_ledger_proof_no_write", + no_write_ledger_no_write, + { + "ledger_mode": no_write_ledger_proof.get("ledger_mode"), + "ledger_write_allowed": no_write_ledger_proof.get( + "ledger_write_allowed" + ), + "database_apply_authorized": no_write_ledger_proof.get( + "database_apply_authorized" + ), + }, + "abort_if_storage_boundary_no_write_ledger_allows_write", + ), + _controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_check( + "no_write_ledger_retention_proof_bound", + retention_bound, + { + "retention_proof_id": retention_id, + "source_storage_boundary_no_write_ledger_proof_id": source_ledger_id, + "field_count": retention_proof.get( + "no_write_ledger_retention_proof_field_count" + ), + }, + "wait_for_no_write_ledger_retention_proof", + ), + _controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_check( + "no_write_ledger_retention_proof_blocks_persistence", + retention_blocks_persistence, + { + "retention_mode": retention_proof.get("retention_mode"), + "ledger_retention_write_allowed": retention_proof.get( + "ledger_retention_write_allowed" + ), + "ledger_retention_written": retention_proof.get( + "ledger_retention_written" + ), + }, + "abort_if_no_write_ledger_retention_allows_persistence", + ), + _controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_check( + "previous_closeouts_carried_forward", + previous_closeouts_carried_forward, + { + "source_closeout_only": ledger_closeout.get( + "receipt_persistence_storage_boundary_proof_closeout_only" + ), + "source_ledger_only": ledger_closeout.get( + "storage_boundary_no_write_ledger_proof_only" + ), + }, + "wait_for_previous_closeouts", + ), + _controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": ledger_closeout.get("target_file"), + "hash_matches": ledger_closeout.get("hash_matches"), + "expected_sha256_present": bool(ledger_closeout.get("expected_sha256")), + "actual_sha256_present": bool(ledger_closeout.get("actual_sha256")), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_check( + "rollback_and_post_apply_verifier_bound", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_check( + "receipt_persistence_storage_boundary_proof_closeout_contract_blocks_retention_storage_persistence_and_database_apply", + contract_blocks_retention_storage_persistence_and_apply, + { + "permits_future_storage_boundary_no_write_ledger_proof": ledger_contract.get( + "permits_future_database_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof" + ), + "ledger_write_allowed": ledger_contract.get("ledger_write_allowed"), + "database_apply_authorized": ledger_contract.get( + "database_apply_authorized" + ), + }, + "abort_if_source_contract_allows_retention_storage_persistence_or_database_apply", + ), + _controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_check( + "preview_has_no_side_effects_no_retention_no_ledger_no_storage_no_persistence_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "ledger_written_count": summary.get("ledger_written_count", 0), + }, + "abort_on_preview_retention_ledger_storage_persistence_side_effect_execution_or_signing", + ), + _controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_check( + "manual_review_not_required_for_safe_preview", + ledger_contract.get("manual_review_mode") == "exception_only" + and safety.get("manual_review_mode") == "exception_only", + { + "contract_manual_review_mode": ledger_contract.get( + "manual_review_mode" + ), + "safety_manual_review_mode": safety.get("manual_review_mode"), + }, + "keep_manual_review_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_STORAGE_BOUNDARY_NO_WRITE_LEDGER_PROOF_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_RECEIPT_PERSISTENCE_STORAGE_BOUNDARY_PROOF_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_no_write_ledger_retention_proof = { + "storage_boundary_no_write_ledger_proof_closeout_id": closeout_id, + "no_write_ledger_retention_proof_id": retention_id, + "source_receipt_persistence_storage_boundary_proof_closeout_id": ( + source_closeout_id + ), + "source_storage_boundary_no_write_ledger_proof_id": source_ledger_id, + "source_receipt_persistence_storage_boundary_proof_id": source_storage_id, + "source_verifier_receipt_persistence_guard_proof_closeout_id": ( + source_guard_closeout_id + ), + "source_verifier_receipt_persistence_guard_proof_id": source_guard_id, + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_no_write_ledger_retention_proof": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_no_write_ledger_retention_proof_closeout": ( + closeout_ready + ), + "storage_boundary_no_write_ledger_proof_closeout_ready": closeout_ready, + "receipt_persistence_storage_boundary_proof_closeout_ready": ( + ledger_closeout_ready + ), + "storage_boundary_no_write_ledger_proof_ready": no_write_ledger_ready, + "no_write_ledger_retention_proof_bound": closeout_ready, + "ledger_retention_write_locked": True, + "ledger_retention_write_allowed": False, + "ledger_retention_written": False, + "storage_boundary_write_locked": True, + "storage_boundary_write_allowed": False, + "storage_boundary_written": False, + "ledger_write_allowed": False, + "ledger_written": False, + "receipt_persistence_storage_write_allowed": False, + "receipt_persistence_storage_written": False, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "ready_for_database_apply_now": False, + "ready_for_no_write_ledger_retention_now": False, + "ready_for_storage_boundary_ledger_write_now": False, + "ready_for_receipt_persistence_storage_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout = { + "storage_boundary_no_write_ledger_proof_closeout_id": closeout_id, + "authorization_material_type": ( + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout" + ), + "source_receipt_persistence_storage_boundary_proof_closeout_id": ( + source_closeout_id + ), + "source_storage_boundary_no_write_ledger_proof_id": source_ledger_id, + "source_receipt_persistence_storage_boundary_proof_id": source_storage_id, + "source_verifier_receipt_persistence_guard_proof_closeout_id": ( + source_guard_closeout_id + ), + "source_verifier_receipt_persistence_guard_proof_id": source_guard_id, + "required_command_shape_hash": no_write_ledger_proof.get( + "required_command_shape_hash" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_no_write_ledger_retention_proof": ( + closeout_ready + ), + "storage_boundary_no_write_ledger_proof_closeout_fields": closeout_fields, + "storage_boundary_no_write_ledger_proof_closeout_field_count": len( + closeout_fields + ), + "storage_boundary_no_write_ledger_proof_closeout_acceptance_gates": ( + acceptance_gates + ), + "storage_boundary_no_write_ledger_proof_closeout_acceptance_gate_count": len( + acceptance_gates + ), + "no_write_ledger_retention_proof": retention_proof, + "no_write_ledger_retention_proof_count": 1, + "no_write_ledger_retention_proof_field_count": len(retention_fields), + "storage_boundary_no_write_ledger_proof": no_write_ledger_proof, + "storage_boundary_no_write_ledger_proof_count": 1, + "receipt_persistence_storage_boundary_proof_closeout": ledger_closeout, + "receipt_persistence_storage_boundary_proof_closeout_count": 1, + "receipt_persistence_storage_boundary_proof": storage_proof, + "receipt_persistence_storage_boundary_proof_count": 1, + "target_file": ledger_closeout.get("target_file"), + "expected_sha256": ledger_closeout.get("expected_sha256"), + "actual_sha256": ledger_closeout.get("actual_sha256"), + "hash_matches": ledger_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "storage_boundary_no_write_ledger_proof_closeout_only": True, + "no_write_ledger_retention_proof_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "ledger_retention_write_locked": True, + "ledger_retention_write_allowed": False, + "ledger_retention_written": False, + "storage_boundary_write_locked": True, + "storage_boundary_write_allowed": False, + "storage_boundary_written": False, + "ledger_write_allowed": False, + "ledger_written": False, + "receipt_persistence_storage_boundary_locked": True, + "receipt_persistence_storage_write_allowed": False, + "receipt_persistence_storage_written": False, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_no_write_ledger_retention_now": False, + "ready_for_storage_boundary_ledger_write_now": False, + "ready_for_receipt_persistence_storage_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + } + controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_contract = { + "mode": "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_and_no_write_ledger_retention_proof_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-storage-boundary-no-write-ledger-proof-closeout" + ), + "source_receipt_persistence_storage_boundary_proof_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-receipt-persistence-storage-boundary-proof-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_no_write_ledger_retention_proof": ( + closeout_ready + ), + "ledger_retention_write_locked": True, + "ledger_retention_write_allowed": False, + "ledger_retention_written": False, + "storage_boundary_write_locked": True, + "storage_boundary_write_allowed": False, + "storage_boundary_written": False, + "ledger_write_allowed": False, + "ledger_written": False, + "receipt_persistence_storage_write_allowed": False, + "receipt_persistence_storage_written": False, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_invocation_locked": True, + "verifier_invoked": False, + "verifier_receipt_present": False, + "ready_for_database_apply_now": False, + "ready_for_no_write_ledger_retention_now": False, + "ready_for_storage_boundary_ledger_write_now": False, + "ready_for_receipt_persistence_storage_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": False, + "signs_database_apply_authorization": False, + "writes_database": False, + "executes_in_preview": False, + "secret_material_required_in_preview": False, + "manual_review_mode": "exception_only", + } + output_summary = dict(summary) + output_summary.update( + { + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_check_count": len( + checks + ), + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_pass_count": ( + passed_count + ), + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_count": 1, + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_field_count": len( + closeout_fields + ), + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_acceptance_gate_count": len( + acceptance_gates + ), + "no_write_ledger_retention_proof_count": 1, + "no_write_ledger_retention_proof_field_count": len(retention_fields), + "ledger_retention_write_locked_count": 1, + "ledger_retention_write_allowed_count": 0, + "ledger_retention_written_count": 0, + "storage_boundary_write_allowed_count": 0, + "storage_boundary_written_count": 0, + "ledger_write_allowed_count": 0, + "ledger_written_count": 0, + "receipt_persistence_storage_write_allowed_count": 0, + "receipt_persistence_storage_written_count": 0, + "verifier_receipt_persistence_allowed_count": 0, + "verifier_receipt_persisted_count": 0, + "persists_verifier_receipt_count": 0, + "verifier_invoked_count": 0, + "verifier_receipt_present_count": 0, + "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, + "dry_run_executor_invoked_count": 0, + "runner_invocation_performed_count": 0, + "endpoint_executed_count": 0, + "sql_executed_count": 0, + "database_written_count": 0, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + } + ) + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_STORAGE_BOUNDARY_NO_WRITE_LEDGER_PROOF_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(retention_closeout_result.get("success")), + "generated_at": retention_closeout_result.get("generated_at"), + "source_policy": retention_closeout_result.get("policy"), + "stats": retention_closeout_result.get("stats") or {}, + "summary": output_summary, + "future_database_apply_controlled_dry_run_no_write_ledger_retention_proof": ( + future_database_apply_controlled_dry_run_no_write_ledger_retention_proof + ), + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout": ( + controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout + ), + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_contract": ( + controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_contract + ), + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_checks": ( + checks + ), + "source_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_summary": ( + summary + ), + "source_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_contract": ( + ledger_contract + ), + "source_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout": ( + ledger_closeout + ), + "source_database_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof": ( + future_ledger + ), + "safety": { + "read_only_db_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future no-write ledger retention proof closeout.", + "Keep ledger retention writes disabled until a later retention boundary closeout proves the retention path.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, ledger writes, ledger retention writes, verifier invocation, verifier receipt persistence, receipt storage, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_no_write_ledger_retention_proof_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out the retention proof and add a no-write archive proof.""" + archive_closeout_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_retention = ( + archive_closeout_result.get( + "future_database_apply_controlled_dry_run_no_write_ledger_retention_proof" + ) + or {} + ) + retention_closeout = ( + archive_closeout_result.get( + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout" + ) + or {} + ) + retention_contract = ( + archive_closeout_result.get( + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_contract" + ) + or {} + ) + summary = archive_closeout_result.get("summary") or {} + safety = archive_closeout_result.get("safety") or {} + retention_proof = retention_closeout.get("no_write_ledger_retention_proof") or {} + no_write_ledger_proof = ( + retention_closeout.get("storage_boundary_no_write_ledger_proof") or {} + ) + previous_ledger_closeout = ( + retention_closeout.get( + "receipt_persistence_storage_boundary_proof_closeout" + ) + or {} + ) + rollback_binding = retention_closeout.get("rollback_binding") or {} + verifier_binding = retention_closeout.get("post_apply_verifier_binding") or {} + source_closeout_id = retention_closeout.get( + "storage_boundary_no_write_ledger_proof_closeout_id" + ) + source_retention_id = retention_proof.get("retention_proof_id") + source_ledger_id = retention_proof.get( + "source_storage_boundary_no_write_ledger_proof_id" + ) + source_storage_closeout_id = retention_proof.get( + "source_receipt_persistence_storage_boundary_proof_closeout_id" + ) + source_storage_id = retention_proof.get( + "source_receipt_persistence_storage_boundary_proof_id" + ) + source_guard_closeout_id = retention_proof.get( + "source_verifier_receipt_persistence_guard_proof_closeout_id" + ) + source_guard_id = retention_proof.get( + "source_verifier_receipt_persistence_guard_proof_id" + ) + closeout_id = ( + _db_apply_controlled_dry_run_no_write_ledger_retention_proof_closeout_id( + archive_closeout_result + ) + ) + archive_id = f"{closeout_id}-retention-boundary-no-write-archive-proof" + closeout_fields = [ + "no_write_ledger_retention_proof_closeout_id", + "source_storage_boundary_no_write_ledger_proof_closeout_id", + "source_no_write_ledger_retention_proof_id", + "source_storage_boundary_no_write_ledger_proof_id", + "source_receipt_persistence_storage_boundary_proof_closeout_id", + "retention_boundary_no_write_archive_proof_id", + "required_command_shape_hash", + "target_migration_file", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "retention_archive_write_locked", + "abort_conditions", + ] + acceptance_gates = [ + "storage_boundary_no_write_ledger_proof_closeout_ready", + "source_chain_ids_match", + "no_write_ledger_retention_proof_ready", + "no_write_ledger_retention_proof_no_write", + "retention_boundary_no_write_archive_proof_bound", + "retention_boundary_no_write_archive_proof_blocks_archive", + "previous_closeouts_carried_forward", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "no_secret_signature_archive_retention_ledger_storage_persistence_or_database_apply", + ] + archive_fields = [ + "archive_proof_id", + "source_no_write_ledger_retention_proof_closeout_id", + "source_no_write_ledger_retention_proof_id", + "source_storage_boundary_no_write_ledger_proof_closeout_id", + "source_storage_boundary_no_write_ledger_proof_id", + "archive_mode", + "retention_archive_write_locked", + "retention_archive_write_allowed", + "retention_archive_written", + "ledger_retention_write_allowed", + "sql_execution_allowed", + "database_apply_authorized", + ] + abort_conditions = [ + "abort_if_storage_boundary_no_write_ledger_proof_closeout_not_ready", + "abort_if_source_chain_ids_do_not_match", + "abort_if_no_write_ledger_retention_proof_missing", + "abort_if_no_write_ledger_retention_allows_write", + "abort_if_retention_boundary_no_write_archive_proof_missing", + "abort_if_retention_boundary_archive_allows_write", + "abort_if_retention_archive_or_ledger_written", + "abort_if_endpoint_or_sql_execution_is_allowed", + "abort_if_database_write_or_apply_is_allowed", + "abort_if_target_migration_hash_changes", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_any_secret_signature_archive_retention_ledger_storage_persistence_or_apply_material_is_present", + ] + archive_proof = { + "archive_proof_id": archive_id, + "source_no_write_ledger_retention_proof_closeout_id": closeout_id, + "source_no_write_ledger_retention_proof_id": source_retention_id, + "source_storage_boundary_no_write_ledger_proof_closeout_id": ( + source_closeout_id + ), + "source_storage_boundary_no_write_ledger_proof_id": source_ledger_id, + "source_receipt_persistence_storage_boundary_proof_closeout_id": ( + source_storage_closeout_id + ), + "source_receipt_persistence_storage_boundary_proof_id": source_storage_id, + "source_verifier_receipt_persistence_guard_proof_closeout_id": ( + source_guard_closeout_id + ), + "source_verifier_receipt_persistence_guard_proof_id": source_guard_id, + "required_command_shape_hash": retention_proof.get( + "required_command_shape_hash" + ), + "archive_status": "retention_boundary_no_write_archive_proof_preview_ready", + "archive_mode": "retention_boundary_no_write_archive_proof_preview_only", + "retention_archive_write_locked": True, + "retention_archive_write_allowed": False, + "retention_archive_written": False, + "archive_receipt_present": False, + "archive_receipt_required": False, + "ledger_retention_write_locked": True, + "ledger_retention_write_allowed": False, + "ledger_retention_written": False, + "retention_receipt_present": False, + "retention_receipt_required": False, + "storage_boundary_write_locked": True, + "storage_boundary_write_allowed": False, + "storage_boundary_written": False, + "ledger_write_allowed": False, + "ledger_written": False, + "ledger_receipt_present": False, + "ledger_receipt_required": False, + "receipt_persistence_storage_boundary_locked": True, + "receipt_persistence_storage_write_allowed": False, + "receipt_persistence_storage_written": False, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_receipt_present": False, + "verifier_receipt_required": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "ready_for_retention_boundary_archive_now": False, + "ready_for_no_write_ledger_retention_now": False, + "ready_for_storage_boundary_ledger_write_now": False, + "ready_for_receipt_persistence_storage_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_database_apply_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "database_apply_authorized": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "retention_boundary_no_write_archive_proof_field_count": len( + archive_fields + ), + "retention_boundary_no_write_archive_proof_fields": archive_fields, + } + retention_closeout_ready = ( + archive_closeout_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_STORAGE_BOUNDARY_NO_WRITE_LEDGER_PROOF_CLOSEOUT_READY" + and summary.get( + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_ready_count" + ) + == 1 + and summary.get( + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_pass_count" + ) + == summary.get( + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_check_count" + ) + ) + source_chain_ids_match = ( + bool(source_closeout_id) + and source_closeout_id + == future_retention.get( + "storage_boundary_no_write_ledger_proof_closeout_id" + ) + == retention_proof.get( + "source_storage_boundary_no_write_ledger_proof_closeout_id" + ) + == archive_proof.get( + "source_storage_boundary_no_write_ledger_proof_closeout_id" + ) + and source_retention_id + == future_retention.get("no_write_ledger_retention_proof_id") + == archive_proof.get("source_no_write_ledger_retention_proof_id") + and source_ledger_id + == future_retention.get("source_storage_boundary_no_write_ledger_proof_id") + == no_write_ledger_proof.get("ledger_proof_id") + == archive_proof.get("source_storage_boundary_no_write_ledger_proof_id") + and source_storage_closeout_id + == future_retention.get( + "source_receipt_persistence_storage_boundary_proof_closeout_id" + ) + == archive_proof.get( + "source_receipt_persistence_storage_boundary_proof_closeout_id" + ) + and source_storage_id + == future_retention.get("source_receipt_persistence_storage_boundary_proof_id") + == archive_proof.get("source_receipt_persistence_storage_boundary_proof_id") + and source_guard_closeout_id + == future_retention.get( + "source_verifier_receipt_persistence_guard_proof_closeout_id" + ) + == archive_proof.get( + "source_verifier_receipt_persistence_guard_proof_closeout_id" + ) + and source_guard_id + == future_retention.get("source_verifier_receipt_persistence_guard_proof_id") + == archive_proof.get("source_verifier_receipt_persistence_guard_proof_id") + and retention_proof.get("required_command_shape_hash") + == no_write_ledger_proof.get("required_command_shape_hash") + == archive_proof.get("required_command_shape_hash") + ) + retention_proof_ready = ( + retention_closeout_ready + and retention_proof.get("retention_status") + == "no_write_ledger_retention_proof_preview_ready" + and retention_proof.get("retention_proof_id") + == future_retention.get("no_write_ledger_retention_proof_id") + and int( + retention_proof.get("no_write_ledger_retention_proof_field_count") + or 0 + ) + == 12 + and summary.get("no_write_ledger_retention_proof_count") == 1 + ) + retention_proof_no_write = ( + retention_proof.get("retention_mode") + == "no_write_ledger_retention_proof_preview_only" + and retention_proof.get("ledger_retention_write_locked") is True + and retention_proof.get("ledger_retention_write_allowed") is False + and retention_proof.get("ledger_retention_written") is False + and retention_proof.get("ledger_write_allowed") is False + and retention_proof.get("ledger_written") is False + and retention_proof.get("receipt_persistence_storage_write_allowed") + is False + and retention_proof.get("receipt_persistence_storage_written") is False + and retention_proof.get("verifier_receipt_persistence_allowed") is False + and retention_proof.get("verifier_receipt_persisted") is False + and retention_proof.get("persists_verifier_receipt") is False + and retention_proof.get("verifier_invoked") is False + and retention_proof.get("dry_run_executor_invoked") is False + and retention_proof.get("runner_invocation_performed") is False + and retention_proof.get("endpoint_executed") is False + and retention_proof.get("sql_executed") is False + and retention_proof.get("database_written") is False + and retention_proof.get("endpoint_execution_allowed") is False + and retention_proof.get("sql_execution_allowed") is False + and retention_proof.get("database_write_allowed") is False + and retention_proof.get("database_apply_authorized") is False + and retention_proof.get("executes_database_apply") is False + and retention_proof.get("executes_endpoint") is False + and retention_proof.get("executes_sql") is False + and retention_proof.get("writes_database") is False + and retention_proof.get("stdout_included") is False + and retention_proof.get("stderr_included") is False + ) + archive_bound = ( + retention_proof_ready + and bool(archive_proof.get("archive_proof_id")) + and archive_proof.get( + "source_no_write_ledger_retention_proof_closeout_id" + ) + == closeout_id + and archive_proof.get("source_no_write_ledger_retention_proof_id") + == source_retention_id + and archive_proof.get( + "source_storage_boundary_no_write_ledger_proof_closeout_id" + ) + == source_closeout_id + and archive_proof.get("source_storage_boundary_no_write_ledger_proof_id") + == source_ledger_id + and archive_proof.get("required_command_shape_hash") + == retention_proof.get("required_command_shape_hash") + and int( + archive_proof.get( + "retention_boundary_no_write_archive_proof_field_count" + ) + or 0 + ) + == len(archive_fields) + ) + archive_blocks_write = ( + archive_proof.get("archive_mode") + == "retention_boundary_no_write_archive_proof_preview_only" + and archive_proof.get("retention_archive_write_locked") is True + and archive_proof.get("retention_archive_write_allowed") is False + and archive_proof.get("retention_archive_written") is False + and archive_proof.get("ledger_retention_write_allowed") is False + and archive_proof.get("ledger_retention_written") is False + and archive_proof.get("ledger_write_allowed") is False + and archive_proof.get("ledger_written") is False + and archive_proof.get("receipt_persistence_storage_write_allowed") is False + and archive_proof.get("receipt_persistence_storage_written") is False + and archive_proof.get("verifier_receipt_persistence_allowed") is False + and archive_proof.get("verifier_receipt_persisted") is False + and archive_proof.get("persists_verifier_receipt") is False + and archive_proof.get("verifier_invoked") is False + and archive_proof.get("dry_run_executor_invoked") is False + and archive_proof.get("runner_invocation_performed") is False + and archive_proof.get("endpoint_executed") is False + and archive_proof.get("sql_executed") is False + and archive_proof.get("database_written") is False + and archive_proof.get("endpoint_execution_allowed") is False + and archive_proof.get("sql_execution_allowed") is False + and archive_proof.get("database_write_allowed") is False + and archive_proof.get("database_apply_authorized") is False + and archive_proof.get("executes_database_apply") is False + and archive_proof.get("executes_endpoint") is False + and archive_proof.get("executes_sql") is False + and archive_proof.get("writes_database") is False + and archive_proof.get("stdout_included") is False + and archive_proof.get("stderr_included") is False + ) + previous_closeouts_carried_forward = ( + retention_closeout.get("storage_boundary_no_write_ledger_proof_closeout_only") + is True + and retention_closeout.get("no_write_ledger_retention_proof_only") + is True + and previous_ledger_closeout.get( + "receipt_persistence_storage_boundary_proof_closeout_only" + ) + is True + and retention_proof_no_write + ) + target_hash_locked = ( + retention_closeout.get("target_file") + == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + and bool(retention_closeout.get("expected_sha256")) + and bool(retention_closeout.get("actual_sha256")) + and retention_closeout.get("expected_sha256") + == retention_closeout.get("actual_sha256") + and retention_closeout.get("hash_matches") is True + and retention_closeout.get("target_migration_hash_locked") is True + ) + rollback_and_verifier_bound = ( + bool(rollback_binding.get("rollback_binding_id")) + and rollback_binding.get("rollback_execution_authorized") is False + and rollback_binding.get("rollback_executes_sql") is False + and rollback_binding.get("rollback_writes_database") is False + and bool(verifier_binding.get("post_apply_verifier_binding_id")) + and verifier_binding.get("verifier_must_run_after_apply") is True + and verifier_binding.get("verifier_execution_authorized_in_preview") + is False + and verifier_binding.get("database_apply_authorized") is False + ) + contract_blocks_archive_retention_storage_persistence_and_apply = ( + retention_contract.get( + "permits_future_database_apply_controlled_dry_run_no_write_ledger_retention_proof" + ) + is True + and retention_contract.get("ledger_retention_write_allowed") is False + and retention_contract.get("ledger_retention_written") is False + and retention_contract.get("ledger_write_allowed") is False + and retention_contract.get("ledger_written") is False + and retention_contract.get("receipt_persistence_storage_write_allowed") + is False + and retention_contract.get("receipt_persistence_storage_written") is False + and retention_contract.get("persists_verifier_receipt") is False + and retention_contract.get("executes_database_apply") is False + and retention_contract.get("database_apply_authorized") is False + and retention_contract.get("writes_database") is False + ) + side_effect_free = ( + summary.get("reads_secret_count", 0) == 0 + and summary.get("executes_endpoint_count", 0) == 0 + and summary.get("executes_sql_count", 0) == 0 + and summary.get("writes_database_count", 0) == 0 + and summary.get("signs_database_apply_authorization_count", 0) == 0 + and summary.get("ledger_retention_write_allowed_count", 0) == 0 + and summary.get("ledger_retention_written_count", 0) == 0 + and summary.get("ledger_write_allowed_count", 0) == 0 + and summary.get("ledger_written_count", 0) == 0 + and summary.get("receipt_persistence_storage_write_allowed_count", 0) == 0 + and summary.get("receipt_persistence_storage_written_count", 0) == 0 + and summary.get("persists_verifier_receipt_count", 0) == 0 + and safety.get("persists_verifier_receipt") is False + ) + checks = [ + _controlled_dry_run_no_write_ledger_retention_proof_closeout_check( + "storage_boundary_no_write_ledger_proof_closeout_ready", + retention_closeout_ready, + { + "result": archive_closeout_result.get("result"), + "ready_count": summary.get( + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_ready_count" + ), + }, + "wait_for_storage_boundary_no_write_ledger_proof_closeout", + ), + _controlled_dry_run_no_write_ledger_retention_proof_closeout_check( + "source_chain_ids_match", + source_chain_ids_match, + { + "source_closeout_id": source_closeout_id, + "source_retention_id": source_retention_id, + "source_ledger_id": source_ledger_id, + "source_storage_closeout_id": source_storage_closeout_id, + }, + "wait_for_source_chain_alignment", + ), + _controlled_dry_run_no_write_ledger_retention_proof_closeout_check( + "no_write_ledger_retention_proof_ready", + retention_proof_ready, + { + "retention_proof_id": source_retention_id, + "retention_status": retention_proof.get("retention_status"), + "field_count": retention_proof.get( + "no_write_ledger_retention_proof_field_count" + ), + }, + "wait_for_no_write_ledger_retention_proof", + ), + _controlled_dry_run_no_write_ledger_retention_proof_closeout_check( + "no_write_ledger_retention_proof_no_write", + retention_proof_no_write, + { + "retention_mode": retention_proof.get("retention_mode"), + "ledger_retention_write_allowed": retention_proof.get( + "ledger_retention_write_allowed" + ), + "database_apply_authorized": retention_proof.get( + "database_apply_authorized" + ), + }, + "abort_if_no_write_ledger_retention_allows_write", + ), + _controlled_dry_run_no_write_ledger_retention_proof_closeout_check( + "retention_boundary_no_write_archive_proof_bound", + archive_bound, + { + "archive_proof_id": archive_id, + "source_no_write_ledger_retention_proof_id": source_retention_id, + "field_count": archive_proof.get( + "retention_boundary_no_write_archive_proof_field_count" + ), + }, + "wait_for_retention_boundary_no_write_archive_proof", + ), + _controlled_dry_run_no_write_ledger_retention_proof_closeout_check( + "retention_boundary_no_write_archive_proof_blocks_archive", + archive_blocks_write, + { + "archive_mode": archive_proof.get("archive_mode"), + "retention_archive_write_allowed": archive_proof.get( + "retention_archive_write_allowed" + ), + "retention_archive_written": archive_proof.get( + "retention_archive_written" + ), + }, + "abort_if_retention_boundary_archive_allows_write", + ), + _controlled_dry_run_no_write_ledger_retention_proof_closeout_check( + "previous_closeouts_carried_forward", + previous_closeouts_carried_forward, + { + "source_closeout_only": retention_closeout.get( + "storage_boundary_no_write_ledger_proof_closeout_only" + ), + "source_retention_only": retention_closeout.get( + "no_write_ledger_retention_proof_only" + ), + }, + "wait_for_previous_closeouts", + ), + _controlled_dry_run_no_write_ledger_retention_proof_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": retention_closeout.get("target_file"), + "hash_matches": retention_closeout.get("hash_matches"), + "expected_sha256_present": bool( + retention_closeout.get("expected_sha256") + ), + "actual_sha256_present": bool(retention_closeout.get("actual_sha256")), + }, + "require_target_migration_hash_lock", + ), + _controlled_dry_run_no_write_ledger_retention_proof_closeout_check( + "rollback_and_post_apply_verifier_bound", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_no_write_ledger_retention_proof_closeout_check( + "storage_boundary_no_write_ledger_proof_closeout_contract_blocks_archive_retention_storage_persistence_and_database_apply", + contract_blocks_archive_retention_storage_persistence_and_apply, + { + "permits_future_no_write_ledger_retention_proof": retention_contract.get( + "permits_future_database_apply_controlled_dry_run_no_write_ledger_retention_proof" + ), + "ledger_retention_write_allowed": retention_contract.get( + "ledger_retention_write_allowed" + ), + "database_apply_authorized": retention_contract.get( + "database_apply_authorized" + ), + }, + "abort_if_source_contract_allows_archive_retention_storage_persistence_or_database_apply", + ), + _controlled_dry_run_no_write_ledger_retention_proof_closeout_check( + "preview_has_no_side_effects_no_archive_no_retention_no_ledger_no_storage_no_persistence_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "ledger_retention_written_count": summary.get( + "ledger_retention_written_count", 0 + ), + }, + "abort_on_preview_archive_retention_ledger_storage_persistence_side_effect_execution_or_signing", + ), + _controlled_dry_run_no_write_ledger_retention_proof_closeout_check( + "manual_review_not_required_for_safe_preview", + retention_contract.get("manual_review_mode") == "exception_only" + and safety.get("manual_review_mode") == "exception_only", + { + "contract_manual_review_mode": retention_contract.get( + "manual_review_mode" + ), + "safety_manual_review_mode": safety.get("manual_review_mode"), + }, + "keep_manual_review_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_NO_WRITE_LEDGER_RETENTION_PROOF_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_STORAGE_BOUNDARY_NO_WRITE_LEDGER_PROOF_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof = { + "no_write_ledger_retention_proof_closeout_id": closeout_id, + "retention_boundary_no_write_archive_proof_id": archive_id, + "source_storage_boundary_no_write_ledger_proof_closeout_id": ( + source_closeout_id + ), + "source_no_write_ledger_retention_proof_id": source_retention_id, + "source_storage_boundary_no_write_ledger_proof_id": source_ledger_id, + "source_receipt_persistence_storage_boundary_proof_closeout_id": ( + source_storage_closeout_id + ), + "source_receipt_persistence_storage_boundary_proof_id": source_storage_id, + "source_verifier_receipt_persistence_guard_proof_closeout_id": ( + source_guard_closeout_id + ), + "source_verifier_receipt_persistence_guard_proof_id": source_guard_id, + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout": ( + closeout_ready + ), + "no_write_ledger_retention_proof_closeout_ready": closeout_ready, + "storage_boundary_no_write_ledger_proof_closeout_ready": ( + retention_closeout_ready + ), + "no_write_ledger_retention_proof_ready": retention_proof_ready, + "retention_boundary_no_write_archive_proof_bound": closeout_ready, + "retention_archive_write_locked": True, + "retention_archive_write_allowed": False, + "retention_archive_written": False, + "ledger_retention_write_locked": True, + "ledger_retention_write_allowed": False, + "ledger_retention_written": False, + "ledger_write_allowed": False, + "ledger_written": False, + "receipt_persistence_storage_write_allowed": False, + "receipt_persistence_storage_written": False, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "ready_for_database_apply_now": False, + "ready_for_retention_boundary_archive_now": False, + "ready_for_no_write_ledger_retention_now": False, + "ready_for_storage_boundary_ledger_write_now": False, + "ready_for_receipt_persistence_storage_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_no_write_ledger_retention_proof_closeout = { + "no_write_ledger_retention_proof_closeout_id": closeout_id, + "authorization_material_type": ( + "controlled_dry_run_no_write_ledger_retention_proof_closeout" + ), + "source_storage_boundary_no_write_ledger_proof_closeout_id": ( + source_closeout_id + ), + "source_no_write_ledger_retention_proof_id": source_retention_id, + "source_storage_boundary_no_write_ledger_proof_id": source_ledger_id, + "source_receipt_persistence_storage_boundary_proof_closeout_id": ( + source_storage_closeout_id + ), + "source_receipt_persistence_storage_boundary_proof_id": source_storage_id, + "source_verifier_receipt_persistence_guard_proof_closeout_id": ( + source_guard_closeout_id + ), + "source_verifier_receipt_persistence_guard_proof_id": source_guard_id, + "required_command_shape_hash": retention_proof.get( + "required_command_shape_hash" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof": ( + closeout_ready + ), + "no_write_ledger_retention_proof_closeout_fields": closeout_fields, + "no_write_ledger_retention_proof_closeout_field_count": len( + closeout_fields + ), + "no_write_ledger_retention_proof_closeout_acceptance_gates": ( + acceptance_gates + ), + "no_write_ledger_retention_proof_closeout_acceptance_gate_count": len( + acceptance_gates + ), + "retention_boundary_no_write_archive_proof": archive_proof, + "retention_boundary_no_write_archive_proof_count": 1, + "retention_boundary_no_write_archive_proof_field_count": len( + archive_fields + ), + "no_write_ledger_retention_proof": retention_proof, + "no_write_ledger_retention_proof_count": 1, + "storage_boundary_no_write_ledger_proof_closeout": retention_closeout, + "storage_boundary_no_write_ledger_proof_closeout_count": 1, + "storage_boundary_no_write_ledger_proof": no_write_ledger_proof, + "storage_boundary_no_write_ledger_proof_count": 1, + "target_file": retention_closeout.get("target_file"), + "expected_sha256": retention_closeout.get("expected_sha256"), + "actual_sha256": retention_closeout.get("actual_sha256"), + "hash_matches": retention_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "no_write_ledger_retention_proof_closeout_only": True, + "retention_boundary_no_write_archive_proof_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "retention_archive_write_locked": True, + "retention_archive_write_allowed": False, + "retention_archive_written": False, + "ledger_retention_write_locked": True, + "ledger_retention_write_allowed": False, + "ledger_retention_written": False, + "ledger_write_allowed": False, + "ledger_written": False, + "receipt_persistence_storage_boundary_locked": True, + "receipt_persistence_storage_write_allowed": False, + "receipt_persistence_storage_written": False, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_retention_boundary_archive_now": False, + "ready_for_no_write_ledger_retention_now": False, + "ready_for_storage_boundary_ledger_write_now": False, + "ready_for_receipt_persistence_storage_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + } + controlled_dry_run_no_write_ledger_retention_proof_closeout_contract = { + "mode": "controlled_dry_run_no_write_ledger_retention_proof_closeout_and_retention_boundary_no_write_archive_proof_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-no-write-ledger-retention-proof-closeout" + ), + "source_storage_boundary_no_write_ledger_proof_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-storage-boundary-no-write-ledger-proof-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof": ( + closeout_ready + ), + "retention_archive_write_locked": True, + "retention_archive_write_allowed": False, + "retention_archive_written": False, + "ledger_retention_write_locked": True, + "ledger_retention_write_allowed": False, + "ledger_retention_written": False, + "ledger_write_allowed": False, + "ledger_written": False, + "receipt_persistence_storage_write_allowed": False, + "receipt_persistence_storage_written": False, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_invocation_locked": True, + "verifier_invoked": False, + "verifier_receipt_present": False, + "ready_for_database_apply_now": False, + "ready_for_retention_boundary_archive_now": False, + "ready_for_no_write_ledger_retention_now": False, + "ready_for_storage_boundary_ledger_write_now": False, + "ready_for_receipt_persistence_storage_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": False, + "signs_database_apply_authorization": False, + "writes_database": False, + "executes_in_preview": False, + "secret_material_required_in_preview": False, + "manual_review_mode": "exception_only", + } + output_summary = dict(summary) + output_summary.update( + { + "controlled_dry_run_no_write_ledger_retention_proof_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_no_write_ledger_retention_proof_closeout_check_count": len( + checks + ), + "controlled_dry_run_no_write_ledger_retention_proof_closeout_pass_count": ( + passed_count + ), + "controlled_dry_run_no_write_ledger_retention_proof_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_no_write_ledger_retention_proof_closeout_count": 1, + "controlled_dry_run_no_write_ledger_retention_proof_closeout_field_count": len( + closeout_fields + ), + "controlled_dry_run_no_write_ledger_retention_proof_closeout_acceptance_gate_count": len( + acceptance_gates + ), + "retention_boundary_no_write_archive_proof_count": 1, + "retention_boundary_no_write_archive_proof_field_count": len( + archive_fields + ), + "retention_archive_write_locked_count": 1, + "retention_archive_write_allowed_count": 0, + "retention_archive_written_count": 0, + "ledger_retention_write_allowed_count": 0, + "ledger_retention_written_count": 0, + "storage_boundary_write_allowed_count": 0, + "storage_boundary_written_count": 0, + "ledger_write_allowed_count": 0, + "ledger_written_count": 0, + "receipt_persistence_storage_write_allowed_count": 0, + "receipt_persistence_storage_written_count": 0, + "verifier_receipt_persistence_allowed_count": 0, + "verifier_receipt_persisted_count": 0, + "persists_verifier_receipt_count": 0, + "verifier_invoked_count": 0, + "verifier_receipt_present_count": 0, + "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, + "dry_run_executor_invoked_count": 0, + "runner_invocation_performed_count": 0, + "endpoint_executed_count": 0, + "sql_executed_count": 0, + "database_written_count": 0, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + } + ) + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_NO_WRITE_LEDGER_RETENTION_PROOF_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(archive_closeout_result.get("success")), + "generated_at": archive_closeout_result.get("generated_at"), + "source_policy": archive_closeout_result.get("policy"), + "stats": archive_closeout_result.get("stats") or {}, + "summary": output_summary, + "future_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof": ( + future_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof + ), + "controlled_dry_run_no_write_ledger_retention_proof_closeout": ( + controlled_dry_run_no_write_ledger_retention_proof_closeout + ), + "controlled_dry_run_no_write_ledger_retention_proof_closeout_contract": ( + controlled_dry_run_no_write_ledger_retention_proof_closeout_contract + ), + "controlled_dry_run_no_write_ledger_retention_proof_closeout_checks": ( + checks + ), + "source_controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_summary": ( + summary + ), + "source_controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_contract": ( + retention_contract + ), + "source_controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout": ( + retention_closeout + ), + "source_database_apply_controlled_dry_run_no_write_ledger_retention_proof": ( + future_retention + ), + "safety": { + "read_only_db_apply_controlled_dry_run_no_write_ledger_retention_proof_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future retention boundary no-write archive proof closeout.", + "Keep retention archive writes disabled until a later archive boundary closeout proves the archive path.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, archive writes, ledger writes, ledger retention writes, verifier invocation, verifier receipt persistence, receipt storage, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out the archive proof into a sealed no-write handoff proof.""" + sealed_handoff_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_no_write_ledger_retention_proof_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_archive = ( + sealed_handoff_result.get( + "future_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof" + ) + or {} + ) + archive_closeout = ( + sealed_handoff_result.get( + "controlled_dry_run_no_write_ledger_retention_proof_closeout" + ) + or {} + ) + archive_contract = ( + sealed_handoff_result.get( + "controlled_dry_run_no_write_ledger_retention_proof_closeout_contract" + ) + or {} + ) + summary = sealed_handoff_result.get("summary") or {} + safety = sealed_handoff_result.get("safety") or {} + archive_proof = ( + archive_closeout.get("retention_boundary_no_write_archive_proof") or {} + ) + retention_proof = archive_closeout.get("no_write_ledger_retention_proof") or {} + previous_retention_closeout = ( + archive_closeout.get("storage_boundary_no_write_ledger_proof_closeout") + or {} + ) + rollback_binding = archive_closeout.get("rollback_binding") or {} + verifier_binding = archive_closeout.get("post_apply_verifier_binding") or {} + source_closeout_id = archive_closeout.get( + "no_write_ledger_retention_proof_closeout_id" + ) + source_archive_id = archive_proof.get("archive_proof_id") + source_retention_id = archive_proof.get("source_no_write_ledger_retention_proof_id") + source_storage_closeout_id = archive_proof.get( + "source_storage_boundary_no_write_ledger_proof_closeout_id" + ) + source_ledger_id = archive_proof.get( + "source_storage_boundary_no_write_ledger_proof_id" + ) + source_receipt_storage_closeout_id = archive_proof.get( + "source_receipt_persistence_storage_boundary_proof_closeout_id" + ) + source_receipt_storage_id = archive_proof.get( + "source_receipt_persistence_storage_boundary_proof_id" + ) + source_guard_closeout_id = archive_proof.get( + "source_verifier_receipt_persistence_guard_proof_closeout_id" + ) + source_guard_id = archive_proof.get( + "source_verifier_receipt_persistence_guard_proof_id" + ) + closeout_id = ( + _db_apply_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_id( + sealed_handoff_result + ) + ) + handoff_id = f"{closeout_id}-archive-retention-sealed-handoff-proof" + closeout_fields = [ + "retention_boundary_no_write_archive_proof_closeout_id", + "source_no_write_ledger_retention_proof_closeout_id", + "source_retention_boundary_no_write_archive_proof_id", + "source_no_write_ledger_retention_proof_id", + "source_storage_boundary_no_write_ledger_proof_closeout_id", + "archive_retention_sealed_handoff_proof_id", + "required_command_shape_hash", + "target_migration_file", + "expected_sha256", + "actual_sha256", + "rollback_binding_id", + "post_apply_verifier_binding_id", + ] + handoff_fields = [ + "archive_retention_sealed_handoff_proof_id", + "source_no_write_ledger_retention_proof_closeout_id", + "source_retention_boundary_no_write_archive_proof_id", + "source_no_write_ledger_retention_proof_id", + "source_storage_boundary_no_write_ledger_proof_closeout_id", + "required_command_shape_hash", + "target_migration_file", + "expected_sha256", + "actual_sha256", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "sealed_handoff_manifest_hash", + ] + acceptance_gates = [ + "no_write_ledger_retention_proof_closeout_ready", + "retention_boundary_no_write_archive_proof_ready", + "retention_boundary_no_write_archive_proof_no_write", + "archive_retention_sealed_handoff_proof_bound", + "archive_retention_sealed_handoff_proof_blocks_handoff_write", + "previous_closeouts_carried_forward", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "no_write_ledger_retention_proof_closeout_contract_blocks_handoff_archive_retention_storage_persistence_and_database_apply", + "preview_has_no_side_effects_no_handoff_no_archive_no_retention_no_ledger_no_storage_no_persistence_no_execution_no_signing", + ] + abort_conditions = [ + "abort_if_no_write_ledger_retention_proof_closeout_not_ready", + "abort_if_retention_boundary_archive_proof_missing", + "abort_if_retention_boundary_archive_write_allowed_or_written", + "abort_if_sealed_handoff_write_allowed_or_written", + "abort_if_previous_closeout_ids_do_not_match", + "abort_if_target_migration_hash_is_not_locked", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_any_secret_or_signature_material_is_included", + "abort_if_any_endpoint_sql_database_runner_verifier_or_executor_action_is_allowed", + "abort_if_manual_review_mode_is_not_exception_only", + ] + archive_closeout_ready = ( + sealed_handoff_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_NO_WRITE_LEDGER_RETENTION_PROOF_CLOSEOUT_READY" + and archive_closeout.get( + "ready_for_future_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof" + ) + is True + ) + archive_proof_ready = ( + archive_proof.get("archive_status") + == "retention_boundary_no_write_archive_proof_preview_ready" + and archive_proof.get("archive_mode") + == "retention_boundary_no_write_archive_proof_preview_only" + and archive_proof.get("retention_boundary_no_write_archive_proof_field_count") + == 12 + ) + archive_no_write = ( + archive_proof.get("retention_archive_write_locked") is True + and archive_proof.get("retention_archive_write_allowed") is False + and archive_proof.get("retention_archive_written") is False + and archive_proof.get("ledger_retention_write_allowed") is False + and archive_proof.get("ledger_retention_written") is False + and archive_proof.get("ledger_write_allowed") is False + and archive_proof.get("ledger_written") is False + and archive_proof.get("receipt_persistence_storage_write_allowed") is False + and archive_proof.get("receipt_persistence_storage_written") is False + and archive_proof.get("persists_verifier_receipt") is False + and archive_proof.get("endpoint_executed") is False + and archive_proof.get("sql_executed") is False + and archive_proof.get("database_written") is False + and archive_proof.get("database_apply_authorized") is False + ) + target_hash_locked = ( + bool(archive_closeout.get("target_file")) + and bool(archive_closeout.get("expected_sha256")) + and bool(archive_closeout.get("actual_sha256")) + and archive_closeout.get("expected_sha256") + == archive_closeout.get("actual_sha256") + and archive_closeout.get("hash_matches") is True + ) + rollback_and_verifier_bound = bool( + rollback_binding.get("rollback_binding_id") + ) and bool(verifier_binding.get("post_apply_verifier_binding_id")) + previous_closeouts_carried_forward = ( + archive_closeout.get("no_write_ledger_retention_proof_closeout_only") + is True + and archive_closeout.get("retention_boundary_no_write_archive_proof_only") + is True + and archive_proof.get("source_no_write_ledger_retention_proof_closeout_id") + == source_closeout_id + and archive_proof.get("source_no_write_ledger_retention_proof_id") + == source_retention_id + and archive_proof.get("source_storage_boundary_no_write_ledger_proof_closeout_id") + == source_storage_closeout_id + ) + contract_blocks_handoff_archive_retention_storage_persistence_and_apply = ( + archive_contract.get( + "permits_future_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof" + ) + is True + and archive_contract.get("retention_archive_write_allowed") is False + and archive_contract.get("ledger_retention_write_allowed") is False + and archive_contract.get("ledger_write_allowed") is False + and archive_contract.get("receipt_persistence_storage_write_allowed") is False + and archive_contract.get("persists_verifier_receipt") is False + and archive_contract.get("executes_database_apply") is False + and archive_contract.get("database_apply_authorized") is False + and archive_contract.get("writes_database") is False + ) + handoff_manifest = { + "source_no_write_ledger_retention_proof_closeout_id": source_closeout_id, + "source_retention_boundary_no_write_archive_proof_id": source_archive_id, + "source_no_write_ledger_retention_proof_id": source_retention_id, + "source_storage_boundary_no_write_ledger_proof_closeout_id": ( + source_storage_closeout_id + ), + "target_file": archive_closeout.get("target_file"), + "expected_sha256": archive_closeout.get("expected_sha256"), + "actual_sha256": archive_closeout.get("actual_sha256"), + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "manual_review_mode": "exception_only", + } + handoff_manifest_hash = hashlib.sha256( + json.dumps(handoff_manifest, sort_keys=True).encode("utf-8") + ).hexdigest() + handoff_bound = ( + archive_closeout_ready + and archive_proof_ready + and bool(source_closeout_id) + and bool(source_archive_id) + and bool(source_retention_id) + and len(handoff_fields) == 12 + and bool(handoff_manifest_hash) + ) + handoff_write_blocked = True + nonsecret_machine_readable_handoff = ( + handoff_bound + and bool(handoff_manifest_hash) + and handoff_manifest.get("manual_review_mode") == "exception_only" + ) + side_effect_free = ( + int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_endpoint_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and int(summary.get("retention_archive_written_count") or 0) == 0 + and int(summary.get("ledger_retention_written_count") or 0) == 0 + and int(summary.get("ledger_written_count") or 0) == 0 + and int(summary.get("receipt_persistence_storage_written_count") or 0) == 0 + and int(summary.get("persists_verifier_receipt_count") or 0) == 0 + and int(summary.get("verifier_invoked_count") or 0) == 0 + and int(summary.get("dry_run_executor_invoked_count") or 0) == 0 + and int(summary.get("runner_invocation_performed_count") or 0) == 0 + and int(summary.get("signs_database_apply_authorization_count") or 0) == 0 + and archive_proof.get("retention_archive_written") is False + and archive_proof.get("database_written") is False + and archive_proof.get("database_apply_authorized") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("executes_database_apply") is False + ) + archive_retention_sealed_handoff_proof = { + "archive_retention_sealed_handoff_proof_id": handoff_id, + "authorization_material_type": ( + "controlled_dry_run_archive_retention_sealed_handoff_proof" + ), + "source_no_write_ledger_retention_proof_closeout_id": source_closeout_id, + "source_retention_boundary_no_write_archive_proof_id": source_archive_id, + "source_no_write_ledger_retention_proof_id": source_retention_id, + "source_storage_boundary_no_write_ledger_proof_closeout_id": ( + source_storage_closeout_id + ), + "source_storage_boundary_no_write_ledger_proof_id": source_ledger_id, + "source_receipt_persistence_storage_boundary_proof_closeout_id": ( + source_receipt_storage_closeout_id + ), + "source_receipt_persistence_storage_boundary_proof_id": ( + source_receipt_storage_id + ), + "source_verifier_receipt_persistence_guard_proof_closeout_id": ( + source_guard_closeout_id + ), + "source_verifier_receipt_persistence_guard_proof_id": source_guard_id, + "required_command_shape_hash": archive_proof.get( + "required_command_shape_hash" + ), + "target_file": archive_closeout.get("target_file"), + "expected_sha256": archive_closeout.get("expected_sha256"), + "actual_sha256": archive_closeout.get("actual_sha256"), + "hash_matches": archive_closeout.get("hash_matches"), + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "sealed_handoff_manifest": handoff_manifest, + "sealed_handoff_manifest_hash": handoff_manifest_hash, + "handoff_status": "archive_retention_sealed_handoff_proof_preview_ready", + "handoff_mode": "archive_retention_sealed_handoff_proof_preview_only", + "archive_retention_sealed_handoff_proof_fields": handoff_fields, + "archive_retention_sealed_handoff_proof_field_count": len(handoff_fields), + "sealed_handoff_write_locked": True, + "sealed_handoff_write_allowed": False, + "sealed_handoff_written": False, + "retention_archive_write_locked": True, + "retention_archive_write_allowed": False, + "retention_archive_written": False, + "ledger_retention_write_locked": True, + "ledger_retention_write_allowed": False, + "ledger_retention_written": False, + "ledger_write_allowed": False, + "ledger_written": False, + "receipt_persistence_storage_write_allowed": False, + "receipt_persistence_storage_written": False, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "ready_for_database_apply_now": False, + "ready_for_archive_retention_sealed_handoff_write_now": False, + "ready_for_retention_boundary_archive_now": False, + "ready_for_no_write_ledger_retention_now": False, + "ready_for_storage_boundary_ledger_write_now": False, + "ready_for_receipt_persistence_storage_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + } + checks = [ + _controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_check( + "no_write_ledger_retention_proof_closeout_ready", + archive_closeout_ready, + { + "result": sealed_handoff_result.get("result"), + "ready_count": summary.get( + "controlled_dry_run_no_write_ledger_retention_proof_closeout_ready_count" + ), + }, + "wait_for_no_write_ledger_retention_proof_closeout", + ), + _controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_check( + "retention_boundary_no_write_archive_proof_ready", + archive_proof_ready, + { + "archive_status": archive_proof.get("archive_status"), + "archive_mode": archive_proof.get("archive_mode"), + "field_count": archive_proof.get( + "retention_boundary_no_write_archive_proof_field_count" + ), + }, + "wait_for_retention_boundary_no_write_archive_proof", + ), + _controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_check( + "retention_boundary_no_write_archive_proof_no_write", + archive_no_write, + { + "retention_archive_write_allowed": archive_proof.get( + "retention_archive_write_allowed" + ), + "retention_archive_written": archive_proof.get( + "retention_archive_written" + ), + "database_written": archive_proof.get("database_written"), + "database_apply_authorized": archive_proof.get( + "database_apply_authorized" + ), + }, + "abort_if_archive_retention_storage_persistence_or_database_apply_allowed", + ), + _controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_check( + "archive_retention_sealed_handoff_proof_bound", + handoff_bound, + { + "handoff_id": handoff_id, + "source_archive_id": source_archive_id, + "source_closeout_id": source_closeout_id, + "field_count": len(handoff_fields), + }, + "wait_for_archive_retention_sealed_handoff_proof", + ), + _controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_check( + "archive_retention_sealed_handoff_proof_blocks_handoff_write", + handoff_write_blocked, + { + "handoff_mode": archive_retention_sealed_handoff_proof.get( + "handoff_mode" + ), + "sealed_handoff_write_allowed": False, + "sealed_handoff_written": False, + }, + "abort_if_sealed_handoff_write_allowed_or_written", + ), + _controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_check( + "previous_closeouts_carried_forward", + previous_closeouts_carried_forward, + { + "source_closeout_only": archive_closeout.get( + "no_write_ledger_retention_proof_closeout_only" + ), + "source_archive_only": archive_closeout.get( + "retention_boundary_no_write_archive_proof_only" + ), + "source_previous_closeout_id": previous_retention_closeout.get( + "storage_boundary_no_write_ledger_proof_closeout_id" + ), + }, + "wait_for_previous_closeout_chain", + ), + _controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": archive_closeout.get("target_file"), + "hash_matches": archive_closeout.get("hash_matches"), + "expected_sha256_present": bool( + archive_closeout.get("expected_sha256") + ), + "actual_sha256_present": bool( + archive_closeout.get("actual_sha256") + ), + }, + "wait_for_target_migration_hash_lock", + ), + _controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_check( + "rollback_and_post_apply_verifier_bound", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_check( + "no_write_ledger_retention_proof_closeout_contract_blocks_handoff_archive_retention_storage_persistence_and_database_apply", + contract_blocks_handoff_archive_retention_storage_persistence_and_apply, + { + "permits_future_archive_proof": archive_contract.get( + "permits_future_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof" + ), + "retention_archive_write_allowed": archive_contract.get( + "retention_archive_write_allowed" + ), + "database_apply_authorized": archive_contract.get( + "database_apply_authorized" + ), + }, + "abort_if_source_contract_allows_handoff_archive_retention_storage_persistence_or_database_apply", + ), + _controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_check( + "sealed_handoff_has_nonsecret_machine_readable_manifest", + nonsecret_machine_readable_handoff, + { + "manifest_hash_present": bool(handoff_manifest_hash), + "accepts_plaintext_secret": archive_retention_sealed_handoff_proof.get( + "accepts_plaintext_secret" + ), + "secret_material_included": archive_retention_sealed_handoff_proof.get( + "secret_material_included" + ), + "signature_material_included": archive_retention_sealed_handoff_proof.get( + "signature_material_included" + ), + }, + "abort_if_sealed_handoff_manifest_contains_secret_signature_or_is_not_machine_readable", + ), + _controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_check( + "preview_has_no_side_effects_no_handoff_no_archive_no_retention_no_ledger_no_storage_no_persistence_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "retention_archive_written_count": summary.get( + "retention_archive_written_count", 0 + ), + }, + "abort_on_preview_handoff_archive_retention_ledger_storage_persistence_side_effect_execution_or_signing", + ), + _controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_check( + "manual_review_not_required_for_safe_preview", + archive_contract.get("manual_review_mode") == "exception_only" + and safety.get("manual_review_mode") == "exception_only", + { + "contract_manual_review_mode": archive_contract.get( + "manual_review_mode" + ), + "safety_manual_review_mode": safety.get("manual_review_mode"), + }, + "keep_manual_review_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_RETENTION_BOUNDARY_NO_WRITE_ARCHIVE_PROOF_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_NO_WRITE_LEDGER_RETENTION_PROOF_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof = { + "retention_boundary_no_write_archive_proof_closeout_id": closeout_id, + "archive_retention_sealed_handoff_proof_id": handoff_id, + "source_no_write_ledger_retention_proof_closeout_id": source_closeout_id, + "source_retention_boundary_no_write_archive_proof_id": source_archive_id, + "source_no_write_ledger_retention_proof_id": source_retention_id, + "source_storage_boundary_no_write_ledger_proof_closeout_id": ( + source_storage_closeout_id + ), + "source_storage_boundary_no_write_ledger_proof_id": source_ledger_id, + "source_receipt_persistence_storage_boundary_proof_closeout_id": ( + source_receipt_storage_closeout_id + ), + "source_receipt_persistence_storage_boundary_proof_id": ( + source_receipt_storage_id + ), + "source_verifier_receipt_persistence_guard_proof_closeout_id": ( + source_guard_closeout_id + ), + "source_verifier_receipt_persistence_guard_proof_id": source_guard_id, + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof_closeout": ( + closeout_ready + ), + "retention_boundary_no_write_archive_proof_closeout_ready": closeout_ready, + "no_write_ledger_retention_proof_closeout_ready": archive_closeout_ready, + "retention_boundary_no_write_archive_proof_ready": archive_proof_ready, + "archive_retention_sealed_handoff_proof_bound": closeout_ready, + "sealed_handoff_write_locked": True, + "sealed_handoff_write_allowed": False, + "sealed_handoff_written": False, + "retention_archive_write_locked": True, + "retention_archive_write_allowed": False, + "retention_archive_written": False, + "ledger_retention_write_locked": True, + "ledger_retention_write_allowed": False, + "ledger_retention_written": False, + "ledger_write_allowed": False, + "ledger_written": False, + "receipt_persistence_storage_write_allowed": False, + "receipt_persistence_storage_written": False, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "ready_for_database_apply_now": False, + "ready_for_archive_retention_sealed_handoff_write_now": False, + "ready_for_retention_boundary_archive_now": False, + "ready_for_no_write_ledger_retention_now": False, + "ready_for_storage_boundary_ledger_write_now": False, + "ready_for_receipt_persistence_storage_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + controlled_dry_run_retention_boundary_no_write_archive_proof_closeout = { + "retention_boundary_no_write_archive_proof_closeout_id": closeout_id, + "authorization_material_type": ( + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout" + ), + "source_no_write_ledger_retention_proof_closeout_id": source_closeout_id, + "source_retention_boundary_no_write_archive_proof_id": source_archive_id, + "source_no_write_ledger_retention_proof_id": source_retention_id, + "source_storage_boundary_no_write_ledger_proof_closeout_id": ( + source_storage_closeout_id + ), + "source_storage_boundary_no_write_ledger_proof_id": source_ledger_id, + "source_receipt_persistence_storage_boundary_proof_closeout_id": ( + source_receipt_storage_closeout_id + ), + "source_receipt_persistence_storage_boundary_proof_id": ( + source_receipt_storage_id + ), + "source_verifier_receipt_persistence_guard_proof_closeout_id": ( + source_guard_closeout_id + ), + "source_verifier_receipt_persistence_guard_proof_id": source_guard_id, + "required_command_shape_hash": archive_proof.get( + "required_command_shape_hash" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof": ( + closeout_ready + ), + "retention_boundary_no_write_archive_proof_closeout_fields": ( + closeout_fields + ), + "retention_boundary_no_write_archive_proof_closeout_field_count": len( + closeout_fields + ), + "retention_boundary_no_write_archive_proof_closeout_acceptance_gates": ( + acceptance_gates + ), + "retention_boundary_no_write_archive_proof_closeout_acceptance_gate_count": len( + acceptance_gates + ), + "archive_retention_sealed_handoff_proof": ( + archive_retention_sealed_handoff_proof + ), + "archive_retention_sealed_handoff_proof_count": 1, + "archive_retention_sealed_handoff_proof_field_count": len(handoff_fields), + "retention_boundary_no_write_archive_proof": archive_proof, + "retention_boundary_no_write_archive_proof_count": 1, + "no_write_ledger_retention_proof_closeout": archive_closeout, + "no_write_ledger_retention_proof_closeout_count": 1, + "no_write_ledger_retention_proof": retention_proof, + "no_write_ledger_retention_proof_count": 1, + "storage_boundary_no_write_ledger_proof_closeout": ( + previous_retention_closeout + ), + "storage_boundary_no_write_ledger_proof_closeout_count": 1, + "target_file": archive_closeout.get("target_file"), + "expected_sha256": archive_closeout.get("expected_sha256"), + "actual_sha256": archive_closeout.get("actual_sha256"), + "hash_matches": archive_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "retention_boundary_no_write_archive_proof_closeout_only": True, + "archive_retention_sealed_handoff_proof_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "sealed_handoff_write_locked": True, + "sealed_handoff_write_allowed": False, + "sealed_handoff_written": False, + "retention_archive_write_locked": True, + "retention_archive_write_allowed": False, + "retention_archive_written": False, + "ledger_retention_write_locked": True, + "ledger_retention_write_allowed": False, + "ledger_retention_written": False, + "ledger_write_allowed": False, + "ledger_written": False, + "receipt_persistence_storage_boundary_locked": True, + "receipt_persistence_storage_write_allowed": False, + "receipt_persistence_storage_written": False, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_archive_retention_sealed_handoff_write_now": False, + "ready_for_retention_boundary_archive_now": False, + "ready_for_no_write_ledger_retention_now": False, + "ready_for_storage_boundary_ledger_write_now": False, + "ready_for_receipt_persistence_storage_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + } + controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_contract = { + "mode": "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_and_archive_retention_sealed_handoff_proof_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-retention-boundary-no-write-archive-proof-closeout" + ), + "source_no_write_ledger_retention_proof_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-no-write-ledger-retention-proof-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof": ( + closeout_ready + ), + "sealed_handoff_write_locked": True, + "sealed_handoff_write_allowed": False, + "sealed_handoff_written": False, + "retention_archive_write_locked": True, + "retention_archive_write_allowed": False, + "retention_archive_written": False, + "ledger_retention_write_locked": True, + "ledger_retention_write_allowed": False, + "ledger_retention_written": False, + "ledger_write_allowed": False, + "ledger_written": False, + "receipt_persistence_storage_write_allowed": False, + "receipt_persistence_storage_written": False, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_invocation_locked": True, + "verifier_invoked": False, + "verifier_receipt_present": False, + "ready_for_database_apply_now": False, + "ready_for_archive_retention_sealed_handoff_write_now": False, + "ready_for_retention_boundary_archive_now": False, + "ready_for_no_write_ledger_retention_now": False, + "ready_for_storage_boundary_ledger_write_now": False, + "ready_for_receipt_persistence_storage_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": False, + "signs_database_apply_authorization": False, + "writes_database": False, + "executes_in_preview": False, + "secret_material_required_in_preview": False, + "manual_review_mode": "exception_only", + } + output_summary = dict(summary) + output_summary.update( + { + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_check_count": len( + checks + ), + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_pass_count": ( + passed_count + ), + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_count": 1, + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_field_count": len( + closeout_fields + ), + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_acceptance_gate_count": len( + acceptance_gates + ), + "archive_retention_sealed_handoff_proof_count": 1, + "archive_retention_sealed_handoff_proof_field_count": len( + handoff_fields + ), + "sealed_handoff_write_locked_count": 1, + "sealed_handoff_write_allowed_count": 0, + "sealed_handoff_written_count": 0, + "retention_archive_write_allowed_count": 0, + "retention_archive_written_count": 0, + "ledger_retention_write_allowed_count": 0, + "ledger_retention_written_count": 0, + "storage_boundary_write_allowed_count": 0, + "storage_boundary_written_count": 0, + "ledger_write_allowed_count": 0, + "ledger_written_count": 0, + "receipt_persistence_storage_write_allowed_count": 0, + "receipt_persistence_storage_written_count": 0, + "verifier_receipt_persistence_allowed_count": 0, + "verifier_receipt_persisted_count": 0, + "persists_verifier_receipt_count": 0, + "verifier_invoked_count": 0, + "verifier_receipt_present_count": 0, + "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, + "dry_run_executor_invoked_count": 0, + "runner_invocation_performed_count": 0, + "endpoint_executed_count": 0, + "sql_executed_count": 0, + "database_written_count": 0, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + } + ) + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_RETENTION_BOUNDARY_NO_WRITE_ARCHIVE_PROOF_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(sealed_handoff_result.get("success")), + "generated_at": sealed_handoff_result.get("generated_at"), + "source_policy": sealed_handoff_result.get("policy"), + "stats": sealed_handoff_result.get("stats") or {}, + "summary": output_summary, + "future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof": ( + future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof + ), + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout": ( + controlled_dry_run_retention_boundary_no_write_archive_proof_closeout + ), + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_contract": ( + controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_contract + ), + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_checks": ( + checks + ), + "source_controlled_dry_run_no_write_ledger_retention_proof_closeout_summary": ( + summary + ), + "source_controlled_dry_run_no_write_ledger_retention_proof_closeout_contract": ( + archive_contract + ), + "source_controlled_dry_run_no_write_ledger_retention_proof_closeout": ( + archive_closeout + ), + "source_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof": ( + future_archive + ), + "safety": { + "read_only_db_apply_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future archive retention sealed handoff proof closeout.", + "Keep sealed handoff writes disabled until a later handoff closeout proves the handoff path.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, archive writes, handoff writes, ledger writes, ledger retention writes, verifier invocation, verifier receipt persistence, receipt storage, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_controlled_dry_run_archive_retention_sealed_handoff_proof_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out the sealed handoff proof into a verifier transfer proof.""" + transfer_result = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + ) + future_handoff = ( + transfer_result.get( + "future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof" + ) + or {} + ) + handoff_closeout = ( + transfer_result.get( + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout" + ) + or {} + ) + handoff_contract = ( + transfer_result.get( + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_contract" + ) + or {} + ) + summary = transfer_result.get("summary") or {} + safety = transfer_result.get("safety") or {} + handoff_proof = ( + handoff_closeout.get("archive_retention_sealed_handoff_proof") or {} + ) + archive_proof = ( + handoff_closeout.get("retention_boundary_no_write_archive_proof") or {} + ) + source_archive_closeout = ( + handoff_closeout.get("no_write_ledger_retention_proof_closeout") or {} + ) + rollback_binding = handoff_closeout.get("rollback_binding") or {} + verifier_binding = handoff_closeout.get("post_apply_verifier_binding") or {} + source_closeout_id = handoff_closeout.get( + "retention_boundary_no_write_archive_proof_closeout_id" + ) + source_handoff_id = handoff_proof.get("archive_retention_sealed_handoff_proof_id") + source_archive_id = handoff_proof.get( + "source_retention_boundary_no_write_archive_proof_id" + ) + source_retention_closeout_id = handoff_proof.get( + "source_no_write_ledger_retention_proof_closeout_id" + ) + closeout_id = ( + _db_apply_controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_id( + transfer_result + ) + ) + transfer_id = f"{closeout_id}-sealed-handoff-verifier-transfer-proof" + closeout_fields = [ + "archive_retention_sealed_handoff_proof_closeout_id", + "source_retention_boundary_no_write_archive_proof_closeout_id", + "source_archive_retention_sealed_handoff_proof_id", + "source_retention_boundary_no_write_archive_proof_id", + "source_no_write_ledger_retention_proof_closeout_id", + "sealed_handoff_verifier_transfer_proof_id", + "sealed_handoff_manifest_hash", + "required_command_shape_hash", + "target_migration_file", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "verifier_transfer_manifest_hash", + ] + transfer_fields = [ + "sealed_handoff_verifier_transfer_proof_id", + "source_archive_retention_sealed_handoff_proof_closeout_id", + "source_archive_retention_sealed_handoff_proof_id", + "source_retention_boundary_no_write_archive_proof_closeout_id", + "sealed_handoff_manifest_hash", + "verifier_transfer_manifest_hash", + "target_migration_file", + "expected_sha256", + "actual_sha256", + "rollback_binding_id", + "post_apply_verifier_binding_id", + "verifier_transfer_status", + ] + acceptance_gates = [ + "retention_boundary_no_write_archive_proof_closeout_ready", + "archive_retention_sealed_handoff_proof_ready", + "sealed_handoff_manifest_hash_locked", + "sealed_handoff_verifier_transfer_proof_bound", + "sealed_handoff_verifier_transfer_blocks_verifier_invocation", + "previous_closeouts_carried_forward", + "target_migration_hash_locked", + "rollback_and_post_apply_verifier_bound", + "archive_retention_sealed_handoff_contract_blocks_handoff_verifier_execution_and_database_apply", + "preview_has_no_side_effects_no_handoff_no_verifier_no_receipt_no_execution_no_signing", + ] + abort_conditions = [ + "abort_if_archive_retention_sealed_handoff_proof_not_ready", + "abort_if_sealed_handoff_proof_missing", + "abort_if_sealed_handoff_manifest_hash_missing_or_mismatched", + "abort_if_verifier_transfer_allows_invocation_or_receipt_persistence", + "abort_if_previous_closeout_ids_do_not_match", + "abort_if_target_migration_hash_is_not_locked", + "abort_if_rollback_or_post_apply_verifier_binding_missing", + "abort_if_any_secret_or_signature_material_is_included", + "abort_if_any_endpoint_sql_database_runner_verifier_or_executor_action_is_allowed", + "abort_if_manual_review_mode_is_not_exception_only", + ] + previous_ready = ( + transfer_result.get("result") + == "DB_APPLY_CONTROLLED_DRY_RUN_RETENTION_BOUNDARY_NO_WRITE_ARCHIVE_PROOF_CLOSEOUT_READY" + and future_handoff.get( + "ready_for_future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof" + ) + is True + ) + handoff_ready = ( + handoff_proof.get("handoff_status") + == "archive_retention_sealed_handoff_proof_preview_ready" + and handoff_proof.get("handoff_mode") + == "archive_retention_sealed_handoff_proof_preview_only" + and handoff_proof.get("archive_retention_sealed_handoff_proof_field_count") + == 12 + ) + handoff_manifest = handoff_proof.get("sealed_handoff_manifest") or {} + expected_handoff_manifest_hash = hashlib.sha256( + json.dumps(handoff_manifest, sort_keys=True).encode("utf-8") + ).hexdigest() + sealed_handoff_manifest_hash_locked = ( + bool(handoff_manifest) + and len(handoff_proof.get("sealed_handoff_manifest_hash") or "") == 64 + and handoff_proof.get("sealed_handoff_manifest_hash") + == expected_handoff_manifest_hash + ) + target_hash_locked = ( + bool(handoff_closeout.get("target_file")) + and bool(handoff_closeout.get("expected_sha256")) + and bool(handoff_closeout.get("actual_sha256")) + and handoff_closeout.get("expected_sha256") + == handoff_closeout.get("actual_sha256") + and handoff_closeout.get("hash_matches") is True + ) + rollback_and_verifier_bound = bool( + rollback_binding.get("rollback_binding_id") + ) and bool(verifier_binding.get("post_apply_verifier_binding_id")) + previous_closeouts_carried_forward = ( + handoff_closeout.get("retention_boundary_no_write_archive_proof_closeout_only") + is True + and handoff_closeout.get("archive_retention_sealed_handoff_proof_only") + is True + and handoff_proof.get("source_no_write_ledger_retention_proof_closeout_id") + == source_retention_closeout_id + and handoff_proof.get("source_retention_boundary_no_write_archive_proof_id") + == source_archive_id + and archive_proof.get("archive_proof_id") == source_archive_id + ) + source_contract_blocks_handoff_verifier_execution_and_apply = ( + handoff_contract.get( + "permits_future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof" + ) + is True + and handoff_contract.get("sealed_handoff_write_allowed") is False + and handoff_contract.get("persists_verifier_receipt") is False + and handoff_contract.get("verifier_invoked") is False + and handoff_contract.get("executes_database_apply") is False + and handoff_contract.get("database_apply_authorized") is False + and handoff_contract.get("writes_database") is False + ) + transfer_manifest = { + "source_retention_boundary_no_write_archive_proof_closeout_id": ( + source_closeout_id + ), + "source_archive_retention_sealed_handoff_proof_id": source_handoff_id, + "source_retention_boundary_no_write_archive_proof_id": source_archive_id, + "sealed_handoff_manifest_hash": handoff_proof.get( + "sealed_handoff_manifest_hash" + ), + "target_file": handoff_closeout.get("target_file"), + "expected_sha256": handoff_closeout.get("expected_sha256"), + "actual_sha256": handoff_closeout.get("actual_sha256"), + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "verifier_transfer_mode": "sealed_handoff_verifier_transfer_proof_preview_only", + "manual_review_mode": "exception_only", + } + verifier_transfer_manifest_hash = hashlib.sha256( + json.dumps(transfer_manifest, sort_keys=True).encode("utf-8") + ).hexdigest() + transfer_bound = ( + previous_ready + and handoff_ready + and sealed_handoff_manifest_hash_locked + and bool(source_closeout_id) + and bool(source_handoff_id) + and bool(source_archive_id) + and len(transfer_fields) == 12 + and bool(verifier_transfer_manifest_hash) + ) + verifier_transfer_blocks_invocation = True + nonsecret_machine_readable_transfer = ( + transfer_bound + and bool(verifier_transfer_manifest_hash) + and transfer_manifest.get("manual_review_mode") == "exception_only" + ) + side_effect_free = ( + int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_endpoint_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and int(summary.get("sealed_handoff_written_count") or 0) == 0 + and int(summary.get("retention_archive_written_count") or 0) == 0 + and int(summary.get("ledger_retention_written_count") or 0) == 0 + and int(summary.get("ledger_written_count") or 0) == 0 + and int(summary.get("receipt_persistence_storage_written_count") or 0) == 0 + and int(summary.get("persists_verifier_receipt_count") or 0) == 0 + and int(summary.get("verifier_invoked_count") or 0) == 0 + and int(summary.get("dry_run_executor_invoked_count") or 0) == 0 + and int(summary.get("runner_invocation_performed_count") or 0) == 0 + and int(summary.get("signs_database_apply_authorization_count") or 0) == 0 + and handoff_proof.get("sealed_handoff_written") is False + and handoff_proof.get("verifier_invoked") is False + and handoff_proof.get("database_written") is False + and handoff_proof.get("database_apply_authorized") is False + and safety.get("executes_endpoint") is False + and safety.get("executes_sql") is False + and safety.get("writes_database") is False + and safety.get("executes_database_apply") is False + ) + sealed_handoff_verifier_transfer_proof = { + "sealed_handoff_verifier_transfer_proof_id": transfer_id, + "authorization_material_type": ( + "controlled_dry_run_sealed_handoff_verifier_transfer_proof" + ), + "source_archive_retention_sealed_handoff_proof_closeout_id": closeout_id, + "source_retention_boundary_no_write_archive_proof_closeout_id": ( + source_closeout_id + ), + "source_archive_retention_sealed_handoff_proof_id": source_handoff_id, + "source_retention_boundary_no_write_archive_proof_id": source_archive_id, + "source_no_write_ledger_retention_proof_closeout_id": ( + source_retention_closeout_id + ), + "required_command_shape_hash": handoff_proof.get( + "required_command_shape_hash" + ), + "target_file": handoff_closeout.get("target_file"), + "expected_sha256": handoff_closeout.get("expected_sha256"), + "actual_sha256": handoff_closeout.get("actual_sha256"), + "hash_matches": handoff_closeout.get("hash_matches"), + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + "sealed_handoff_manifest_hash": handoff_proof.get( + "sealed_handoff_manifest_hash" + ), + "verifier_transfer_manifest": transfer_manifest, + "verifier_transfer_manifest_hash": verifier_transfer_manifest_hash, + "verifier_transfer_status": ( + "sealed_handoff_verifier_transfer_proof_preview_ready" + ), + "verifier_transfer_mode": ( + "sealed_handoff_verifier_transfer_proof_preview_only" + ), + "sealed_handoff_verifier_transfer_proof_fields": transfer_fields, + "sealed_handoff_verifier_transfer_proof_field_count": len( + transfer_fields + ), + "verifier_transfer_write_locked": True, + "verifier_transfer_write_allowed": False, + "verifier_transfer_written": False, + "sealed_handoff_write_locked": True, + "sealed_handoff_write_allowed": False, + "sealed_handoff_written": False, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "ready_for_database_apply_now": False, + "ready_for_archive_retention_sealed_handoff_write_now": False, + "ready_for_verifier_transfer_write_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + } + checks = [ + _controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_check( + "retention_boundary_no_write_archive_proof_closeout_ready", + previous_ready, + { + "result": transfer_result.get("result"), + "ready_count": summary.get( + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_ready_count" + ), + }, + "wait_for_retention_boundary_no_write_archive_proof_closeout", + ), + _controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_check( + "archive_retention_sealed_handoff_proof_ready", + handoff_ready, + { + "handoff_status": handoff_proof.get("handoff_status"), + "handoff_mode": handoff_proof.get("handoff_mode"), + "field_count": handoff_proof.get( + "archive_retention_sealed_handoff_proof_field_count" + ), + }, + "wait_for_archive_retention_sealed_handoff_proof", + ), + _controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_check( + "sealed_handoff_manifest_hash_locked", + sealed_handoff_manifest_hash_locked, + { + "manifest_hash_present": bool( + handoff_proof.get("sealed_handoff_manifest_hash") + ), + "expected_hash_matches": sealed_handoff_manifest_hash_locked, + }, + "wait_for_sealed_handoff_manifest_hash_lock", + ), + _controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_check( + "sealed_handoff_verifier_transfer_proof_bound", + transfer_bound, + { + "transfer_id": transfer_id, + "source_handoff_id": source_handoff_id, + "source_closeout_id": source_closeout_id, + "field_count": len(transfer_fields), + }, + "wait_for_sealed_handoff_verifier_transfer_proof", + ), + _controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_check( + "sealed_handoff_verifier_transfer_blocks_verifier_invocation", + verifier_transfer_blocks_invocation, + { + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "persists_verifier_receipt": False, + }, + "abort_if_verifier_transfer_allows_invocation_or_receipt_persistence", + ), + _controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_check( + "previous_closeouts_carried_forward", + previous_closeouts_carried_forward, + { + "source_handoff_only": handoff_closeout.get( + "archive_retention_sealed_handoff_proof_only" + ), + "source_closeout_only": handoff_closeout.get( + "retention_boundary_no_write_archive_proof_closeout_only" + ), + "source_archive_closeout_id": source_archive_closeout.get( + "no_write_ledger_retention_proof_closeout_id" + ), + }, + "wait_for_previous_closeout_chain", + ), + _controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_check( + "target_migration_hash_locked", + target_hash_locked, + { + "target_file": handoff_closeout.get("target_file"), + "hash_matches": handoff_closeout.get("hash_matches"), + "expected_sha256_present": bool( + handoff_closeout.get("expected_sha256") + ), + "actual_sha256_present": bool(handoff_closeout.get("actual_sha256")), + }, + "wait_for_target_migration_hash_lock", + ), + _controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_check( + "rollback_and_post_apply_verifier_bound", + rollback_and_verifier_bound, + { + "rollback_binding_id": rollback_binding.get("rollback_binding_id"), + "post_apply_verifier_binding_id": verifier_binding.get( + "post_apply_verifier_binding_id" + ), + }, + "wait_for_rollback_and_post_apply_verifier_bindings", + ), + _controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_check( + "archive_retention_sealed_handoff_contract_blocks_handoff_verifier_execution_and_database_apply", + source_contract_blocks_handoff_verifier_execution_and_apply, + { + "permits_future_handoff_proof": handoff_contract.get( + "permits_future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof" + ), + "sealed_handoff_write_allowed": handoff_contract.get( + "sealed_handoff_write_allowed" + ), + "verifier_invoked": handoff_contract.get("verifier_invoked"), + "database_apply_authorized": handoff_contract.get( + "database_apply_authorized" + ), + }, + "abort_if_source_contract_allows_handoff_verifier_execution_or_database_apply", + ), + _controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_check( + "sealed_handoff_verifier_transfer_has_nonsecret_machine_readable_manifest", + nonsecret_machine_readable_transfer, + { + "manifest_hash_present": bool(verifier_transfer_manifest_hash), + "accepts_plaintext_secret": sealed_handoff_verifier_transfer_proof.get( + "accepts_plaintext_secret" + ), + "secret_material_included": sealed_handoff_verifier_transfer_proof.get( + "secret_material_included" + ), + "signature_material_included": sealed_handoff_verifier_transfer_proof.get( + "signature_material_included" + ), + }, + "abort_if_verifier_transfer_manifest_contains_secret_signature_or_is_not_machine_readable", + ), + _controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_check( + "preview_has_no_side_effects_no_handoff_no_verifier_no_receipt_no_execution_no_signing", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_endpoint_count": summary.get("executes_endpoint_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + "verifier_invoked_count": summary.get("verifier_invoked_count", 0), + }, + "abort_on_preview_handoff_verifier_receipt_execution_or_signing_side_effect", + ), + _controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_check( + "manual_review_not_required_for_safe_preview", + handoff_contract.get("manual_review_mode") == "exception_only" + and safety.get("manual_review_mode") == "exception_only", + { + "contract_manual_review_mode": handoff_contract.get( + "manual_review_mode" + ), + "safety_manual_review_mode": safety.get("manual_review_mode"), + }, + "keep_manual_review_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_CONTROLLED_DRY_RUN_ARCHIVE_RETENTION_SEALED_HANDOFF_PROOF_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_RETENTION_BOUNDARY_NO_WRITE_ARCHIVE_PROOF_CLOSEOUT" + ) + future_database_apply_controlled_dry_run_sealed_handoff_verifier_transfer_proof = { + "archive_retention_sealed_handoff_proof_closeout_id": closeout_id, + "sealed_handoff_verifier_transfer_proof_id": transfer_id, + "source_retention_boundary_no_write_archive_proof_closeout_id": ( + source_closeout_id + ), + "source_archive_retention_sealed_handoff_proof_id": source_handoff_id, + "source_retention_boundary_no_write_archive_proof_id": source_archive_id, + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_sealed_handoff_verifier_transfer_proof": ( + closeout_ready + ), + "can_enter_future_database_apply_controlled_dry_run_sealed_handoff_verifier_transfer_proof_closeout": ( + closeout_ready + ), + "archive_retention_sealed_handoff_proof_closeout_ready": closeout_ready, + "retention_boundary_no_write_archive_proof_closeout_ready": previous_ready, + "archive_retention_sealed_handoff_proof_ready": handoff_ready, + "sealed_handoff_manifest_hash_locked": sealed_handoff_manifest_hash_locked, + "sealed_handoff_verifier_transfer_proof_bound": closeout_ready, + "verifier_transfer_write_locked": True, + "verifier_transfer_write_allowed": False, + "verifier_transfer_written": False, + "sealed_handoff_write_locked": True, + "sealed_handoff_write_allowed": False, + "sealed_handoff_written": False, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "ready_for_database_apply_now": False, + "ready_for_archive_retention_sealed_handoff_write_now": False, + "ready_for_verifier_transfer_write_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "writes_database": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + } + archive_retention_sealed_handoff_proof_closeout = { + "archive_retention_sealed_handoff_proof_closeout_id": closeout_id, + "authorization_material_type": ( + "controlled_dry_run_archive_retention_sealed_handoff_proof_closeout" + ), + "source_retention_boundary_no_write_archive_proof_closeout_id": ( + source_closeout_id + ), + "source_archive_retention_sealed_handoff_proof_id": source_handoff_id, + "source_retention_boundary_no_write_archive_proof_id": source_archive_id, + "source_no_write_ledger_retention_proof_closeout_id": ( + source_retention_closeout_id + ), + "required_command_shape_hash": handoff_proof.get( + "required_command_shape_hash" + ), + "status": closeout_status, + "ready_for_future_database_apply_controlled_dry_run_sealed_handoff_verifier_transfer_proof": ( + closeout_ready + ), + "archive_retention_sealed_handoff_proof_closeout_fields": ( + closeout_fields + ), + "archive_retention_sealed_handoff_proof_closeout_field_count": len( + closeout_fields + ), + "archive_retention_sealed_handoff_proof_closeout_acceptance_gates": ( + acceptance_gates + ), + "archive_retention_sealed_handoff_proof_closeout_acceptance_gate_count": len( + acceptance_gates + ), + "sealed_handoff_verifier_transfer_proof": ( + sealed_handoff_verifier_transfer_proof + ), + "sealed_handoff_verifier_transfer_proof_count": 1, + "sealed_handoff_verifier_transfer_proof_field_count": len( + transfer_fields + ), + "archive_retention_sealed_handoff_proof": handoff_proof, + "archive_retention_sealed_handoff_proof_count": 1, + "retention_boundary_no_write_archive_proof_closeout": handoff_closeout, + "retention_boundary_no_write_archive_proof_closeout_count": 1, + "target_file": handoff_closeout.get("target_file"), + "expected_sha256": handoff_closeout.get("expected_sha256"), + "actual_sha256": handoff_closeout.get("actual_sha256"), + "hash_matches": handoff_closeout.get("hash_matches"), + "target_migration_hash_locked": target_hash_locked, + "rollback_binding": rollback_binding, + "rollback_binding_count": 1, + "post_apply_verifier_binding": verifier_binding, + "post_apply_verifier_binding_count": 1, + "abort_conditions": abort_conditions, + "abort_condition_count": len(abort_conditions), + "dry_run_only": True, + "check_mode_only": True, + "archive_retention_sealed_handoff_proof_closeout_only": True, + "sealed_handoff_verifier_transfer_proof_only": True, + "requires_fresh_production_truth_in_same_run": True, + "requires_post_apply_verifier": True, + "verifier_transfer_write_locked": True, + "verifier_transfer_write_allowed": False, + "verifier_transfer_written": False, + "sealed_handoff_write_locked": True, + "sealed_handoff_write_allowed": False, + "sealed_handoff_written": False, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "dry_run_executor_invoked": False, + "runner_invocation_performed": False, + "endpoint_executed": False, + "sql_executed": False, + "database_written": False, + "execution_receipt_present": False, + "runner_execution_authorized": False, + "dry_run_execution_authorized": False, + "execution_authorized": False, + "endpoint_execution_allowed": False, + "sql_execution_allowed": False, + "database_write_allowed": False, + "ready_for_database_apply_now": False, + "ready_for_archive_retention_sealed_handoff_write_now": False, + "ready_for_verifier_transfer_write_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "database_apply_authorized": False, + "issues_database_apply_authorization": False, + "signs_database_apply_authorization": False, + "accepts_plaintext_secret": False, + "reads_secret_in_preview": False, + "signature_material_included": False, + "secret_material_included": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "captures_stdout": False, + "captures_stderr": False, + "stdout_included": False, + "stderr_included": False, + } + archive_retention_sealed_handoff_proof_closeout_contract = { + "mode": "controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_and_sealed_handoff_verifier_transfer_proof_only", + "source_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-archive-retention-sealed-handoff-proof-closeout" + ), + "source_retention_boundary_no_write_archive_proof_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/" + "auto-policy-db-apply-controlled-dry-run-retention-boundary-no-write-archive-proof-closeout" + ), + "machine_verifiable": True, + "permits_future_database_apply_controlled_dry_run_sealed_handoff_verifier_transfer_proof": ( + closeout_ready + ), + "verifier_transfer_write_locked": True, + "verifier_transfer_write_allowed": False, + "verifier_transfer_written": False, + "sealed_handoff_write_locked": True, + "sealed_handoff_write_allowed": False, + "sealed_handoff_written": False, + "verifier_receipt_persistence_locked": True, + "verifier_receipt_persistence_allowed": False, + "verifier_receipt_persisted": False, + "persists_verifier_receipt": False, + "verifier_invocation_locked": True, + "verifier_invocation_allowed": False, + "verifier_invoked": False, + "verifier_receipt_present": False, + "ready_for_database_apply_now": False, + "ready_for_archive_retention_sealed_handoff_write_now": False, + "ready_for_verifier_transfer_write_now": False, + "ready_for_verifier_receipt_persistence_now": False, + "ready_for_verifier_invocation_now": False, + "ready_for_dry_run_executor_invocation_now": False, + "ready_for_actual_dry_run_execution_now": False, + "accepts_plaintext_secret": False, + "performs_detached_signature_verification": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "executes_endpoint": False, + "executes_sql": False, + "issues_database_apply_authorization": False, + "database_apply_authorized": False, + "signs_database_apply_authorization": False, + "writes_database": False, + "executes_in_preview": False, + "secret_material_required_in_preview": False, + "manual_review_mode": "exception_only", + } + output_summary = dict(summary) + output_summary.update( + { + "controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_ready_count": ( + 1 if closeout_ready else 0 + ), + "controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_check_count": len( + checks + ), + "controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_pass_count": ( + passed_count + ), + "controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_waiting_count": len( + waiting_checks + ), + "controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_count": 1, + "controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_field_count": len( + closeout_fields + ), + "controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_acceptance_gate_count": len( + acceptance_gates + ), + "sealed_handoff_verifier_transfer_proof_count": 1, + "sealed_handoff_verifier_transfer_proof_field_count": len( + transfer_fields + ), + "sealed_handoff_manifest_hash_locked_count": ( + 1 if sealed_handoff_manifest_hash_locked else 0 + ), + "verifier_transfer_write_locked_count": 1, + "verifier_transfer_write_allowed_count": 0, + "verifier_transfer_written_count": 0, + "sealed_handoff_write_allowed_count": 0, + "sealed_handoff_written_count": 0, + "persists_verifier_receipt_count": 0, + "verifier_invoked_count": 0, + "verifier_receipt_present_count": 0, + "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, + "dry_run_executor_invoked_count": 0, + "runner_invocation_performed_count": 0, + "endpoint_executed_count": 0, + "sql_executed_count": 0, + "database_written_count": 0, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: ( + summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0) or 0 + ), + } + ) + return { + "policy": AUTO_POLICY_DB_APPLY_CONTROLLED_DRY_RUN_ARCHIVE_RETENTION_SEALED_HANDOFF_PROOF_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(transfer_result.get("success")), + "generated_at": transfer_result.get("generated_at"), + "source_policy": transfer_result.get("policy"), + "stats": transfer_result.get("stats") or {}, + "summary": output_summary, + "future_database_apply_controlled_dry_run_sealed_handoff_verifier_transfer_proof": ( + future_database_apply_controlled_dry_run_sealed_handoff_verifier_transfer_proof + ), + "controlled_dry_run_archive_retention_sealed_handoff_proof_closeout": ( + archive_retention_sealed_handoff_proof_closeout + ), + "controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_contract": ( + archive_retention_sealed_handoff_proof_closeout_contract + ), + "controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_checks": ( + checks + ), + "source_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_summary": ( + summary + ), + "source_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_contract": ( + handoff_contract + ), + "source_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout": ( + handoff_closeout + ), + "source_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof": ( + future_handoff + ), + "safety": { + "read_only_db_apply_controlled_dry_run_archive_retention_sealed_handoff_proof_closeout": True, + "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_verifier_receipt": False, + "executes_authorization_evidence": False, + "executes_database_apply": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout to build a future sealed handoff verifier transfer proof closeout.", + "Keep verifier invocation and verifier receipt persistence disabled until a later verifier transfer closeout proves the transfer boundary.", + "This closeout still does not authorize endpoint execution, SQL, DB writes, handoff writes, verifier invocation, verifier receipt persistence, receipt storage, or database apply.", + ], + } + + +def build_pchome_auto_policy_db_apply_authorization_lane_guard( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Guard the future DB apply authorization lane without issuing authorization.""" + closeout = build_pchome_auto_policy_db_apply_authorization_request_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + package = closeout.get("final_exact_request_package") or {} + manifest = closeout.get("machine_request_manifest") or {} + summary = closeout.get("summary") or {} + safety = closeout.get("safety") or {} + template = package.get("exact_request_payload_template") or {} + manifest_steps = manifest.get("manifest_steps") or [] + side_effect_free = ( + int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and package.get("reads_secret_in_preview") is False + and package.get("executes_shell_in_preview") is False + and package.get("executes_sql_in_preview") is False + and package.get("writes_database_in_preview") is False + and safety.get("writes_database") is False + ) + template_has_truth_gate = ( + isinstance(template.get("fresh_production_truth"), dict) + and template["fresh_production_truth"].get("same_run_only") is True + and template["fresh_production_truth"].get("required") is True + ) + manifest_step_names = {step.get("name") for step in manifest_steps} + checks = [ + _authorization_lane_guard_check( + "request_closeout_ready", + closeout.get("result") == "DB_APPLY_AUTHORIZATION_REQUEST_CLOSEOUT_READY" + and package.get("ready_for_exact_authorization_request_package") is True, + { + "result": closeout.get("result"), + "ready_for_exact_authorization_request_package": package.get( + "ready_for_exact_authorization_request_package" + ), + }, + "wait_for_authorization_request_closeout", + ), + _authorization_lane_guard_check( + "package_does_not_issue_apply_authorization", + package.get("issues_database_apply_authorization") is False + and package.get("ready_for_database_apply_now") is False, + { + "issues_database_apply_authorization": package.get( + "issues_database_apply_authorization" + ), + "ready_for_database_apply_now": package.get("ready_for_database_apply_now"), + }, + "block_if_package_issues_apply_authorization", + ), + _authorization_lane_guard_check( + "manifest_does_not_issue_apply_authorization", + manifest.get("issues_database_apply_authorization") is False + and manifest.get("writes_database") is False + and manifest.get("executes_in_preview") is False, + { + "issues_database_apply_authorization": manifest.get( + "issues_database_apply_authorization" + ), + "writes_database": manifest.get("writes_database"), + "executes_in_preview": manifest.get("executes_in_preview"), + }, + "block_if_manifest_executes_or_authorizes", + ), + _authorization_lane_guard_check( + "fresh_production_truth_same_run_required", + template_has_truth_gate, + {"fresh_production_truth": template.get("fresh_production_truth")}, + "require_same_run_production_truth", + ), + _authorization_lane_guard_check( + "exact_request_payload_complete", + int(summary.get("exact_request_payload_field_count") or 0) == 10 + and package.get("payload_template_field_count") == 10, + { + "exact_request_payload_field_count": summary.get( + "exact_request_payload_field_count", 0 + ), + "payload_template_field_count": package.get("payload_template_field_count"), + }, + "wait_for_exact_request_payload", + ), + _authorization_lane_guard_check( + "machine_manifest_complete", + int(summary.get("machine_request_manifest_step_count") or 0) == 6 + and manifest.get("manifest_step_count") == 6, + { + "machine_request_manifest_step_count": summary.get( + "machine_request_manifest_step_count", 0 + ), + "manifest_step_count": manifest.get("manifest_step_count"), + }, + "wait_for_machine_request_manifest", + ), + _authorization_lane_guard_check( + "secret_rejection_step_present", + "reject_secret_material" in manifest_step_names + and template.get("operator_acknowledges_secret_boundary") is True, + { + "manifest_step_names": sorted(name for name in manifest_step_names if name), + "operator_acknowledges_secret_boundary": template.get( + "operator_acknowledges_secret_boundary" + ), + }, + "block_until_secret_rejection_step_exists", + ), + _authorization_lane_guard_check( + "rollback_acknowledgement_present", + template.get("operator_acknowledges_rollback_boundary") is True, + { + "operator_acknowledges_rollback_boundary": template.get( + "operator_acknowledges_rollback_boundary" + ), + }, + "block_until_rollback_boundary_acknowledged", + ), + _authorization_lane_guard_check( + "migration_hash_locked", + bool(package.get("target_file")) + and bool(package.get("expected_sha256")) + and package.get("hash_matches") is True, + { + "target_file": package.get("target_file"), + "hash_matches": package.get("hash_matches"), + }, + "abort_on_migration_hash_gap", + ), + _authorization_lane_guard_check( + "preview_has_no_side_effects", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + }, + "abort_on_preview_side_effect", + ), + _authorization_lane_guard_check( + "source_intake_and_closeout_ids_present", + bool(package.get("source_intake_id")) + and bool(package.get("source_closeout_boundary_id")) + and bool(package.get("source_dry_run_shell_preview_id")), + { + "source_intake_id": package.get("source_intake_id"), + "source_closeout_boundary_id": package.get("source_closeout_boundary_id"), + "source_dry_run_shell_preview_id": package.get("source_dry_run_shell_preview_id"), + }, + "wait_for_source_proof_ids", + ), + _authorization_lane_guard_check( + "manual_review_regression_absent", + int(summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY) or 0) == 0, + {LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0)}, + "route_failed_verifier_to_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + lane_ready = not waiting_checks + lane_status = ( + "DB_APPLY_AUTHORIZATION_LANE_GUARD_READY" + if lane_ready + else "WAITING_FOR_DB_APPLY_AUTHORIZATION_REQUEST_CLOSEOUT" + ) + lane_entry_requirements = [ + { + "key": "production_truth_refreshed_in_same_run", + "required": True, + "source_command": "python scripts/ops/check_production_version_truth.py", + }, + { + "key": "exact_request_payload_matches_template", + "required": True, + "field_count": len(template), + }, + { + "key": "migration_file_hash_matches_package", + "required": True, + "target_file": package.get("target_file"), + "expected_sha256": package.get("expected_sha256"), + }, + { + "key": "secret_material_absent_from_request", + "required": True, + "rejects_database_url": True, + "rejects_authorization_header": True, + "rejects_cookie": True, + }, + { + "key": "rollback_boundary_acknowledged", + "required": True, + }, + { + "key": "direct_apply_rejected_until_next_lane", + "required": True, + "issues_database_apply_authorization": False, + }, + ] + lane_transfer_contract = { + "mode": "future_authorization_lane_entry_guard_only", + "source_endpoint": "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-lane-guard", + "source_request_closeout_endpoint": ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-request-closeout" + ), + "entry_requirement_count": len(lane_entry_requirements), + "machine_verifiable": True, + "issues_database_apply_authorization": False, + "ready_for_database_apply_now": False, + "executes_in_preview": False, + "writes_database": False, + } + future_authorization_lane_guard = { + "guard_id": _db_apply_authorization_lane_guard_id(closeout), + "source_closeout_package_id": package.get("package_id"), + "source_intake_id": package.get("source_intake_id"), + "source_closeout_boundary_id": package.get("source_closeout_boundary_id"), + "source_dry_run_shell_preview_id": package.get("source_dry_run_shell_preview_id"), + "status": lane_status, + "ready_for_future_authorization_lane_entry": lane_ready, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "request_scope": "future_explicit_db_apply_authorization_only", + "target_file": package.get("target_file"), + "expected_sha256": package.get("expected_sha256"), + "actual_sha256": package.get("actual_sha256"), + "hash_matches": package.get("hash_matches"), + "requires_fresh_production_truth_in_same_run": True, + "operator_secret_boundary": "future_shell_only", + "reads_secret_in_preview": False, + "executes_shell_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "manual_review_mode": "exception_only", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_AUTHORIZATION_LANE_GUARD_POLICY, + "result": lane_status, + "success": bool(closeout.get("success")), + "generated_at": closeout.get("generated_at"), + "source_policy": closeout.get("policy"), + "stats": closeout.get("stats") or {}, + "summary": { + "authorization_lane_guard_ready_count": 1 if lane_ready else 0, + "lane_guard_check_count": len(checks), + "lane_guard_pass_count": passed_count, + "lane_guard_waiting_count": len(waiting_checks), + "authorization_request_closeout_ready_count": summary.get( + "authorization_request_closeout_ready_count", 0 + ), + "exact_request_payload_field_count": summary.get( + "exact_request_payload_field_count", 0 + ), + "machine_request_manifest_step_count": summary.get( + "machine_request_manifest_step_count", 0 + ), + "lane_entry_requirement_count": len(lane_entry_requirements), + "required_request_evidence_count": summary.get("required_request_evidence_count", 0), + "authorization_acceptance_gate_count": summary.get( + "authorization_acceptance_gate_count", 0 + ), + "rejection_reason_count": summary.get("rejection_reason_count", 0), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "future_authorization_lane_guard": future_authorization_lane_guard, + "lane_transfer_contract": lane_transfer_contract, + "lane_entry_requirements": lane_entry_requirements, + "lane_guard_checks": checks, + "source_request_closeout_summary": summary, + "source_final_exact_request_package": package, + "source_machine_request_manifest": manifest, + "safety": { + "read_only_db_apply_authorization_lane_guard": True, + "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, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this guard to decide whether the exact request package can enter a future authorization lane.", + "Keep DB apply authorization, shell execution, SQL execution, and database writes blocked in this guard.", + "Require fresh production truth in the future authorization lane before any apply decision is issued.", + ], + } + + +def build_pchome_auto_policy_db_apply_authorization_request_closeout( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Close out authorization request intake into a final exact request package.""" + intake = build_pchome_auto_policy_db_apply_authorization_request_intake( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + request_intake = intake.get("authorization_request_intake") or {} + envelope = intake.get("authorization_envelope") or {} + summary = intake.get("summary") or {} + schema = intake.get("request_payload_schema") or {} + acceptance_gates = intake.get("authorization_acceptance_gates") or [] + evidence = intake.get("required_request_evidence") or [] + rejection_reasons = intake.get("rejection_reasons") or [] + required_fields = schema.get("required_fields") or [] + side_effect_free = ( + int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and request_intake.get("reads_secret_in_preview") is False + and request_intake.get("executes_shell_in_preview") is False + and request_intake.get("executes_sql_in_preview") is False + and request_intake.get("writes_database_in_preview") is False + ) + checks = [ + _authorization_request_closeout_check( + "authorization_request_intake_ready", + intake.get("result") == "DB_APPLY_AUTHORIZATION_REQUEST_INTAKE_READY" + and request_intake.get("ready_for_authorization_request_intake") is True, + { + "result": intake.get("result"), + "ready_for_authorization_request_intake": request_intake.get( + "ready_for_authorization_request_intake" + ), + }, + "wait_for_authorization_request_intake", + ), + _authorization_request_closeout_check( + "source_closeout_ready", + int(summary.get("closeout_ready_count") or 0) == 1, + {"closeout_ready_count": summary.get("closeout_ready_count", 0)}, + "wait_for_controlled_dry_run_shell_closeout", + ), + _authorization_request_closeout_check( + "required_request_evidence_complete", + len(evidence) == 7 and int(summary.get("required_request_evidence_count") or 0) == 7, + {"required_request_evidence_count": summary.get("required_request_evidence_count", 0)}, + "wait_for_required_request_evidence", + ), + _authorization_request_closeout_check( + "request_schema_complete", + len(required_fields) == 10 + and int(summary.get("request_payload_required_field_count") or 0) == 10, + {"required_fields": required_fields}, + "wait_for_request_payload_schema", + ), + _authorization_request_closeout_check( + "acceptance_gates_all_passed", + len(acceptance_gates) == 11 + and int(summary.get("authorization_acceptance_waiting_count", 0) or 0) == 0, + { + "authorization_acceptance_gate_count": summary.get( + "authorization_acceptance_gate_count", 0 + ), + "authorization_acceptance_waiting_count": summary.get( + "authorization_acceptance_waiting_count", 0 + ), + }, + "route_failed_acceptance_gate_to_exception_review", + ), + _authorization_request_closeout_check( + "rejection_policy_complete", + len(rejection_reasons) == 10 and "direct_database_apply_requested_from_intake" in rejection_reasons, + {"rejection_reason_count": len(rejection_reasons)}, + "wait_for_rejection_policy", + ), + _authorization_request_closeout_check( + "envelope_accepts_request_but_not_apply", + envelope.get("accepts_authorization_request") is True + and envelope.get("issues_database_apply_authorization") is False + and envelope.get("ready_for_database_apply_now") is False, + { + "accepts_authorization_request": envelope.get("accepts_authorization_request"), + "issues_database_apply_authorization": envelope.get( + "issues_database_apply_authorization" + ), + "ready_for_database_apply_now": envelope.get("ready_for_database_apply_now"), + }, + "block_if_intake_issues_apply_authorization", + ), + _authorization_request_closeout_check( + "migration_target_and_hash_locked", + bool(request_intake.get("target_file")) + and bool(request_intake.get("expected_sha256")) + and request_intake.get("hash_matches") is True, + { + "target_file": request_intake.get("target_file"), + "hash_matches": request_intake.get("hash_matches"), + }, + "abort_on_target_or_hash_gap", + ), + _authorization_request_closeout_check( + "secret_boundary_future_shell_only", + request_intake.get("operator_secret_boundary") == "future_shell_only" + and request_intake.get("reads_secret_in_preview") is False, + { + "operator_secret_boundary": request_intake.get("operator_secret_boundary"), + "reads_secret_in_preview": request_intake.get("reads_secret_in_preview"), + }, + "abort_on_secret_boundary_violation", + ), + _authorization_request_closeout_check( + "preview_has_no_shell_sql_or_db_side_effect", + side_effect_free, + { + "reads_secret_count": summary.get("reads_secret_count", 0), + "executes_script_count": summary.get("executes_script_count", 0), + "executes_sql_count": summary.get("executes_sql_count", 0), + "writes_database_count": summary.get("writes_database_count", 0), + }, + "abort_on_preview_side_effect", + ), + _authorization_request_closeout_check( + "direct_apply_rejected", + envelope.get("rejects_direct_database_apply") is True, + {"rejects_direct_database_apply": envelope.get("rejects_direct_database_apply")}, + "reject_direct_database_apply_request", + ), + _authorization_request_closeout_check( + "manual_review_regression_absent", + int(summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY) or 0) == 0, + {LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0)}, + "route_failed_verifier_to_exception_only", + ), + ] + passed_count = sum(1 for check in checks if check.get("passed")) + waiting_checks = [check for check in checks if not check.get("passed")] + closeout_ready = not waiting_checks + closeout_status = ( + "DB_APPLY_AUTHORIZATION_REQUEST_CLOSEOUT_READY" + if closeout_ready + else "WAITING_FOR_DB_APPLY_AUTHORIZATION_REQUEST_INTAKE" + ) + exact_request_payload_template = { + "requester": "{future_automation_or_operator_identity}", + "requested_at": "{utc_iso8601}", + "reason": "apply additive PChome auto-policy evidence receipts migration", + "target_file": request_intake.get("target_file"), + "expected_sha256": request_intake.get("expected_sha256"), + "closeout_boundary_id": request_intake.get("source_closeout_boundary_id"), + "dry_run_shell_preview_id": request_intake.get("source_dry_run_shell_preview_id"), + "fresh_production_truth": { + "required": True, + "same_run_only": True, + "source_command": "python scripts/ops/check_production_version_truth.py", + }, + "operator_acknowledges_secret_boundary": True, + "operator_acknowledges_rollback_boundary": True, + } + machine_request_manifest_steps = [ + { + "name": "refresh_production_truth_in_same_run", + "source_command": "python scripts/ops/check_production_version_truth.py", + "executes_in_preview": False, + }, + { + "name": "refresh_authorization_request_closeout", + "source_endpoint": "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-request-closeout", + "executes_in_preview": False, + }, + { + "name": "fill_exact_request_payload", + "required_fields": required_fields, + "writes_artifact_in_preview": False, + }, + { + "name": "reject_secret_material", + "rejects_database_url": True, + "rejects_authorization_header": True, + "rejects_cookie": True, + }, + { + "name": "run_acceptance_gates", + "gate_count": len(acceptance_gates), + "executes_sql_in_preview": False, + }, + { + "name": "emit_request_to_future_apply_authorization_lane", + "issues_database_apply_authorization": False, + "writes_database": False, + }, + ] + final_exact_request_package = { + "package_id": _db_apply_authorization_request_closeout_id(intake), + "source_intake_id": request_intake.get("intake_id"), + "source_closeout_boundary_id": request_intake.get("source_closeout_boundary_id"), + "source_dry_run_shell_preview_id": request_intake.get("source_dry_run_shell_preview_id"), + "status": closeout_status, + "ready_for_exact_authorization_request_package": closeout_ready, + "ready_for_database_apply_now": False, + "issues_database_apply_authorization": False, + "request_scope": "future_explicit_db_apply_authorization_only", + "target_file": request_intake.get("target_file"), + "expected_sha256": request_intake.get("expected_sha256"), + "actual_sha256": request_intake.get("actual_sha256"), + "hash_matches": request_intake.get("hash_matches"), + "exact_request_payload_template": exact_request_payload_template, + "payload_template_field_count": len(exact_request_payload_template), + "operator_secret_boundary": "future_shell_only", + "reads_secret_in_preview": False, + "executes_shell_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "manual_review_mode": "exception_only", + } + + return { + "policy": AUTO_POLICY_DB_APPLY_AUTHORIZATION_REQUEST_CLOSEOUT_POLICY, + "result": closeout_status, + "success": bool(intake.get("success")), + "generated_at": intake.get("generated_at"), + "source_policy": intake.get("policy"), + "stats": intake.get("stats") or {}, + "summary": { + "authorization_request_closeout_ready_count": 1 if closeout_ready else 0, + "closeout_check_count": len(checks), + "closeout_pass_count": passed_count, + "closeout_waiting_count": len(waiting_checks), + "authorization_request_intake_ready_count": summary.get( + "authorization_request_intake_ready_count", 0 + ), + "required_request_evidence_count": len(evidence), + "request_payload_required_field_count": len(required_fields), + "authorization_acceptance_gate_count": len(acceptance_gates), + "rejection_reason_count": len(rejection_reasons), + "exact_request_payload_field_count": len(exact_request_payload_template), + "machine_request_manifest_step_count": len(machine_request_manifest_steps), + "closeout_ready_count": summary.get("closeout_ready_count", 0), + "future_apply_boundary_count": summary.get("future_apply_boundary_count", 0), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "authorization_request_closeout": { + "status": closeout_status, + "ready_for_exact_authorization_request_package": closeout_ready, + "ready_for_database_apply_now": False, + "waiting_checks": waiting_checks, + "manual_review_mode": "exception_only", + }, + "final_exact_request_package": final_exact_request_package, + "machine_request_manifest": { + "mode": "future_apply_authorization_lane_only", + "source_endpoint": "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-request-closeout", + "manifest_steps": machine_request_manifest_steps, + "manifest_step_count": len(machine_request_manifest_steps), + "issues_database_apply_authorization": False, + "executes_in_preview": False, + "writes_database": False, + }, + "closeout_checks": checks, + "source_intake_summary": summary, + "source_request_payload_schema": schema, + "source_rejection_reasons": rejection_reasons, + "safety": { + "read_only_db_apply_authorization_request_closeout": True, + "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, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this closeout as the exact request payload template for the future apply authorization lane.", + "Keep the future lane from issuing DB apply authorization unless fresh production truth is present in the same run.", + "Reject direct apply, secrets, shell execution, SQL execution, or DB writes from this closeout.", + ], + } + + +def build_pchome_auto_policy_db_apply_authorization_request_intake( + payload: dict[str, Any], + batch_size: int = 12, + *, + execute_fetch: bool = False, + timeout_seconds: int = PCHOME_FETCH_DEFAULT_TIMEOUT_SECONDS, + http_get: Any = None, +) -> dict[str, Any]: + """Build a no-write intake envelope for a future explicit DB apply authorization request.""" + closeout = build_pchome_auto_policy_db_apply_controlled_dry_run_shell_closeout( + payload, + batch_size=batch_size, + execute_fetch=execute_fetch, + timeout_seconds=timeout_seconds, + http_get=http_get, + ) + boundary = closeout.get("explicit_authorization_boundary") or {} + summary = closeout.get("summary") or {} + safety = closeout.get("safety") or {} + closeout_ready = ( + closeout.get("result") == "DB_APPLY_CONTROLLED_DRY_RUN_SHELL_CLOSEOUT_READY" + and boundary.get("ready_for_explicit_apply_authorization_boundary") is True + ) + side_effect_free = ( + int(summary.get("writes_script_count") or 0) == 0 + and int(summary.get("reads_secret_count") or 0) == 0 + and int(summary.get("executes_script_count") or 0) == 0 + and int(summary.get("executes_sql_count") or 0) == 0 + and int(summary.get("writes_database_count") or 0) == 0 + and boundary.get("reads_secret_in_preview") is False + and boundary.get("executes_shell_in_preview") is False + and boundary.get("executes_sql_in_preview") is False + and boundary.get("writes_database_in_preview") is False + and safety.get("writes_database") is False + ) + target_file = boundary.get("target_file") + expected_sha256 = boundary.get("expected_sha256") + actual_sha256 = boundary.get("actual_sha256") + hash_matches = boundary.get("hash_matches") + intake_ready = closeout_ready and side_effect_free and bool(target_file) and hash_matches is True + intake_status = ( + "DB_APPLY_AUTHORIZATION_REQUEST_INTAKE_READY" + if intake_ready + else "WAITING_FOR_CONTROLLED_DRY_RUN_SHELL_CLOSEOUT" + ) + required_request_evidence = [ + { + "key": "fresh_production_truth_same_run", + "required": True, + "source_command": "python scripts/ops/check_production_version_truth.py", + }, + { + "key": "controlled_dry_run_shell_closeout_id", + "required": True, + "source_id": boundary.get("boundary_id"), + }, + { + "key": "final_handoff_package_id", + "required": True, + "source_id": boundary.get("source_final_handoff_package_id"), + }, + { + "key": "dry_run_shell_preview_id", + "required": True, + "source_id": boundary.get("source_dry_run_shell_preview_id"), + }, + { + "key": "migration_file_hash", + "required": True, + "target_file": target_file, + "expected_sha256": expected_sha256, + "actual_sha256": actual_sha256, + "hash_matches": hash_matches, + }, + { + "key": "future_apply_boundaries", + "required": True, + "boundary_count": summary.get("future_apply_boundary_count", 0), + }, + { + "key": "no_secret_no_shell_no_sql_no_db_preview_counters", + "required": True, + "reads_secret_count": 0, + "executes_script_count": 0, + "executes_sql_count": 0, + "writes_database_count": 0, + }, + ] + request_payload_schema = { + "required_fields": [ + "requester", + "requested_at", + "reason", + "target_file", + "expected_sha256", + "closeout_boundary_id", + "dry_run_shell_preview_id", + "fresh_production_truth", + "operator_acknowledges_secret_boundary", + "operator_acknowledges_rollback_boundary", + ], + "optional_fields": [ + "change_window_utc", + "rollback_contact", + "ticket_url", + "evidence_bundle_url", + ], + "rejects_extra_secret_material": True, + "accepts_database_url": False, + "accepts_authorization_header": False, + "accepts_cookie": False, + } + authorization_acceptance_gates = [ + { + "key": "closeout_result_ready", + "passed": closeout.get("result") == "DB_APPLY_CONTROLLED_DRY_RUN_SHELL_CLOSEOUT_READY", + "failure_route": "refresh_controlled_dry_run_shell_closeout", + }, + { + "key": "explicit_boundary_ready", + "passed": boundary.get("ready_for_explicit_apply_authorization_boundary") is True, + "failure_route": "wait_for_explicit_authorization_boundary", + }, + { + "key": "closeout_checks_all_passed", + "passed": int(summary.get("closeout_waiting_count") or 0) == 0 + and int(summary.get("closeout_pass_count") or 0) == int(summary.get("closeout_check_count") or -1), + "failure_route": "route_failed_closeout_checks_to_exception_review", + }, + { + "key": "future_apply_boundaries_complete", + "passed": int(summary.get("future_apply_boundary_count") or 0) >= 6, + "failure_route": "wait_for_future_apply_boundaries", + }, + { + "key": "production_truth_refresh_required", + "passed": boundary.get("requires_fresh_production_truth_in_future_run") is True, + "failure_route": "abort_without_fresh_production_truth", + }, + { + "key": "target_file_present", + "passed": bool(target_file), + "failure_route": "wait_for_migration_file_target", + }, + { + "key": "migration_hash_present_and_matches", + "passed": bool(expected_sha256) and bool(actual_sha256) and hash_matches is True, + "failure_route": "abort_on_migration_hash_mismatch", + }, + { + "key": "final_handoff_source_present", + "passed": bool(boundary.get("source_final_handoff_package_id")), + "failure_route": "wait_for_final_handoff_package", + }, + { + "key": "shell_secret_boundary_clean", + "passed": boundary.get("operator_secret_boundary") == "future_shell_only" + and boundary.get("reads_secret_in_preview") is False, + "failure_route": "abort_on_secret_boundary_violation", + }, + { + "key": "preview_has_no_side_effects", + "passed": side_effect_free, + "failure_route": "abort_on_preview_side_effect", + }, + { + "key": "explicit_request_payload_schema_defined", + "passed": len(request_payload_schema["required_fields"]) == 10, + "failure_route": "wait_for_request_schema", + }, + ] + rejection_reasons = [ + "production_truth_missing_or_stale", + "controlled_dry_run_shell_closeout_not_ready", + "closeout_boundary_id_missing", + "migration_file_hash_missing_or_mismatch", + "requester_missing", + "reason_missing", + "secret_material_in_request_payload", + "preview_attempted_shell_sql_or_database_side_effect", + "rollback_boundary_missing", + "direct_database_apply_requested_from_intake", + ] + authorization_request_intake = { + "intake_id": _db_apply_authorization_request_intake_id(closeout), + "source_closeout_boundary_id": boundary.get("boundary_id"), + "source_dry_run_shell_preview_id": boundary.get("source_dry_run_shell_preview_id"), + "source_final_handoff_package_id": boundary.get("source_final_handoff_package_id"), + "source_artifact_preview_id": boundary.get("source_artifact_preview_id"), + "source_authorization_package_id": boundary.get("source_authorization_package_id"), + "source_preflight_id": boundary.get("source_preflight_id"), + "source_request_id": boundary.get("source_request_id"), + "status": intake_status, + "ready_for_authorization_request_intake": intake_ready, + "ready_for_database_apply_now": False, + "request_scope": "future_explicit_db_apply_authorization_only", + "target_file": target_file, + "expected_sha256": expected_sha256, + "actual_sha256": actual_sha256, + "hash_matches": hash_matches, + "requires_new_explicit_db_apply_authorization": True, + "requires_fresh_production_truth_in_future_run": True, + "operator_secret_boundary": "future_shell_only", + "reads_secret_in_preview": False, + "writes_script_in_preview": False, + "executes_shell_in_preview": False, + "executes_sql_in_preview": False, + "writes_database_in_preview": False, + "manual_review_mode": "exception_only", + } + authorization_envelope = { + "mode": "request_intake_only", + "accepts_authorization_request": intake_ready, + "issues_database_apply_authorization": False, + "ready_for_database_apply_now": False, + "manual_review_mode": "exception_only", + "failed_gate_route": "exception_review_only", + "rejects_direct_database_apply": True, + } + passed_gate_count = sum(1 for gate in authorization_acceptance_gates if gate.get("passed")) + + return { + "policy": AUTO_POLICY_DB_APPLY_AUTHORIZATION_REQUEST_INTAKE_POLICY, + "result": intake_status, + "success": bool(closeout.get("success")), + "generated_at": closeout.get("generated_at"), + "source_policy": closeout.get("policy"), + "stats": closeout.get("stats") or {}, + "summary": { + "authorization_request_intake_ready_count": 1 if intake_ready else 0, + "required_request_evidence_count": len(required_request_evidence), + "request_payload_required_field_count": len(request_payload_schema["required_fields"]), + "authorization_acceptance_gate_count": len(authorization_acceptance_gates), + "authorization_acceptance_pass_count": passed_gate_count, + "authorization_acceptance_waiting_count": len(authorization_acceptance_gates) - passed_gate_count, + "rejection_reason_count": len(rejection_reasons), + "closeout_ready_count": summary.get("closeout_ready_count", 0), + "closeout_check_count": summary.get("closeout_check_count", 0), + "future_apply_boundary_count": summary.get("future_apply_boundary_count", 0), + "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, + LEGACY_REVIEW_REQUIRED_COUNT_KEY: summary.get(LEGACY_REVIEW_REQUIRED_COUNT_KEY, 0), + }, + "authorization_request_intake": authorization_request_intake, + "authorization_envelope": authorization_envelope, + "required_request_evidence": required_request_evidence, + "request_payload_schema": request_payload_schema, + "authorization_acceptance_gates": authorization_acceptance_gates, + "rejection_reasons": rejection_reasons, + "source_closeout_summary": summary, + "safety": { + "read_only_db_apply_authorization_request_intake": True, + "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, + "persists_receipt": False, + "updates_mapping": False, + "dispatches_telegram": False, + "llm_calls_in_gate": False, + "manual_review_mode": "exception_only", + }, + "next_actions": [ + "Use this intake envelope to accept or reject a separate explicit DB apply authorization request.", + "Keep the intake from issuing DB apply authorization; it only verifies request evidence completeness.", + "Reject any payload that includes secrets, asks for direct DB apply, or skips fresh production truth.", + ], + } diff --git a/tests/test_ai_automation_debt_service.py b/tests/test_ai_automation_debt_service.py new file mode 100644 index 0000000..145c2f9 --- /dev/null +++ b/tests/test_ai_automation_debt_service.py @@ -0,0 +1,49 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def test_ai_automation_debt_scan_is_read_only_and_prioritized(): + from services.ai_automation_debt_service import build_ai_automation_debt_report + + report = build_ai_automation_debt_report(max_findings=40, per_file_limit=6) + + assert report["policy"] == "read_only_ai_automation_debt_scan" + assert report["success"] is True + assert report["result"] == "PRODUCT_SURFACE_CLEAR" + assert report["summary"]["product_surface_blocker_count"] == 0 + assert report["summary"]["primary_human_gate_count"] == 0 + assert report["summary"]["ai_controlled_apply_ready"] is True + assert report["summary"]["market_intel_ai_controlled_alias_count"] >= 10 + assert report["summary"]["controlled_apply_candidate_count"] == 0 + assert report["summary"]["category_counts"].get("market_intel_ai_controlled_apply_candidate", 0) == 0 + assert report["summary"]["category_counts"].get("automation_debt", 0) == 0 + assert report["summary"]["category_counts"].get("governance_doc_debt", 0) == 0 + assert report["summary"]["category_counts"].get("ea_legacy_callback_debt", 0) == 0 + assert report["safety"] == { + "read_only": True, + "writes_database": False, + "executes_network": False, + "uses_llm": False, + "scans_raw_sessions": False, + "github_used": False, + } + assert all(item["priority"] != "P0" for item in report["findings"]) + assert report["next_work_order"][0]["lane"] == "product_surface" + assert report["next_work_order"][0]["status"] == "clear" + assert report["next_work_order"][1]["lane"] == "market_intel_controlled_apply" + assert report["next_work_order"][1]["status"] == "review_report_alias_layer_complete" + assert report["next_work_order"][1]["alias_count"] >= 90 + assert report["next_work_order"][1]["target_count"] == 0 + assert report["next_work_order"][2]["target_count"] == 0 + assert report["next_work_order"][3]["lane"] == "legacy_compatibility_aliases" + assert report["next_work_order"][3]["target_count"] == report["summary"]["legacy_compatibility_field_count"] + + +def test_ai_automation_debt_api_route_is_registered(): + route_source = (ROOT / "routes" / "ai_routes.py").read_text(encoding="utf-8") + + assert "@ai_bp.route('/api/ai/automation-debt')" in route_source + assert "build_ai_automation_debt_report" in route_source + assert '"source_endpoint"] = "/api/ai/automation-debt"' in route_source diff --git a/tests/test_pchome_mapping_backlog_report.py b/tests/test_pchome_mapping_backlog_report.py new file mode 100644 index 0000000..8780eb8 --- /dev/null +++ b/tests/test_pchome_mapping_backlog_report.py @@ -0,0 +1,20616 @@ +import hashlib +import json +from pathlib import Path + +from scripts.ops import report_pchome_mapping_backlog as report +from services.pchome_mapping_backlog_service import ( + build_pchome_auto_policy_apply_readiness_closeout, + build_pchome_auto_policy_db_apply_controlled_apply_final_preflight, + build_pchome_auto_policy_db_apply_controlled_dry_run_command_artifact_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_execution_plan_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_apply_executor_readiness_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_execution_preflight_guard_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_final_executor_guard_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_invocation_receipt_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_no_apply_enforcement_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_no_write_invocation_package_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_package, + build_pchome_auto_policy_db_apply_controlled_dry_run_pre_apply_replay_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_post_receipt_parser_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_receipt_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_runner_readiness, + build_pchome_auto_policy_db_apply_controlled_dry_run_runner_execution_receipt_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_runner_invocation_boundary_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_no_execution_receipt_handoff_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_final_no_runner_execution_proof_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_controlled_executor_quarantine_proof_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_execution_envelope_freeze_proof_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_frozen_envelope_verifier_handoff_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_verifier_invocation_lock_proof_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_verifier_no_execution_receipt_proof_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_no_write_ledger_retention_proof_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_archive_retention_sealed_handoff_proof_closeout, + build_pchome_auto_policy_db_apply_authorization_decision_closeout, + build_pchome_auto_policy_db_apply_authorization_decision_preflight, + build_pchome_auto_policy_db_apply_authorization_evidence_execution_closeout, + build_pchome_auto_policy_db_apply_authorization_evidence_execution_preflight, + build_pchome_auto_policy_db_apply_authorization_issuer_gate, + build_pchome_auto_policy_db_apply_authorization_lane_guard, + build_pchome_auto_policy_db_apply_authorization_request_closeout, + build_pchome_auto_policy_db_apply_authorization_request_intake, + build_pchome_auto_policy_db_apply_authorization_signing_decision_closeout, + build_pchome_auto_policy_db_apply_authorization_signing_decision_preflight, + build_pchome_auto_policy_db_apply_authorization_signing_execution_closeout, + build_pchome_auto_policy_db_apply_authorization_signing_execution_preflight, + build_pchome_auto_policy_db_apply_authorization_signing_issuer_closeout, + build_pchome_auto_policy_db_apply_authorization_signing_issuer_guard, + build_pchome_auto_policy_db_apply_authorization_detached_verification_evidence_validation, + build_pchome_auto_policy_db_apply_authorization_signed_receipt_closeout, + build_pchome_auto_policy_db_apply_authorization_signed_receipt_evidence_intake, + build_pchome_auto_policy_db_apply_authorization_signed_receipt_preflight, + build_pchome_auto_policy_db_apply_authorization_verifier_receipt_closeout, + build_pchome_auto_policy_db_apply_authorization_package, + build_pchome_auto_policy_db_apply_controlled_dry_run_shell_closeout, + build_pchome_auto_policy_db_apply_controlled_dry_run_shell_preview, + build_pchome_auto_policy_db_apply_execution_preflight, + build_pchome_auto_policy_db_apply_final_handoff_package, + build_pchome_auto_policy_db_apply_request_gate_preview, + build_pchome_auto_policy_db_apply_verifier_artifact_preview, + build_pchome_auto_policy_migration_apply_gate_preview, + build_pchome_auto_policy_migration_file_generation_request, + build_pchome_auto_policy_migration_file_preview, + build_pchome_auto_policy_persistence_gate, + build_pchome_auto_policy_receipt_gate, + build_pchome_auto_policy_schema_migration_preview, + build_pchome_evidence_enrichment_preview, + build_pchome_evidence_fetch_gate, + build_pchome_evidence_merge_preview, + build_pchome_evidence_source_preview, + build_pchome_direct_mapping_auto_search_package, + build_pchome_direct_mapping_candidate_decision_package, + build_pchome_growth_ai_automation_readiness, + build_pchome_mapping_operator_preview, + parse_pchome_product_page_evidence_html, + parse_unit_package_basis, + summarize_pchome_mapping_backlog, +) + + +ROOT = Path(__file__).resolve().parents[1] + + +def _payload(): + return { + "success": True, + "system_name": "MOMO Pro", + "generated_at": "2026-06-28T01:16:02", + "cache_state": "fresh", + "stats": { + "candidate_count": 4, + "mapped_count": 1, + "mapping_rate": 25.0, + "needs_mapping_count": 3, + "review_candidate_count": 1, + "overall_latest_sales_date": "2026-06-24", + "overall_sales_7d": 2020234.0, + "action_counts": { + "先補商品對應": 2, + "確認候選": 1, + "放大價格優勢": 1, + }, + "action_code_counts": { + "map_external_product": 2, + "review_external_candidate": 1, + "amplify_price_advantage": 1, + }, + }, + "opportunities": [ + { + "pchome_product_id": "PCH-1", + "product_name": "Mapped product", + "sales_7d": 0, + "external_price": {"momo_sku": "M-1", "price_basis": "unit_price", "gap_pct": 12.5}, + "recommended_action": {"code": "amplify_price_advantage", "label": "放大價格優勢"}, + "priority_score": 75.0, + }, + { + "pchome_product_id": "PCH-2", + "product_name": "Direct mapping product 40ml x2", + "sales_7d": 9800, + "pchome_price": 1200, + "external_price": None, + "recommended_action": {"code": "map_external_product", "label": "先補商品對應"}, + "priority_score": 88.0, + "reason_lines": ["需要補商品對應"], + }, + { + "pchome_product_id": "PCH-3", + "product_name": "Review candidate product", + "sales_7d": 1200, + "external_price": None, + "review_candidate": { + "id": 725, + "momo_sku": "5868343", + "momo_name": "MOMO candidate", + "quality_score": 94.8, + }, + "recommended_action": {"code": "review_external_candidate", "label": "確認候選"}, + "priority_score": 64.0, + }, + { + "pchome_product_id": "PCH-4", + "product_name": "Another direct mapping product", + "sales_7d": 3100, + "external_price": None, + "recommended_action": {"code": "map_external_product", "label": "先補商品對應"}, + "priority_score": 52.0, + }, + ], + } + + +def test_with_limit_preserves_existing_query_and_clamps_limit(): + url = report.with_limit("https://example.test/path?refresh=1", 99) + + assert url == "https://example.test/path?refresh=1&limit=50" + + +def test_summarize_payload_turns_growth_api_into_mapping_backlog(): + summary = report.summarize_payload(_payload()) + + assert summary["policy"] == "read_only_pchome_growth_mapping_backlog" + assert summary["result"] == "NEEDS_MAPPING" + assert summary["stats"]["mapping_rate"] == 25.0 + assert summary["stats"]["needs_mapping_count"] == 3 + assert summary["backlog"]["direct_mapping_count"] == 2 + assert summary["backlog"]["review_candidate_count"] == 1 + assert summary["backlog"]["mapped_opportunity_count"] == 1 + assert summary["backlog"]["top_needs_mapping"][0]["pchome_product_id"] == "PCH-2" + assert summary["backlog"]["top_needs_mapping"][0]["product_url"] == "https://24h.pchome.com.tw/prod/PCH-2" + direct_evidence = summary["backlog"]["top_needs_mapping"][0]["evidence_completeness"] + assert "stable_product_id" in direct_evidence["present_fields"] + assert "unit_price_or_package_basis" in direct_evidence["present_fields"] + assert direct_evidence["unit_package_basis"]["package_basis"] == "multi_pack_quantity_candidate" + assert direct_evidence["unit_package_basis"]["estimated_total_quantity"] == 80 + assert "image" in direct_evidence["missing_fields"] + assert "availability" in direct_evidence["missing_fields"] + assert direct_evidence["auto_accept_ready"] is False + assert direct_evidence["human_review_required"] is False + assert direct_evidence["legacy_human_review_required"] is True + assert direct_evidence["ai_exception_required"] is True + assert direct_evidence["primary_human_gate_count"] == 0 + assert summary["backlog"]["top_review_candidates"][0]["review_candidate"]["momo_sku"] == "5868343" + + +def test_shared_service_is_the_single_mapping_backlog_summary_source(): + assert report.summarize_payload(_payload()) == summarize_pchome_mapping_backlog(_payload()) + + +def test_operator_preview_is_read_only_and_requires_write_gate(): + preview = build_pchome_mapping_operator_preview(_payload(), batch_size=1) + + assert preview["policy"] == "read_only_pchome_growth_mapping_operator_preview" + assert preview["result"] == "READY_FOR_OPERATOR_PREVIEW" + assert preview["operator_batch"]["selected_direct_mapping_count"] == 1 + assert preview["operator_batch"]["direct_mapping_targets"][0]["pchome_product_id"] == "PCH-2" + assert preview["operator_batch"]["direct_mapping_targets"][0]["evidence_completeness"]["auto_accept_ready"] is False + assert preview["command_preview"]["endpoint"] == "/api/ai/pchome-growth/backfill-momo-candidates" + assert preview["command_preview"]["writes_database"] is True + assert preview["command_preview"]["write_gate_required"] is True + assert preview["external_benchmark_alignment"]["references"][0]["source"] == "Google Merchant Center product data specification" + assert any( + item["field"] == "image" and item["status"] == "missing_in_current_growth_payload" + for item in preview["external_benchmark_alignment"]["required_evidence_fields"] + ) + assert preview["ai_automation_plan"]["policy"] == "ollama_first_read_only_ai_assist" + assert preview["ai_automation_plan"]["llm_calls_in_preview"] is False + assert preview["ai_automation_plan"]["gemini_allowed"] is False + assert preview["ai_automation_plan"]["automation_readiness"]["can_execute_write"] is False + assert preview["safety"]["read_only_preview"] is True + assert preview["safety"]["writes_database"] is False + assert preview["safety"]["executes_search"] is False + + +def test_direct_mapping_auto_search_package_builds_p1_no_write_search_terms(): + package = build_pchome_direct_mapping_auto_search_package(_payload(), batch_size=1) + + target = package["search_package"]["targets"][0] + assert package["policy"] == "read_only_pchome_growth_direct_mapping_auto_search_package" + assert package["result"] == "DIRECT_MAPPING_SEARCH_PACKAGE_READY" + assert package["source_policy"] == "read_only_pchome_growth_mapping_operator_preview" + assert package["summary"]["direct_mapping_count"] == 2 + assert package["summary"]["selected_direct_mapping_count"] == 1 + assert package["summary"]["search_ready_target_count"] == 1 + assert package["summary"]["planned_search_term_count"] >= 1 + assert package["summary"]["execute_search_count"] == 0 + assert package["summary"]["candidates_found_count"] == 0 + assert target["pchome_product_id"] == "PCH-2" + assert target["can_execute_read_only_search"] is True + assert "40ml" in " ".join(target["search_terms"]).lower() + assert target["identity_anchors"]["stable_product_id"] is True + assert target["identity_anchors"]["unit_basis_present"] is True + assert "no_database_write_from_search_package" in target["candidate_acceptance_gates"] + assert package["search_execution"]["executed"] is False + assert package["search_execution"]["writes_database"] is False + assert package["candidate_acceptance_policy"]["routes_manual_review_to_machine_verifiable_decision"] is True + assert package["safety"]["executes_search"] is False + assert package["safety"]["writes_database"] is False + assert package["safety"]["syncs_external_offers"] is False + + +def test_direct_mapping_auto_search_package_executes_fake_search_without_db_write(): + def fake_search(targets, limit_per_product, max_products, max_terms_per_product, min_score): + assert targets[0]["product_id"] == "PCH-2" + assert max_products == 1 + assert limit_per_product == 3 + assert max_terms_per_product == 2 + assert min_score == 0.5 + return True, "found", [ + { + "product_id": "MOMO-1", + "name": "Direct mapping product 40ml x2", + "price": 999, + "target_pchome_product_id": "PCH-2", + "target_match_score": 0.92, + "auto_compare_type": "total_price", + "target_hard_veto": False, + } + ] + + package = build_pchome_direct_mapping_auto_search_package( + _payload(), + batch_size=1, + execute_search=True, + limit_per_product=3, + max_terms_per_product=2, + min_score=0.5, + search_func=fake_search, + ) + + target = package["search_package"]["targets"][0] + assert package["result"] == "DIRECT_MAPPING_CANDIDATES_FOUND" + assert package["summary"]["execute_search_count"] == 1 + assert package["summary"]["candidates_found_count"] == 1 + assert package["summary"]["auto_compare_candidate_count"] == 1 + assert package["summary"]["review_candidate_count"] == 0 + assert target["candidate_count"] == 1 + assert target["candidate_ids"] == ["MOMO-1"] + assert package["search_execution"]["executed"] is True + assert package["search_execution"]["search_success"] is True + assert package["search_execution"]["candidate_count"] == 1 + assert package["search_execution"]["writes_database"] is False + assert package["search_execution"]["syncs_external_offers"] is False + assert package["candidate_preview"][0]["target_pchome_product_id"] == "PCH-2" + assert package["safety"]["executes_search"] is True + assert package["safety"]["writes_database"] is False + assert package["safety"]["persists_candidate"] is False + + +def test_direct_mapping_candidate_decision_package_waits_for_search_candidates_without_db_write(): + package = build_pchome_direct_mapping_candidate_decision_package(_payload(), batch_size=1) + + assert package["policy"] == "read_only_pchome_growth_direct_mapping_candidate_decision_package" + assert package["result"] == "WAITING_FOR_DIRECT_MAPPING_CANDIDATES" + assert package["source_policy"] == "read_only_pchome_growth_direct_mapping_auto_search_package" + assert package["summary"]["direct_mapping_count"] == 2 + assert package["summary"]["selected_direct_mapping_count"] == 1 + assert package["summary"]["candidate_decision_count"] == 0 + assert package["summary"]["auto_compare_decision_count"] == 0 + assert package["summary"]["machine_review_decision_count"] == 0 + assert package["summary"]["can_auto_persist_now_count"] == 0 + assert package["decision_package"]["candidate_decisions"] == [] + assert package["decision_package"]["manual_review_mode"] == "exception_only" + assert package["decision_acceptance_policy"]["writes_database"] is False + assert package["safety"]["executes_search"] is False + assert package["safety"]["writes_database"] is False + assert package["safety"]["persists_candidate"] is False + + +def test_direct_mapping_candidate_decision_package_routes_candidates_to_machine_verifiable_actions(): + def fake_search(targets, limit_per_product, max_products, max_terms_per_product, min_score): + return True, "found", [ + { + "product_id": "MOMO-1", + "name": "Direct mapping product 40ml x2", + "price": 999, + "target_pchome_product_id": "PCH-2", + "target_pchome_name": "Direct mapping product 40ml x2", + "target_match_score": 0.92, + "auto_compare_type": "total_price", + "target_hard_veto": False, + "target_price_basis": "total_price", + "target_gap_pct": 16.8, + "target_search_term": "direct mapping product 40ml x2", + }, + { + "product_id": "MOMO-2", + "name": "Variant candidate", + "price": 899, + "target_pchome_product_id": "PCH-2", + "target_pchome_name": "Direct mapping product 40ml x2", + "target_match_score": 0.51, + "auto_compare_type": "manual_review", + "target_hard_veto": False, + }, + ] + + package = build_pchome_direct_mapping_candidate_decision_package( + _payload(), + batch_size=1, + execute_search=True, + limit_per_product=3, + max_terms_per_product=2, + min_score=0.5, + search_func=fake_search, + ) + + decisions = package["decision_package"]["candidate_decisions"] + assert package["result"] == "DIRECT_MAPPING_CANDIDATE_DECISION_PACKAGE_READY" + assert package["summary"]["candidates_found_count"] == 2 + assert package["summary"]["candidate_decision_count"] == 2 + assert package["summary"]["auto_compare_decision_count"] == 1 + assert package["summary"]["machine_review_decision_count"] == 1 + assert package["summary"]["can_auto_persist_now_count"] == 0 + assert decisions[0]["decision_id"].startswith("pchome-direct-mapping-candidate-") + assert decisions[0]["decision"] == "route_to_no_write_auto_compare_receipt" + assert decisions[0]["data_quality"] == "ready_for_no_write_receipt" + assert decisions[0]["guardrails"]["machine_actionable"] is True + assert decisions[0]["guardrails"]["can_auto_execute"] is False + assert decisions[0]["guardrails"]["writes_database"] is False + assert decisions[0]["guardrails"]["persists_candidate"] is False + assert decisions[0]["guardrails"]["manual_review_mode"] == "exception_only" + assert decisions[1]["decision"] == "route_to_machine_review_decision" + assert decisions[1]["failure_reasons"] == ["auto_compare_type_not_receipt_ready"] + assert package["safety"]["executes_search"] is True + assert package["safety"]["writes_database"] is False + assert package["safety"]["persists_candidate"] is False + + +def test_ai_automation_readiness_makes_automation_visible_without_manual_primary_flow(): + readiness = build_pchome_growth_ai_automation_readiness(_payload(), batch_size=1) + + lanes = {lane["key"]: lane for lane in readiness["automation_lanes"]} + assert readiness["policy"] == "read_only_pchome_growth_ai_automation_readiness" + assert readiness["result"] == "AI_AUTOMATION_ACTIVE_WAITING_FOR_CANDIDATES" + assert readiness["summary"]["direct_mapping_count"] == 2 + assert readiness["summary"]["selected_search_target_count"] == 1 + assert readiness["summary"]["planned_search_term_count"] >= 1 + assert readiness["summary"]["waiting_candidate_count"] == 1 + assert readiness["summary"]["primary_human_gate_count"] == 0 + assert readiness["summary"]["ai_exception_count"] == 0 + assert readiness["summary"]["manual_required_as_primary_flow_count"] == 0 + assert readiness["automation_policy"]["primary_flow"] == "ai_controlled" + assert readiness["automation_policy"]["human_primary_flow"] is False + assert readiness["ai_exception_auto_resolution"]["mode"] == "machine_verifiable_auto_resolution" + assert readiness["ai_exception_auto_resolution"]["primary_human_gate_count"] == 0 + assert readiness["manual_policy"]["manual_review_mode"] == "exception_only" + assert readiness["manual_policy"]["deprecated_product_surface"] is True + assert readiness["manual_policy"]["manual_as_primary_flow"] is False + assert lanes["same_item_search_package"]["status"] == "ready" + assert lanes["candidate_decision_package"]["status"] == "waiting" + assert lanes["candidate_decision_package"]["ai_exception_mode"] == "machine_verifiable_auto_resolution" + assert lanes["controlled_apply"]["status"] == "blocked_until_verifier" + assert readiness["safety"]["writes_database"] is False + assert readiness["safety"]["llm_calls_in_preview"] is False + + +def test_unit_package_basis_parser_extracts_quantity_count_and_risk_signals(): + single = parse_unit_package_basis("雅詩蘭黛 粉持久完美持妝粉底 30ml") + assert single["package_basis"] == "single_unit_quantity_candidate" + assert single["quantities"][0] == {"value": 30, "unit": "ml", "raw": "30ml"} + assert single["unit_pricing_measure"] == {"value": 30, "unit": "ml"} + assert single["unit_pricing_base_measure"] == {"value": 100, "unit": "ml"} + assert single["human_review_required"] is False + assert single["ai_exception_required"] is False + assert single["primary_human_gate_count"] == 0 + assert single["writes_database"] is False + assert single["fetches_external_sites"] is False + assert single["llm_calls"] is False + + bundle = parse_unit_package_basis("理膚寶水 B5 修復霜 40ml x2 超值組") + assert bundle["package_basis"] == "variant_sensitive_quantity_candidate" + assert bundle["multipliers"] == [2] + assert bundle["estimated_total_quantity"] == 80 + assert "bundle_or_promo" in bundle["risk_signals"] + assert bundle["human_review_required"] is False + assert bundle["legacy_human_review_required"] is True + assert bundle["ai_exception_required"] is True + + variant = parse_unit_package_basis("Dior 癮誘唇膏 3.2g 多款任選") + assert variant["package_basis"] == "variant_sensitive_quantity_candidate" + assert variant["unit_label"] == "g" + assert "variant_selection" in variant["risk_signals"] + assert variant["human_review_required"] is False + assert variant["legacy_human_review_required"] is True + assert variant["ai_exception_required"] is True + + count_only = parse_unit_package_basis("濕紙巾 42張") + assert count_only["package_basis"] == "count_package_candidate" + assert count_only["unit_pricing_measure"] == {"value": 42, "unit": "ct"} + + +def test_evidence_enrichment_preview_builds_missing_field_tasks(): + preview = build_pchome_evidence_enrichment_preview(_payload(), batch_size=1) + + assert preview["policy"] == "read_only_pchome_growth_evidence_enrichment_preview" + assert preview["result"] == "NEEDS_EVIDENCE_ENRICHMENT" + assert preview["summary"]["task_count"] == 2 + assert preview["summary"]["tasks_with_blockers"] == 2 + assert preview["summary"]["missing_field_counts"]["image"] == 2 + assert preview["summary"]["missing_field_counts"]["availability"] == 2 + assert preview["summary"]["missing_field_counts"]["unit_price_or_package_basis"] == 1 + assert preview["evidence_tasks"][0]["lane"] == "direct_mapping" + assert preview["evidence_tasks"][0]["product_url"] == "https://24h.pchome.com.tw/prod/PCH-2" + assert "unit_price_or_package_basis" not in preview["evidence_tasks"][0]["missing_fields"] + assert preview["evidence_tasks"][0]["unit_package_basis"]["estimated_total_quantity"] == 80 + assert "image" in preview["evidence_tasks"][0]["blocking_missing_fields"] + assert preview["evidence_tasks"][0]["enrichment_steps"][0]["sources"][0]["writes_database"] is False + assert preview["ai_automation_plan"]["policy"] == "ollama_first_read_only_evidence_assist" + assert preview["ai_automation_plan"]["llm_calls_in_preview"] is False + assert preview["safety"]["fetches_external_sites"] is False + assert preview["safety"]["writes_database"] is False + + +def test_review_candidate_pchome_price_counts_as_price_evidence(): + payload = json.loads(json.dumps(_payload())) + payload["opportunities"][2]["review_candidate"]["pchome_price"] = 880 + + preview = build_pchome_evidence_enrichment_preview(payload, batch_size=1) + review_task = next(task for task in preview["evidence_tasks"] if task["pchome_product_id"] == "PCH-3") + + assert "price" not in review_task["missing_fields"] + assert "price" in review_task["present_fields"] + + +def test_evidence_source_preview_plans_read_only_fetch_gates_without_fetching(): + preview = build_pchome_evidence_source_preview(_payload(), batch_size=1) + + assert preview["policy"] == "read_only_pchome_growth_evidence_source_preview" + assert preview["result"] == "NEEDS_SOURCE_WIRING" + assert preview["source_policy"] == "read_only_pchome_growth_evidence_enrichment_preview" + assert preview["summary"]["field_counts"]["image"]["missing_count"] == 2 + assert preview["summary"]["field_counts"]["availability"]["missing_count"] == 2 + assert preview["summary"]["field_counts"]["price"]["missing_count"] == 1 + assert preview["source_plans"]["image"]["future_read_only_fetch_gate"]["method"] == "GET" + assert ( + preview["source_plans"]["image"]["future_read_only_fetch_gate"]["check_mode_parser"] + == "read_only_pchome_product_page_evidence_parser" + ) + assert preview["source_plans"]["image"]["future_read_only_fetch_gate"]["fetches_external_sites_in_preview"] is False + assert preview["source_plans"]["availability"]["future_read_only_fetch_gate"]["writes_database"] is False + assert preview["source_plans"]["price"]["payload_mapping_probe"]["writes_database"] is False + assert preview["fetch_gate_candidates"][0]["product_url"] == "https://24h.pchome.com.tw/prod/PCH-2" + assert preview["fetch_gate_candidates"][0]["executes_fetch_in_preview"] is False + assert preview["ai_automation_plan"]["llm_calls_in_preview"] is False + assert preview["ai_automation_plan"]["gemini_allowed"] is False + assert preview["safety"]["fetches_external_sites"] is False + assert preview["safety"]["writes_database"] is False + + +def test_product_page_evidence_parser_reads_jsonld_without_fetching(): + html = """ + + + + """ + + parsed = parse_pchome_product_page_evidence_html(html, product_url="https://24h.pchome.com.tw/prod/PCH-2") + + assert parsed["policy"] == "read_only_pchome_product_page_evidence_parser" + assert parsed["source"] == "html_fixture" + assert parsed["image_url"] == "https://cdn.example.test/product.jpg" + assert parsed["availability"] == "in_stock" + assert parsed["jsonld_product_found"] is True + assert parsed["jsonld_offer_found"] is True + assert parsed["safety"]["fetches_external_sites"] is False + assert parsed["safety"]["writes_database"] is False + assert parsed["safety"]["llm_calls"] is False + + +def test_product_page_evidence_parser_uses_meta_fallbacks_and_skips_invalid_jsonld(): + html = """ + + + + + + """ + + parsed = parse_pchome_product_page_evidence_html(html) + + assert parsed["image_url"] == "https://cdn.example.test/fallback.jpg" + assert parsed["availability"] == "out_of_stock" + assert parsed["fallbacks_used"] == ["og:image", "product:availability"] + assert parsed["parser_warnings"] == ["invalid_jsonld_skipped"] + + +def test_evidence_fetch_gate_defaults_to_planned_no_fetch_receipts(): + preview = build_pchome_evidence_fetch_gate(_payload(), batch_size=1) + + assert preview["policy"] == "controlled_read_only_pchome_product_page_evidence_fetch_gate" + assert preview["result"] == "FETCH_GATE_PLANNED" + assert preview["summary"]["candidate_count"] == 1 + assert preview["summary"]["executed_fetch_count"] == 0 + assert preview["fetch_config"]["execute_fetch"] is False + assert preview["fetch_receipts"][0]["status"] == "PLANNED" + assert preview["fetch_receipts"][0]["executed_fetch"] is False + assert preview["fetch_receipts"][0]["writes_database"] is False + assert preview["safety"]["read_only_fetch_gate"] is True + assert preview["safety"]["writes_database"] is False + + +def test_evidence_fetch_gate_executes_fake_get_and_parses_receipt(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + calls = [] + + def fake_get(url, timeout, headers): + calls.append({"url": url, "timeout": timeout, "headers": headers}) + return FakeResponse() + + preview = build_pchome_evidence_fetch_gate( + _payload(), + batch_size=1, + execute_fetch=True, + timeout_seconds=2, + http_get=fake_get, + ) + + receipt = preview["fetch_receipts"][0] + assert preview["result"] == "FETCH_GATE_EXECUTED_WITH_EVIDENCE" + assert preview["summary"]["executed_fetch_count"] == 1 + assert preview["summary"]["parsed_image_count"] == 1 + assert preview["summary"]["parsed_availability_count"] == 1 + assert receipt["status"] == "FETCHED_WITH_EVIDENCE" + assert receipt["executed_fetch"] is True + assert receipt["parsed_evidence"]["image_url"] == "https://cdn.example.test/pchome.jpg" + assert receipt["parsed_evidence"]["availability"] == "in_stock" + assert receipt["writes_database"] is False + assert calls[0]["url"] == "https://24h.pchome.com.tw/prod/PCH-2" + assert calls[0]["timeout"] == 2 + + +def test_evidence_fetch_gate_blocks_non_allowlisted_product_url(): + payload = _payload() + payload["opportunities"][1]["product_url"] = "https://example.test/prod/PCH-2" + + preview = build_pchome_evidence_fetch_gate(payload, batch_size=1, execute_fetch=True) + + assert preview["result"] == "FETCH_GATE_EXECUTED_WITH_BLOCKERS" + assert preview["summary"]["blocked_count"] == 1 + assert preview["fetch_receipts"][0]["status"] == "BLOCKED_BY_ALLOWLIST" + assert preview["fetch_receipts"][0]["executed_fetch"] is False + + +def test_evidence_merge_preview_requires_fetch_before_merge_by_default(): + preview = build_pchome_evidence_merge_preview(_payload(), batch_size=1) + + assert preview["policy"] == "read_only_pchome_growth_evidence_merge_preview" + assert preview["result"] == "FETCH_REQUIRED_FOR_MERGE_PREVIEW" + assert preview["summary"]["executed_fetch_count"] == 0 + assert preview["summary"]["writes_database_count"] == 0 + assert preview["summary"]["manual_review_required_count"] == 0 + assert preview["summary"]["manual_review_mode"] == "exception_only" + assert preview["merge_items"][0]["merge_status"] == "FETCH_GATE_PLANNED" + assert preview["merge_items"][0]["automation_decision"] == "AUTO_RUN_FETCH_GATE" + assert preview["merge_items"][0]["manual_review_required"] is False + assert preview["merge_items"][0]["writes_database"] is False + assert preview["safety"]["writes_database"] is False + assert preview["safety"]["updates_mapping"] is False + assert preview["safety"]["requires_operator_review_before_write"] is False + + +def test_evidence_merge_preview_merges_fake_fetch_receipt_without_writing(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + preview = build_pchome_evidence_merge_preview( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + item = preview["merge_items"][0] + assert preview["result"] == "MERGE_PREVIEW_READY" + assert preview["summary"]["merge_ready_count"] == 1 + assert preview["summary"]["auto_merge_ready_count"] == 1 + assert preview["summary"]["manual_review_required_count"] == 0 + assert item["merge_status"] == "MERGE_PREVIEW_READY" + assert item["automation_decision"] == "AUTO_ACCEPT_EVIDENCE_MERGE" + assert item["automation_allowed"] is True + assert item["manual_review_required"] is False + assert item["evidence_delta"] == { + "image_url": "https://cdn.example.test/merged.jpg", + "availability": "in_stock", + } + assert "image" in item["merged_present_fields"] + assert "availability" in item["merged_present_fields"] + assert "image" not in item["remaining_missing_fields"] + assert "availability" not in item["remaining_missing_fields"] + assert item["writes_database"] is False + assert preview["safety"]["writes_database"] is False + assert preview["safety"]["manual_review_mode"] == "exception_only" + + +def test_auto_policy_receipt_gate_builds_planned_receipts_without_persisting(): + gate = build_pchome_auto_policy_receipt_gate(_payload(), batch_size=1) + + receipt = gate["auto_policy_receipts"][0] + assert gate["policy"] == "read_only_pchome_growth_auto_policy_receipt_gate" + assert gate["result"] == "AUTO_POLICY_RECEIPTS_PLANNED" + assert gate["summary"]["receipt_count"] == 2 + assert gate["summary"]["ready_for_auto_fetch_count"] == 1 + assert gate["summary"]["manual_review_required_count"] == 0 + assert gate["summary"]["writes_database_count"] == 0 + assert gate["summary"]["persists_receipt_count"] == 0 + assert receipt["receipt_id"].startswith("pchome-evidence-") + assert receipt["receipt_status"] == "READY_FOR_AUTO_FETCH" + assert receipt["automation_decision"] == "AUTO_RUN_FETCH_GATE" + assert receipt["manual_review_required"] is False + assert receipt["writes_database"] is False + assert receipt["persists_receipt"] is False + assert gate["persistence_gate"]["mode"] == "no_write_receipt_preview" + assert gate["safety"]["writes_database"] is False + assert gate["safety"]["persists_receipt"] is False + + +def test_auto_policy_receipt_gate_prepares_auto_persistence_receipt_after_fake_fetch(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + gate = build_pchome_auto_policy_receipt_gate( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + receipt = gate["auto_policy_receipts"][0] + assert gate["result"] == "AUTO_POLICY_RECEIPTS_READY" + assert gate["summary"]["ready_for_auto_persistence_count"] == 1 + assert gate["summary"]["receipt_status_counts"]["READY_FOR_AUTO_PERSISTENCE"] == 1 + assert receipt["receipt_status"] == "READY_FOR_AUTO_PERSISTENCE" + assert receipt["automation_decision"] == "AUTO_ACCEPT_EVIDENCE_MERGE" + assert receipt["evidence_delta"] == { + "image_url": "https://cdn.example.test/receipt.jpg", + "availability": "in_stock", + } + assert receipt["persists_receipt"] is False + assert receipt["writes_database"] is False + + +def test_auto_policy_persistence_gate_builds_no_write_dry_run_contract_without_fetch(): + gate = build_pchome_auto_policy_persistence_gate(_payload(), batch_size=1) + + item = gate["persistence_items"][0] + assert gate["policy"] == "read_only_pchome_growth_auto_policy_persistence_gate" + assert gate["result"] == "PERSISTENCE_WAITING_FOR_RECEIPTS" + assert gate["summary"]["persistence_item_count"] == 2 + assert gate["summary"]["dry_run_ready_count"] == 0 + assert gate["summary"]["waiting_for_receipt_count"] == 2 + assert gate["summary"]["writes_database_count"] == 0 + assert gate["summary"]["persists_receipt_count"] == 0 + assert gate["schema_contract"]["requires_schema_migration_before_apply"] is True + assert gate["apply_gate"]["mode"] == "dry_run_only" + assert item["persistence_status"] == "WAITING_FOR_READY_RECEIPT" + assert item["planned_operation"] == "NOOP" + assert item["writes_database"] is False + assert item["persists_receipt"] is False + assert gate["safety"]["writes_database"] is False + assert gate["safety"]["persists_receipt"] is False + + +def test_auto_policy_persistence_gate_prepares_idempotent_transaction_preview_after_fake_fetch(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + gate = build_pchome_auto_policy_persistence_gate( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + item = gate["persistence_items"][0] + assert gate["result"] == "PERSISTENCE_DRY_RUN_READY" + assert gate["summary"]["dry_run_ready_count"] == 1 + assert item["persistence_status"] == "DRY_RUN_READY" + assert item["planned_operation"] == "UPSERT_EVIDENCE_RECEIPT_AND_PATCH_EXTERNAL_OFFER_EVIDENCE" + assert item["idempotency_key"].startswith("pchome-evidence-") + assert item["payload_hash"] + assert item["transaction_preview"]["commit"] == "future_apply_gate_only" + assert item["parameter_preview"]["image_url_present"] is True + assert item["parameter_preview"]["availability"] == "in_stock" + assert item["target_tables"] == ["external_offer_evidence_receipts", "external_offers"] + assert item["writes_database"] is False + assert item["persists_receipt"] is False + assert gate["apply_gate"]["writes_database"] is False + assert gate["safety"]["writes_database"] is False + + +def test_auto_policy_schema_migration_preview_stays_no_write_without_fetch(): + preview = build_pchome_auto_policy_schema_migration_preview(_payload(), batch_size=1) + + ddl_preview = "\n".join(preview["schema_migration_preview"]["ddl_preview"]) + assert preview["policy"] == "read_only_pchome_growth_auto_policy_schema_migration_preview" + assert preview["result"] == "SCHEMA_MIGRATION_PREVIEW_READY" + assert preview["future_apply_gate"]["status"] == "WAITING_FOR_DRY_RUN_READY_ITEMS" + assert preview["summary"]["dry_run_ready_count"] == 0 + assert preview["summary"]["executes_migration_count"] == 0 + assert preview["summary"]["writes_database_count"] == 0 + assert "CREATE TABLE IF NOT EXISTS external_offer_evidence_receipts" in ddl_preview + assert "JSONB" in ddl_preview + assert preview["schema_migration_preview"]["executes_sql"] is False + assert preview["prewrite_snapshot_contract"]["executes_sql"] is False + assert preview["future_apply_verifier"]["executes_in_preview"] is False + assert preview["future_apply_gate"]["current_preview_apply_allowed"] is False + assert preview["safety"]["executes_migration"] is False + assert preview["safety"]["writes_database"] is False + + +def test_auto_policy_schema_migration_preview_builds_future_apply_verifier_after_fake_fetch(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + preview = build_pchome_auto_policy_schema_migration_preview( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + assert preview["future_apply_gate"]["status"] == "APPLY_CONTRACT_READY" + assert preview["summary"]["dry_run_ready_count"] == 1 + assert preview["summary"]["future_verifier_count"] == 5 + assert preview["prewrite_snapshot_contract"]["target_receipt_ids"][0].startswith("pchome-evidence-") + assert preview["prewrite_snapshot_contract"]["target_pchome_product_ids"] == ["PCH-2"] + assert preview["future_apply_verifier"]["manual_review_mode"] == "exception_only" + assert preview["future_apply_verifier"]["checks"][0]["routes_failure_to"] == "exception_review" + assert preview["future_apply_gate"]["requires_prewrite_snapshot"] is True + assert preview["future_apply_gate"]["requires_post_write_readback"] is True + assert preview["future_apply_gate"]["writes_database"] is False + assert preview["safety"]["persists_receipt"] is False + + +def test_auto_policy_migration_file_preview_stays_no_write_without_fetch(): + preview = build_pchome_auto_policy_migration_file_preview(_payload(), batch_size=1) + + migration = preview["migration_file_preview"] + endpoint = preview["future_apply_endpoint_verifier"] + forward_sql = "\n".join(migration["forward_sql_preview"]) + assert preview["policy"] == "read_only_pchome_growth_auto_policy_migration_file_preview" + assert preview["result"] == "MIGRATION_FILE_PREVIEW_READY" + assert preview["summary"]["apply_endpoint_contract_ready_count"] == 0 + assert preview["summary"]["writes_file_count"] == 0 + assert preview["summary"]["executes_endpoint_count"] == 0 + assert preview["summary"]["writes_database_count"] == 0 + assert migration["migration_filename"] == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + assert migration["file_write_mode"] == "preview_only" + assert migration["forbidden_forward_tokens_absent"] is True + assert "Migration 045: PChome auto-policy evidence receipts" in forward_sql + assert "CREATE TABLE IF NOT EXISTS external_offer_evidence_receipts" in forward_sql + assert "GRANT ALL PRIVILEGES ON external_offer_evidence_receipts TO momo" in forward_sql + assert migration["writes_file"] is False + assert migration["executes_sql"] is False + assert endpoint["contract_status"] == "WAITING_FOR_APPLY_INPUTS" + assert endpoint["executes_endpoint"] is False + assert endpoint["writes_database"] is False + assert preview["safety"]["writes_file"] is False + assert preview["safety"]["writes_database"] is False + + +def test_auto_policy_migration_file_preview_builds_apply_endpoint_contract_after_fake_fetch(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + preview = build_pchome_auto_policy_migration_file_preview( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + endpoint = preview["future_apply_endpoint_verifier"] + assert preview["summary"]["dry_run_ready_count"] == 1 + assert preview["summary"]["apply_endpoint_contract_ready_count"] == 1 + assert preview["migration_file_preview"]["forward_sql_hash"] + assert endpoint["contract_status"] == "APPLY_ENDPOINT_CONTRACT_READY" + assert endpoint["request_contract"]["receipt_count"] == 1 + assert endpoint["request_contract"]["product_count"] == 1 + assert endpoint["request_contract"]["expected_migration_filename"] == ( + "migrations/045_pchome_auto_policy_evidence_receipts.sql" + ) + assert endpoint["request_contract"]["expected_migration_hash"] == ( + preview["migration_file_preview"]["forward_sql_hash"] + ) + assert endpoint["rollback_contract"]["uses_prewrite_snapshot"] is True + assert endpoint["post_write_verifier_contract"]["verifier_count"] == 5 + assert endpoint["manual_review_mode"] == "exception_only" + assert endpoint["executes_endpoint"] is False + assert endpoint["executes_sql"] is False + + +def test_auto_policy_apply_readiness_closeout_waits_for_ready_receipts_without_fetch(): + closeout = build_pchome_auto_policy_apply_readiness_closeout(_payload(), batch_size=1) + + waiting_keys = {check["key"] for check in closeout["closeout"]["waiting_checks"]} + assert closeout["policy"] == "read_only_pchome_growth_auto_policy_apply_readiness_closeout" + assert closeout["result"] == "APPLY_READINESS_WAITING_FOR_READY_RECEIPTS" + assert closeout["summary"]["readiness_check_count"] == 9 + assert closeout["summary"]["current_preview_ready_count"] == 0 + assert closeout["summary"]["future_apply_blocker_count"] == 4 + assert "ready_receipts_present" in waiting_keys + assert "future_apply_endpoint_contract_ready" in waiting_keys + assert closeout["closeout"]["ready_for_migration_file_generation_request"] is False + assert closeout["closeout"]["ready_for_database_apply"] is False + assert closeout["summary"]["writes_file_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["safety"]["writes_file"] is False + assert closeout["safety"]["writes_database"] is False + + +def test_auto_policy_apply_readiness_closeout_ready_after_fake_fetch_but_not_db_apply(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = build_pchome_auto_policy_apply_readiness_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + assert closeout["result"] == "APPLY_READINESS_CLOSEOUT_READY" + assert closeout["summary"]["readiness_pass_count"] == 9 + assert closeout["summary"]["readiness_waiting_count"] == 0 + assert closeout["summary"]["current_preview_ready_count"] == 1 + assert closeout["summary"]["future_apply_blocker_count"] == 4 + assert closeout["closeout"]["status"] == "READY_FOR_MIGRATION_FILE_GENERATION_REQUEST" + assert closeout["closeout"]["ready_for_migration_file_generation_request"] is True + assert closeout["closeout"]["ready_for_database_apply"] is False + assert closeout["migration_file_preview_summary"]["migration_filename"] == ( + "migrations/045_pchome_auto_policy_evidence_receipts.sql" + ) + assert closeout["future_apply_endpoint_summary"]["contract_status"] == "APPLY_ENDPOINT_CONTRACT_READY" + assert closeout["future_apply_endpoint_summary"]["receipt_count"] == 1 + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["writes_database"] is False + + +def test_auto_policy_migration_file_generation_request_waits_for_closeout_without_fetch(): + request_package = build_pchome_auto_policy_migration_file_generation_request(_payload(), batch_size=1) + + request = request_package["file_generation_request"] + assert request_package["policy"] == "read_only_pchome_growth_auto_policy_migration_file_generation_request" + assert request_package["result"] == "WAITING_FOR_APPLY_READINESS_CLOSEOUT" + assert request_package["summary"]["request_ready_count"] == 0 + assert request_package["summary"]["required_artifact_count"] == 4 + assert request_package["summary"]["file_generation_step_count"] == 3 + assert request["ready_to_generate_file"] is False + assert request["ready_for_database_apply"] is False + assert request["target_file"] == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + assert request["writes_file_in_preview"] is False + assert request_package["summary"]["writes_file_count"] == 0 + assert request_package["summary"]["executes_endpoint_count"] == 0 + assert request_package["summary"]["writes_database_count"] == 0 + assert request_package["safety"]["writes_file"] is False + assert request_package["safety"]["writes_database"] is False + + +def test_auto_policy_migration_file_generation_request_ready_after_fake_fetch_without_writing_file(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + request_package = build_pchome_auto_policy_migration_file_generation_request( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + request = request_package["file_generation_request"] + assert request_package["result"] == "FILE_GENERATION_REQUEST_READY" + assert request_package["summary"]["request_ready_count"] == 1 + assert request["request_id"].startswith("pchome-migration-file-request-") + assert request["ready_to_generate_file"] is True + assert request["ready_for_database_apply"] is False + assert request["expected_sha256"] + assert request["expected_line_count"] == 31 + assert request["file_generation_steps"][0]["content_source"] == "migration_file_preview.forward_sql_preview" + assert request_package["future_apply_endpoint_summary"]["contract_status"] == "APPLY_ENDPOINT_CONTRACT_READY" + assert request_package["future_apply_blockers"][0]["key"] == "migration_file_not_written" + assert request_package["safety"]["writes_file"] is False + assert request_package["safety"]["executes_sql"] is False + + +def test_generated_auto_policy_migration_file_matches_generation_request_hash(): + request_package = build_pchome_auto_policy_migration_file_generation_request(_payload(), batch_size=1) + request = request_package["file_generation_request"] + migration_path = ROOT / request["target_file"] + + assert migration_path.exists() + migration_text = migration_path.read_text(encoding="utf-8") + assert request["expected_sha256"] == hashlib.sha256(migration_text.encode("utf-8")).hexdigest() + assert migration_text.endswith("\n") + assert request_package["safety"]["writes_database"] is False + + +def test_auto_policy_migration_apply_gate_preview_reads_generated_file_without_db_apply(): + preview = build_pchome_auto_policy_migration_apply_gate_preview(_payload(), batch_size=1) + + assert preview["policy"] == "read_only_pchome_growth_auto_policy_migration_apply_gate_preview" + assert preview["result"] == "MIGRATION_APPLY_GATE_WAITING" + assert preview["summary"]["generated_file_exists_count"] == 1 + assert preview["summary"]["generated_file_hash_matches_count"] == 1 + assert preview["apply_gate"]["ready_for_database_apply_now"] is False + assert preview["generated_migration_file"]["exists"] is True + assert preview["generated_migration_file"]["target_file"] == ( + "migrations/045_pchome_auto_policy_evidence_receipts.sql" + ) + assert preview["generated_migration_file"]["sha256"] == preview["apply_gate"]["expected_sha256"] + assert preview["safety"]["executes_migration"] is False + assert preview["safety"]["writes_database"] is False + + +def test_auto_policy_migration_apply_gate_preview_ready_after_fake_fetch_but_still_no_db_apply(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + preview = build_pchome_auto_policy_migration_apply_gate_preview( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + assert preview["result"] == "MIGRATION_APPLY_GATE_PREVIEW_READY" + assert preview["summary"]["apply_gate_pass_count"] == 9 + assert preview["summary"]["apply_gate_waiting_count"] == 0 + assert preview["summary"]["apply_preview_ready_count"] == 1 + assert preview["summary"]["future_apply_blocker_count"] == 3 + assert preview["apply_gate"]["status"] == "READY_FOR_EXPLICIT_DB_APPLY_REQUEST" + assert preview["apply_gate"]["ready_for_explicit_db_apply_request"] is True + assert preview["apply_gate"]["ready_for_database_apply_now"] is False + assert preview["apply_gate"]["hash_matches"] is True + assert preview["future_apply_endpoint_summary"]["contract_status"] == "APPLY_ENDPOINT_CONTRACT_READY" + assert preview["future_apply_endpoint_summary"]["receipt_count"] == 1 + assert preview["safety"]["executes_sql"] is False + assert preview["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_request_gate_preview_waits_without_ready_receipts(): + preview = build_pchome_auto_policy_db_apply_request_gate_preview(_payload(), batch_size=1) + + gate = preview["db_apply_request_gate"] + assert preview["policy"] == "read_only_pchome_growth_auto_policy_db_apply_request_gate_preview" + assert preview["result"] == "WAITING_FOR_MIGRATION_APPLY_GATE_PREVIEW" + assert preview["summary"]["request_ready_count"] == 0 + assert preview["summary"]["required_artifact_count"] == 5 + assert preview["summary"]["apply_sequence_step_count"] == 5 + assert gate["ready_for_explicit_db_apply_request"] is False + assert gate["ready_for_database_apply_now"] is False + assert gate["command_preview"]["executes_in_preview"] is False + assert gate["command_preview"]["reads_secret_in_preview"] is False + assert gate["command_preview"]["writes_database"] is False + assert preview["rollback_gate_preview"]["writes_database"] is False + assert preview["safety"]["reads_secret_in_preview"] is False + assert preview["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_request_gate_preview_ready_after_fake_fetch_but_still_no_execution(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + preview = build_pchome_auto_policy_db_apply_request_gate_preview( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + gate = preview["db_apply_request_gate"] + assert preview["result"] == "DB_APPLY_REQUEST_GATE_READY" + assert preview["summary"]["request_ready_count"] == 1 + assert preview["summary"]["generated_file_hash_matches_count"] == 1 + assert gate["request_id"].startswith("pchome-db-apply-request-") + assert gate["ready_for_explicit_db_apply_request"] is True + assert gate["ready_for_database_apply_now"] is False + assert gate["hash_matches"] is True + assert gate["command_preview"]["command"].startswith('psql "$DATABASE_URL"') + assert gate["command_preview"]["uses_secret_placeholder"] is True + assert gate["apply_sequence_preview"][3]["name"] == "execute_migration" + assert preview["required_runtime_readback"] == [ + "fresh production /health", + "schema catalog readback for external_offer_evidence_receipts", + "index catalog readback", + "privilege readback", + "mapping backlog read-only smoke", + ] + assert preview["safety"]["executes_migration"] is False + assert preview["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_execution_preflight_waits_without_ready_request_gate(): + preflight = build_pchome_auto_policy_db_apply_execution_preflight(_payload(), batch_size=1) + + execution = preflight["execution_preflight"] + assert preflight["policy"] == "read_only_pchome_growth_auto_policy_db_apply_execution_preflight" + assert preflight["result"] == "WAITING_FOR_DB_APPLY_REQUEST_GATE" + assert preflight["summary"]["preflight_ready_count"] == 0 + assert preflight["summary"]["request_ready_count"] == 0 + assert preflight["summary"]["required_artifact_count"] == 6 + assert preflight["summary"]["snapshot_plan_count"] == 5 + assert preflight["summary"]["readback_plan_count"] == 6 + assert preflight["summary"]["rollback_artifact_count"] == 1 + assert preflight["summary"]["abort_condition_count"] == 8 + assert preflight["summary"]["reads_secret_count"] == 0 + assert execution["ready_for_preflight_artifact_generation"] is False + assert execution["ready_for_database_apply_now"] is False + assert execution["reads_secret_in_preview"] is False + assert preflight["prewrite_snapshot_plan"]["required"] is True + assert preflight["prewrite_snapshot_plan"]["executes_sql_in_preview"] is False + assert preflight["post_apply_readback_plan"]["readback_check_count"] == 6 + assert preflight["rollback_artifact_plan"]["uses_prewrite_snapshot"] is True + assert preflight["rollback_artifact_plan"]["writes_database"] is False + assert preflight["safety"]["reads_secret_in_preview"] is False + assert preflight["safety"]["executes_sql"] is False + assert preflight["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_execution_preflight_ready_after_fake_fetch_but_still_no_db_apply(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + preflight = build_pchome_auto_policy_db_apply_execution_preflight( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + execution = preflight["execution_preflight"] + readback_keys = [check["key"] for check in preflight["post_apply_readback_plan"]["readback_checks"]] + snapshot_keys = [step["key"] for step in preflight["prewrite_snapshot_plan"]["snapshot_steps"]] + assert preflight["result"] == "DB_APPLY_EXECUTION_PREFLIGHT_READY" + assert preflight["summary"]["preflight_ready_count"] == 1 + assert preflight["summary"]["request_ready_count"] == 1 + assert preflight["summary"]["generated_file_hash_matches_count"] == 1 + assert preflight["summary"]["executes_migration_count"] == 0 + assert preflight["summary"]["writes_database_count"] == 0 + assert execution["preflight_id"].startswith("pchome-db-apply-preflight-") + assert execution["source_request_id"].startswith("pchome-db-apply-request-") + assert execution["ready_for_preflight_artifact_generation"] is True + assert execution["ready_for_database_apply_now"] is False + assert execution["hash_matches"] is True + assert execution["operator_secret_boundary"] == "future_shell_only" + assert "schema_catalog_prewrite_snapshot" in snapshot_keys + assert "receipt_table_exists" in readback_keys + assert "mapping_backlog_read_only_smoke" in readback_keys + assert preflight["rollback_artifact_plan"]["artifacts"][0]["rollback_sql_preview"] == [ + "DROP TABLE IF EXISTS external_offer_evidence_receipts;" + ] + assert preflight["rollback_artifact_plan"]["artifacts"][0]["executes_sql_in_preview"] is False + assert preflight["safety"]["writes_artifact_in_preview"] is False + assert preflight["safety"]["executes_sql"] is False + assert preflight["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_package_waits_without_ready_preflight(): + package = build_pchome_auto_policy_db_apply_authorization_package(_payload(), batch_size=1) + + authorization = package["authorization_package"] + assert package["policy"] == "read_only_pchome_growth_auto_policy_db_apply_authorization_package" + assert package["result"] == "WAITING_FOR_DB_APPLY_EXECUTION_PREFLIGHT" + assert package["summary"]["authorization_check_count"] == 11 + assert package["summary"]["authorization_package_ready_count"] == 0 + assert package["summary"]["freshness_requirement_count"] == 5 + assert package["summary"]["manifest_step_count"] == 6 + assert package["summary"]["verifier_bundle_count"] == 3 + assert package["summary"]["reads_secret_count"] == 0 + assert package["summary"]["executes_sql_count"] == 0 + assert package["summary"]["writes_database_count"] == 0 + assert authorization["ready_for_explicit_apply_authorization_request"] is False + assert authorization["ready_for_database_apply_now"] is False + assert authorization["reads_secret_in_preview"] is False + assert package["machine_apply_manifest"]["executes_in_preview"] is False + assert package["machine_apply_manifest"]["writes_database"] is False + assert package["verifier_bundle"]["executes_in_preview"] is False + assert package["verifier_bundle"]["writes_database"] is False + assert package["safety"]["executes_sql"] is False + assert package["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_package_ready_after_fake_fetch_but_still_no_db_apply(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + package = build_pchome_auto_policy_db_apply_authorization_package( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + authorization = package["authorization_package"] + assert package["result"] == "DB_APPLY_AUTHORIZATION_PACKAGE_READY" + assert package["summary"]["authorization_check_count"] == 11 + assert package["summary"]["authorization_pass_count"] == 11 + assert package["summary"]["authorization_waiting_count"] == 0 + assert package["summary"]["authorization_package_ready_count"] == 1 + assert package["summary"]["required_artifact_count"] == 6 + assert package["summary"]["snapshot_plan_count"] == 5 + assert package["summary"]["readback_plan_count"] == 6 + assert package["summary"]["rollback_artifact_count"] == 1 + assert package["summary"]["executes_migration_count"] == 0 + assert package["summary"]["writes_database_count"] == 0 + assert authorization["package_id"].startswith("pchome-db-apply-authorization-") + assert authorization["source_preflight_id"].startswith("pchome-db-apply-preflight-") + assert authorization["source_request_id"].startswith("pchome-db-apply-request-") + assert authorization["ready_for_explicit_apply_authorization_request"] is True + assert authorization["ready_for_database_apply_now"] is False + assert authorization["freshness_window_seconds"] == 300 + assert authorization["operator_secret_boundary"] == "future_shell_only" + assert authorization["reads_secret_in_preview"] is False + assert authorization["executes_sql_in_preview"] is False + assert authorization["writes_database_in_preview"] is False + assert package["freshness_requirements"][0]["key"] == "production_truth_fresh_within_300_seconds" + assert package["machine_apply_manifest"]["manifest_step_count"] == 6 + assert package["machine_apply_manifest"]["manifest_steps"][3]["executes_in_preview"] is False + assert "receipt_table_exists" in package["verifier_bundle"]["post_apply_verifiers"] + assert package["safety"]["reads_secret_in_preview"] is False + assert package["safety"]["executes_sql"] is False + assert package["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_verifier_artifact_preview_waits_without_ready_authorization_package(): + preview = build_pchome_auto_policy_db_apply_verifier_artifact_preview(_payload(), batch_size=1) + + artifact_preview = preview["artifact_preview"] + assert preview["policy"] == "read_only_pchome_growth_auto_policy_db_apply_verifier_artifact_preview" + assert preview["result"] == "WAITING_FOR_DB_APPLY_AUTHORIZATION_PACKAGE" + assert preview["summary"]["artifact_preview_ready_count"] == 0 + assert preview["summary"]["artifact_schema_count"] == 3 + assert preview["summary"]["artifact_generation_step_count"] == 5 + assert preview["summary"]["verifier_check_count"] == 15 + assert preview["summary"]["writes_artifact_count"] == 0 + assert preview["summary"]["executes_sql_count"] == 0 + assert preview["summary"]["writes_database_count"] == 0 + assert artifact_preview["ready_for_future_artifact_generation"] is False + assert artifact_preview["ready_to_write_artifacts_now"] is False + assert artifact_preview["ready_for_database_apply_now"] is False + assert artifact_preview["writes_artifact_in_preview"] is False + assert preview["artifact_generation_plan"]["writes_artifact_in_preview"] is False + assert preview["verifier_manifest"]["executes_in_preview"] is False + assert preview["verifier_manifest"]["writes_database"] is False + assert preview["safety"]["writes_artifact_in_preview"] is False + assert preview["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_verifier_artifact_preview_ready_after_fake_fetch_but_no_artifact_write(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + preview = build_pchome_auto_policy_db_apply_verifier_artifact_preview( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + artifact_preview = preview["artifact_preview"] + artifact_keys = [schema["key"] for schema in preview["artifact_schemas"]] + assert preview["result"] == "DB_APPLY_VERIFIER_ARTIFACT_PREVIEW_READY" + assert preview["summary"]["artifact_preview_ready_count"] == 1 + assert preview["summary"]["authorization_package_ready_count"] == 1 + assert preview["summary"]["artifact_schema_count"] == 3 + assert preview["summary"]["artifact_generation_step_count"] == 5 + assert preview["summary"]["verifier_check_count"] == 15 + assert preview["summary"]["writes_artifact_count"] == 0 + assert preview["summary"]["writes_database_count"] == 0 + assert artifact_preview["preview_id"].startswith("pchome-db-apply-artifacts-") + assert artifact_preview["source_authorization_package_id"].startswith("pchome-db-apply-authorization-") + assert artifact_preview["ready_for_future_artifact_generation"] is True + assert artifact_preview["ready_to_write_artifacts_now"] is False + assert artifact_preview["ready_for_database_apply_now"] is False + assert artifact_preview["writes_artifact_in_preview"] is False + assert artifact_preview["executes_sql_in_preview"] is False + assert "prewrite_snapshot_artifact" in artifact_keys + assert "post_apply_readback_artifact" in artifact_keys + assert "rollback_artifact" in artifact_keys + assert preview["artifact_schemas"][2]["rollback_sql_preview"] == [ + "DROP TABLE IF EXISTS external_offer_evidence_receipts;" + ] + assert "receipt_table_exists" in preview["verifier_manifest"]["post_apply_checks"] + assert preview["artifact_generation_plan"]["generation_step_count"] == 5 + assert preview["safety"]["writes_artifact_in_preview"] is False + assert preview["safety"]["executes_sql"] is False + assert preview["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_final_handoff_package_waits_without_ready_artifact_preview(): + package = build_pchome_auto_policy_db_apply_final_handoff_package(_payload(), batch_size=1) + + handoff = package["final_handoff_package"] + assert package["policy"] == "read_only_pchome_growth_auto_policy_db_apply_final_handoff_package" + assert package["result"] == "WAITING_FOR_DB_APPLY_VERIFIER_ARTIFACT_PREVIEW" + assert package["summary"]["final_handoff_ready_count"] == 0 + assert package["summary"]["handoff_section_count"] == 6 + assert package["summary"]["final_runbook_step_count"] == 7 + assert package["summary"]["command_preview_count"] == 3 + assert package["summary"]["abort_gate_count"] == 10 + assert package["summary"]["source_endpoint_count"] == 4 + assert package["summary"]["writes_artifact_count"] == 0 + assert package["summary"]["executes_sql_count"] == 0 + assert package["summary"]["writes_database_count"] == 0 + assert handoff["ready_for_explicit_db_apply_handoff"] is False + assert handoff["ready_for_database_apply_now"] is False + assert handoff["reads_secret_in_preview"] is False + assert handoff["writes_artifact_in_preview"] is False + assert package["final_runbook_manifest"]["executes_in_preview"] is False + assert package["final_runbook_manifest"]["writes_database"] is False + assert package["command_previews"][1]["reads_secret_in_preview"] is False + assert package["safety"]["executes_sql"] is False + assert package["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_final_handoff_package_ready_after_fake_fetch_but_no_apply(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + package = build_pchome_auto_policy_db_apply_final_handoff_package( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + handoff = package["final_handoff_package"] + assert package["result"] == "DB_APPLY_FINAL_HANDOFF_PACKAGE_READY" + assert package["summary"]["final_handoff_ready_count"] == 1 + assert package["summary"]["artifact_preview_ready_count"] == 1 + assert package["summary"]["handoff_section_count"] == 6 + assert package["summary"]["final_runbook_step_count"] == 7 + assert package["summary"]["command_preview_count"] == 3 + assert package["summary"]["abort_gate_count"] == 10 + assert package["summary"]["source_endpoint_count"] == 4 + assert package["summary"]["artifact_schema_count"] == 3 + assert package["summary"]["verifier_check_count"] == 15 + assert package["summary"]["writes_artifact_count"] == 0 + assert package["summary"]["executes_sql_count"] == 0 + assert package["summary"]["writes_database_count"] == 0 + assert handoff["package_id"].startswith("pchome-db-apply-final-handoff-") + assert handoff["source_artifact_preview_id"].startswith("pchome-db-apply-artifacts-") + assert handoff["source_authorization_package_id"].startswith("pchome-db-apply-authorization-") + assert handoff["ready_for_explicit_db_apply_handoff"] is True + assert handoff["ready_for_database_apply_now"] is False + assert handoff["requires_separate_explicit_db_apply_authorization"] is True + assert handoff["operator_secret_boundary"] == "future_shell_only" + assert handoff["reads_secret_in_preview"] is False + assert handoff["executes_sql_in_preview"] is False + assert handoff["writes_database_in_preview"] is False + assert package["source_proof_manifest"]["source_endpoint_chain"][-1].endswith( + "auto-policy-db-apply-verifier-artifact-preview" + ) + assert package["command_previews"][1]["command"].startswith('psql "$DATABASE_URL"') + assert package["command_previews"][1]["executes_in_preview"] is False + assert package["final_runbook_manifest"]["step_count"] == 7 + assert package["safety"]["reads_secret_in_preview"] is False + assert package["safety"]["executes_sql"] is False + assert package["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_shell_preview_waits_without_ready_final_handoff(): + preview = build_pchome_auto_policy_db_apply_controlled_dry_run_shell_preview(_payload(), batch_size=1) + + shell = preview["controlled_dry_run_shell_preview"] + assert preview["policy"] == "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_shell_preview" + assert preview["result"] == "WAITING_FOR_DB_APPLY_FINAL_HANDOFF_PACKAGE" + assert preview["summary"]["dry_run_shell_preview_ready_count"] == 0 + assert preview["summary"]["shell_phase_count"] == 9 + assert preview["summary"]["shell_script_line_count"] == 10 + assert preview["summary"]["check_mode_required_check_count"] == 6 + assert preview["summary"]["rollback_hook_count"] == 3 + assert preview["summary"]["writes_script_count"] == 0 + assert preview["summary"]["executes_script_count"] == 0 + assert preview["summary"]["executes_sql_count"] == 0 + assert preview["summary"]["writes_database_count"] == 0 + assert shell["ready_for_future_shell_script_generation"] is False + assert shell["ready_to_write_script_now"] is False + assert shell["ready_to_execute_shell_now"] is False + assert shell["ready_for_database_apply_now"] is False + assert shell["reads_secret_in_preview"] is False + assert preview["shell_script_preview"]["writes_file_in_preview"] is False + assert preview["shell_script_preview"]["executes_script_in_preview"] is False + assert preview["check_mode_contract"]["dry_run_only"] is True + assert preview["rollback_hook_preview"]["writes_database"] is False + assert preview["safety"]["executes_script"] is False + assert preview["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_shell_preview_ready_after_fake_fetch_but_no_shell_execution(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + preview = build_pchome_auto_policy_db_apply_controlled_dry_run_shell_preview( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + shell = preview["controlled_dry_run_shell_preview"] + phase_names = [phase["name"] for phase in preview["shell_phases"]] + assert preview["result"] == "DB_APPLY_CONTROLLED_DRY_RUN_SHELL_PREVIEW_READY" + assert preview["summary"]["dry_run_shell_preview_ready_count"] == 1 + assert preview["summary"]["final_handoff_ready_count"] == 1 + assert preview["summary"]["shell_phase_count"] == 9 + assert preview["summary"]["shell_script_line_count"] == 10 + assert preview["summary"]["check_mode_required_check_count"] == 6 + assert preview["summary"]["rollback_hook_count"] == 3 + assert preview["summary"]["writes_script_count"] == 0 + assert preview["summary"]["executes_script_count"] == 0 + assert preview["summary"]["writes_database_count"] == 0 + assert shell["preview_id"].startswith("pchome-db-apply-dry-run-shell-") + assert shell["source_final_handoff_package_id"].startswith("pchome-db-apply-final-handoff-") + assert shell["ready_for_future_shell_script_generation"] is True + assert shell["ready_to_write_script_now"] is False + assert shell["ready_to_execute_shell_now"] is False + assert shell["ready_for_database_apply_now"] is False + assert shell["dry_run_only"] is True + assert shell["reads_secret_in_preview"] is False + assert shell["executes_sql_in_preview"] is False + assert shell["writes_database_in_preview"] is False + assert "render_database_apply_command_preview" in phase_names + assert preview["shell_script_preview"]["line_count"] == 10 + assert preview["shell_script_preview"]["executes_script_in_preview"] is False + assert any('psql "$DATABASE_URL"' in line for line in preview["shell_script_preview"]["lines"]) + assert preview["check_mode_contract"]["required_check_count"] == 6 + assert preview["rollback_hook_preview"]["hook_count"] == 3 + assert preview["safety"]["writes_script_in_preview"] is False + assert preview["safety"]["executes_script"] is False + assert preview["safety"]["executes_sql"] is False + assert preview["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_shell_closeout_waits_without_ready_preview(): + closeout = build_pchome_auto_policy_db_apply_controlled_dry_run_shell_closeout(_payload(), batch_size=1) + + boundary = closeout["explicit_authorization_boundary"] + assert closeout["policy"] == "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_shell_closeout" + assert closeout["result"] == "WAITING_FOR_CONTROLLED_DRY_RUN_SHELL_PREVIEW" + assert closeout["summary"]["closeout_ready_count"] == 0 + assert closeout["summary"]["closeout_check_count"] == 13 + assert closeout["summary"]["closeout_waiting_count"] > 0 + assert closeout["summary"]["future_apply_boundary_count"] == 6 + assert closeout["summary"]["writes_script_count"] == 0 + assert closeout["summary"]["executes_script_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert boundary["ready_for_explicit_apply_authorization_boundary"] is False + assert boundary["ready_for_database_apply_now"] is False + assert boundary["requires_new_explicit_db_apply_authorization"] is True + assert boundary["reads_secret_in_preview"] is False + assert boundary["executes_shell_in_preview"] is False + assert boundary["executes_sql_in_preview"] is False + assert closeout["controlled_dry_run_shell_closeout"]["ready_for_database_apply_now"] is False + assert closeout["safety"]["executes_script"] is False + assert closeout["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_shell_closeout_ready_after_fake_fetch_but_no_apply(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = build_pchome_auto_policy_db_apply_controlled_dry_run_shell_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + boundary = closeout["explicit_authorization_boundary"] + check_keys = [check["key"] for check in closeout["closeout_checks"]] + future_boundary_keys = [item["key"] for item in closeout["future_apply_boundaries"]] + assert closeout["result"] == "DB_APPLY_CONTROLLED_DRY_RUN_SHELL_CLOSEOUT_READY" + assert closeout["summary"]["closeout_ready_count"] == 1 + assert closeout["summary"]["closeout_check_count"] == 13 + assert closeout["summary"]["closeout_pass_count"] == 13 + assert closeout["summary"]["closeout_waiting_count"] == 0 + assert closeout["summary"]["dry_run_shell_preview_ready_count"] == 1 + assert closeout["summary"]["future_apply_boundary_count"] == 6 + assert closeout["summary"]["writes_script_count"] == 0 + assert closeout["summary"]["executes_script_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert boundary["boundary_id"].startswith("pchome-db-apply-dry-run-closeout-") + assert boundary["source_dry_run_shell_preview_id"].startswith("pchome-db-apply-dry-run-shell-") + assert boundary["ready_for_explicit_apply_authorization_boundary"] is True + assert boundary["ready_for_database_apply_now"] is False + assert boundary["requires_new_explicit_db_apply_authorization"] is True + assert boundary["operator_secret_boundary"] == "future_shell_only" + assert boundary["reads_secret_in_preview"] is False + assert boundary["executes_shell_in_preview"] is False + assert boundary["executes_sql_in_preview"] is False + assert "preview_executes_no_shell" in check_keys + assert "database_url_from_future_shell_only" in future_boundary_keys + assert closeout["controlled_dry_run_shell_closeout"]["waiting_checks"] == [] + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_script"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_request_intake_waits_without_ready_closeout(): + intake = build_pchome_auto_policy_db_apply_authorization_request_intake(_payload(), batch_size=1) + + request_intake = intake["authorization_request_intake"] + envelope = intake["authorization_envelope"] + assert intake["policy"] == "read_only_pchome_growth_auto_policy_db_apply_authorization_request_intake" + assert intake["result"] == "WAITING_FOR_CONTROLLED_DRY_RUN_SHELL_CLOSEOUT" + assert intake["summary"]["authorization_request_intake_ready_count"] == 0 + assert intake["summary"]["required_request_evidence_count"] == 7 + assert intake["summary"]["request_payload_required_field_count"] == 10 + assert intake["summary"]["authorization_acceptance_gate_count"] == 11 + assert intake["summary"]["rejection_reason_count"] == 10 + assert intake["summary"]["writes_script_count"] == 0 + assert intake["summary"]["reads_secret_count"] == 0 + assert intake["summary"]["executes_script_count"] == 0 + assert intake["summary"]["executes_sql_count"] == 0 + assert intake["summary"]["writes_database_count"] == 0 + assert request_intake["ready_for_authorization_request_intake"] is False + assert request_intake["ready_for_database_apply_now"] is False + assert request_intake["requires_new_explicit_db_apply_authorization"] is True + assert request_intake["reads_secret_in_preview"] is False + assert request_intake["executes_shell_in_preview"] is False + assert request_intake["executes_sql_in_preview"] is False + assert envelope["accepts_authorization_request"] is False + assert envelope["issues_database_apply_authorization"] is False + assert envelope["ready_for_database_apply_now"] is False + assert "operator_acknowledges_secret_boundary" in intake["request_payload_schema"]["required_fields"] + assert intake["request_payload_schema"]["accepts_database_url"] is False + assert intake["safety"]["executes_script"] is False + assert intake["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_request_intake_ready_after_fake_fetch_but_no_apply(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + intake = build_pchome_auto_policy_db_apply_authorization_request_intake( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + request_intake = intake["authorization_request_intake"] + envelope = intake["authorization_envelope"] + evidence_keys = [item["key"] for item in intake["required_request_evidence"]] + gate_keys = [item["key"] for item in intake["authorization_acceptance_gates"]] + assert intake["result"] == "DB_APPLY_AUTHORIZATION_REQUEST_INTAKE_READY" + assert intake["summary"]["authorization_request_intake_ready_count"] == 1 + assert intake["summary"]["required_request_evidence_count"] == 7 + assert intake["summary"]["request_payload_required_field_count"] == 10 + assert intake["summary"]["authorization_acceptance_gate_count"] == 11 + assert intake["summary"]["authorization_acceptance_pass_count"] == 11 + assert intake["summary"]["authorization_acceptance_waiting_count"] == 0 + assert intake["summary"]["rejection_reason_count"] == 10 + assert intake["summary"]["closeout_ready_count"] == 1 + assert intake["summary"]["future_apply_boundary_count"] == 6 + assert intake["summary"]["writes_script_count"] == 0 + assert intake["summary"]["reads_secret_count"] == 0 + assert intake["summary"]["executes_script_count"] == 0 + assert intake["summary"]["executes_sql_count"] == 0 + assert intake["summary"]["writes_database_count"] == 0 + assert request_intake["intake_id"].startswith("pchome-db-apply-authorization-intake-") + assert request_intake["source_closeout_boundary_id"].startswith("pchome-db-apply-dry-run-closeout-") + assert request_intake["source_dry_run_shell_preview_id"].startswith("pchome-db-apply-dry-run-shell-") + assert request_intake["ready_for_authorization_request_intake"] is True + assert request_intake["ready_for_database_apply_now"] is False + assert request_intake["request_scope"] == "future_explicit_db_apply_authorization_only" + assert request_intake["target_file"] == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + assert request_intake["hash_matches"] is True + assert request_intake["operator_secret_boundary"] == "future_shell_only" + assert request_intake["reads_secret_in_preview"] is False + assert request_intake["executes_shell_in_preview"] is False + assert request_intake["executes_sql_in_preview"] is False + assert request_intake["writes_database_in_preview"] is False + assert envelope["accepts_authorization_request"] is True + assert envelope["issues_database_apply_authorization"] is False + assert envelope["ready_for_database_apply_now"] is False + assert envelope["rejects_direct_database_apply"] is True + assert "migration_file_hash" in evidence_keys + assert "preview_has_no_side_effects" in gate_keys + assert "direct_database_apply_requested_from_intake" in intake["rejection_reasons"] + assert intake["request_payload_schema"]["accepts_database_url"] is False + assert intake["request_payload_schema"]["accepts_authorization_header"] is False + assert intake["safety"]["reads_secret_in_preview"] is False + assert intake["safety"]["executes_script"] is False + assert intake["safety"]["executes_sql"] is False + assert intake["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_request_closeout_waits_without_ready_intake(): + closeout = build_pchome_auto_policy_db_apply_authorization_request_closeout(_payload(), batch_size=1) + + package = closeout["final_exact_request_package"] + assert closeout["policy"] == "read_only_pchome_growth_auto_policy_db_apply_authorization_request_closeout" + assert closeout["result"] == "WAITING_FOR_DB_APPLY_AUTHORIZATION_REQUEST_INTAKE" + assert closeout["summary"]["authorization_request_closeout_ready_count"] == 0 + assert closeout["summary"]["closeout_check_count"] == 12 + assert closeout["summary"]["closeout_waiting_count"] > 0 + assert closeout["summary"]["exact_request_payload_field_count"] == 10 + assert closeout["summary"]["machine_request_manifest_step_count"] == 6 + assert closeout["summary"]["required_request_evidence_count"] == 7 + assert closeout["summary"]["authorization_acceptance_gate_count"] == 11 + assert closeout["summary"]["rejection_reason_count"] == 10 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_script_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert package["ready_for_exact_authorization_request_package"] is False + assert package["ready_for_database_apply_now"] is False + assert package["issues_database_apply_authorization"] is False + assert package["reads_secret_in_preview"] is False + assert package["executes_shell_in_preview"] is False + assert package["executes_sql_in_preview"] is False + assert closeout["machine_request_manifest"]["issues_database_apply_authorization"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_request_closeout_ready_after_fake_fetch_but_no_apply(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = build_pchome_auto_policy_db_apply_authorization_request_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + package = closeout["final_exact_request_package"] + manifest = closeout["machine_request_manifest"] + check_keys = [check["key"] for check in closeout["closeout_checks"]] + template = package["exact_request_payload_template"] + assert closeout["result"] == "DB_APPLY_AUTHORIZATION_REQUEST_CLOSEOUT_READY" + assert closeout["summary"]["authorization_request_closeout_ready_count"] == 1 + assert closeout["summary"]["closeout_check_count"] == 12 + assert closeout["summary"]["closeout_pass_count"] == 12 + assert closeout["summary"]["closeout_waiting_count"] == 0 + assert closeout["summary"]["authorization_request_intake_ready_count"] == 1 + assert closeout["summary"]["exact_request_payload_field_count"] == 10 + assert closeout["summary"]["machine_request_manifest_step_count"] == 6 + assert closeout["summary"]["required_request_evidence_count"] == 7 + assert closeout["summary"]["authorization_acceptance_gate_count"] == 11 + assert closeout["summary"]["rejection_reason_count"] == 10 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_script_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert package["package_id"].startswith("pchome-db-apply-authorization-closeout-") + assert package["source_intake_id"].startswith("pchome-db-apply-authorization-intake-") + assert package["ready_for_exact_authorization_request_package"] is True + assert package["ready_for_database_apply_now"] is False + assert package["issues_database_apply_authorization"] is False + assert package["request_scope"] == "future_explicit_db_apply_authorization_only" + assert package["target_file"] == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + assert package["hash_matches"] is True + assert package["payload_template_field_count"] == 10 + assert template["target_file"] == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + assert template["operator_acknowledges_secret_boundary"] is True + assert template["fresh_production_truth"]["same_run_only"] is True + assert package["reads_secret_in_preview"] is False + assert package["executes_shell_in_preview"] is False + assert package["executes_sql_in_preview"] is False + assert package["writes_database_in_preview"] is False + assert manifest["manifest_step_count"] == 6 + assert manifest["issues_database_apply_authorization"] is False + assert manifest["writes_database"] is False + assert "direct_apply_rejected" in check_keys + assert "preview_has_no_shell_sql_or_db_side_effect" in check_keys + assert closeout["authorization_request_closeout"]["waiting_checks"] == [] + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_script"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_lane_guard_waits_without_ready_closeout(): + guard = build_pchome_auto_policy_db_apply_authorization_lane_guard(_payload(), batch_size=1) + + lane = guard["future_authorization_lane_guard"] + contract = guard["lane_transfer_contract"] + assert guard["policy"] == "read_only_pchome_growth_auto_policy_db_apply_authorization_lane_guard" + assert guard["result"] == "WAITING_FOR_DB_APPLY_AUTHORIZATION_REQUEST_CLOSEOUT" + assert guard["summary"]["authorization_lane_guard_ready_count"] == 0 + assert guard["summary"]["lane_guard_check_count"] == 12 + assert guard["summary"]["lane_guard_waiting_count"] > 0 + assert guard["summary"]["lane_entry_requirement_count"] == 6 + assert guard["summary"]["exact_request_payload_field_count"] == 10 + assert guard["summary"]["machine_request_manifest_step_count"] == 6 + assert guard["summary"]["reads_secret_count"] == 0 + assert guard["summary"]["executes_script_count"] == 0 + assert guard["summary"]["executes_sql_count"] == 0 + assert guard["summary"]["writes_database_count"] == 0 + assert lane["ready_for_future_authorization_lane_entry"] is False + assert lane["ready_for_database_apply_now"] is False + assert lane["issues_database_apply_authorization"] is False + assert lane["reads_secret_in_preview"] is False + assert lane["executes_shell_in_preview"] is False + assert lane["executes_sql_in_preview"] is False + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["writes_database"] is False + assert guard["safety"]["executes_sql"] is False + assert guard["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_lane_guard_ready_after_fake_fetch_but_no_apply(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + guard = build_pchome_auto_policy_db_apply_authorization_lane_guard( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + lane = guard["future_authorization_lane_guard"] + contract = guard["lane_transfer_contract"] + check_keys = [check["key"] for check in guard["lane_guard_checks"]] + requirement_keys = [item["key"] for item in guard["lane_entry_requirements"]] + assert guard["result"] == "DB_APPLY_AUTHORIZATION_LANE_GUARD_READY" + assert guard["summary"]["authorization_lane_guard_ready_count"] == 1 + assert guard["summary"]["lane_guard_check_count"] == 12 + assert guard["summary"]["lane_guard_pass_count"] == 12 + assert guard["summary"]["lane_guard_waiting_count"] == 0 + assert guard["summary"]["authorization_request_closeout_ready_count"] == 1 + assert guard["summary"]["exact_request_payload_field_count"] == 10 + assert guard["summary"]["machine_request_manifest_step_count"] == 6 + assert guard["summary"]["lane_entry_requirement_count"] == 6 + assert guard["summary"]["required_request_evidence_count"] == 7 + assert guard["summary"]["authorization_acceptance_gate_count"] == 11 + assert guard["summary"]["rejection_reason_count"] == 10 + assert guard["summary"]["reads_secret_count"] == 0 + assert guard["summary"]["executes_script_count"] == 0 + assert guard["summary"]["executes_sql_count"] == 0 + assert guard["summary"]["writes_database_count"] == 0 + assert lane["guard_id"].startswith("pchome-db-apply-authorization-lane-") + assert lane["source_closeout_package_id"].startswith("pchome-db-apply-authorization-closeout-") + assert lane["ready_for_future_authorization_lane_entry"] is True + assert lane["ready_for_database_apply_now"] is False + assert lane["issues_database_apply_authorization"] is False + assert lane["request_scope"] == "future_explicit_db_apply_authorization_only" + assert lane["target_file"] == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + assert lane["hash_matches"] is True + assert lane["requires_fresh_production_truth_in_same_run"] is True + assert lane["operator_secret_boundary"] == "future_shell_only" + assert lane["reads_secret_in_preview"] is False + assert lane["executes_shell_in_preview"] is False + assert lane["executes_sql_in_preview"] is False + assert lane["writes_database_in_preview"] is False + assert contract["machine_verifiable"] is True + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["writes_database"] is False + assert "fresh_production_truth_same_run_required" in check_keys + assert "secret_rejection_step_present" in check_keys + assert "rollback_acknowledgement_present" in check_keys + assert "production_truth_refreshed_in_same_run" in requirement_keys + assert "direct_apply_rejected_until_next_lane" in requirement_keys + assert guard["safety"]["reads_secret_in_preview"] is False + assert guard["safety"]["executes_script"] is False + assert guard["safety"]["executes_sql"] is False + assert guard["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_decision_preflight_waits_without_ready_lane_guard(): + preflight = build_pchome_auto_policy_db_apply_authorization_decision_preflight(_payload(), batch_size=1) + + decision = preflight["future_authorization_decision_preflight"] + envelope = preflight["decision_preflight_envelope"] + assert preflight["policy"] == "read_only_pchome_growth_auto_policy_db_apply_authorization_decision_preflight" + assert preflight["result"] == "WAITING_FOR_DB_APPLY_AUTHORIZATION_LANE_GUARD" + assert preflight["summary"]["authorization_decision_preflight_ready_count"] == 0 + assert preflight["summary"]["decision_preflight_check_count"] == 12 + assert preflight["summary"]["decision_preflight_waiting_count"] > 0 + assert preflight["summary"]["decision_input_requirement_count"] == 8 + assert preflight["summary"]["decision_rejection_reason_count"] == 10 + assert preflight["summary"]["reads_secret_count"] == 0 + assert preflight["summary"]["executes_script_count"] == 0 + assert preflight["summary"]["executes_sql_count"] == 0 + assert preflight["summary"]["writes_database_count"] == 0 + assert decision["ready_for_future_authorization_decision"] is False + assert decision["can_enter_authorization_decision_lane"] is False + assert decision["ready_for_database_apply_now"] is False + assert decision["issues_database_apply_authorization"] is False + assert decision["reads_secret_in_preview"] is False + assert decision["executes_shell_in_preview"] is False + assert decision["executes_sql_in_preview"] is False + assert envelope["allows_authorization_decision_in_future_lane"] is False + assert envelope["issues_database_apply_authorization"] is False + assert envelope["ready_for_database_apply_now"] is False + assert envelope["requires_post_apply_verifier"] is True + assert preflight["safety"]["executes_sql"] is False + assert preflight["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_decision_preflight_ready_after_fake_fetch_but_no_apply(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + preflight = build_pchome_auto_policy_db_apply_authorization_decision_preflight( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + decision = preflight["future_authorization_decision_preflight"] + envelope = preflight["decision_preflight_envelope"] + check_keys = [check["key"] for check in preflight["decision_preflight_checks"]] + input_keys = [item["key"] for item in preflight["decision_input_requirements"]] + assert preflight["result"] == "DB_APPLY_AUTHORIZATION_DECISION_PREFLIGHT_READY" + assert preflight["summary"]["authorization_decision_preflight_ready_count"] == 1 + assert preflight["summary"]["decision_preflight_check_count"] == 12 + assert preflight["summary"]["decision_preflight_pass_count"] == 12 + assert preflight["summary"]["decision_preflight_waiting_count"] == 0 + assert preflight["summary"]["authorization_lane_guard_ready_count"] == 1 + assert preflight["summary"]["decision_input_requirement_count"] == 8 + assert preflight["summary"]["decision_rejection_reason_count"] == 10 + assert preflight["summary"]["lane_entry_requirement_count"] == 6 + assert preflight["summary"]["exact_request_payload_field_count"] == 10 + assert preflight["summary"]["machine_request_manifest_step_count"] == 6 + assert preflight["summary"]["reads_secret_count"] == 0 + assert preflight["summary"]["executes_script_count"] == 0 + assert preflight["summary"]["executes_sql_count"] == 0 + assert preflight["summary"]["writes_database_count"] == 0 + assert decision["preflight_id"].startswith("pchome-db-apply-authorization-decision-") + assert decision["source_lane_guard_id"].startswith("pchome-db-apply-authorization-lane-") + assert decision["ready_for_future_authorization_decision"] is True + assert decision["can_enter_authorization_decision_lane"] is True + assert decision["ready_for_database_apply_now"] is False + assert decision["issues_database_apply_authorization"] is False + assert decision["request_scope"] == "future_explicit_db_apply_authorization_only" + assert decision["target_file"] == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + assert decision["hash_matches"] is True + assert decision["requires_fresh_production_truth_in_same_run"] is True + assert decision["operator_secret_boundary"] == "future_shell_only" + assert decision["reads_secret_in_preview"] is False + assert decision["executes_shell_in_preview"] is False + assert decision["executes_sql_in_preview"] is False + assert decision["writes_database_in_preview"] is False + assert envelope["allows_authorization_decision_in_future_lane"] is True + assert envelope["issues_database_apply_authorization"] is False + assert envelope["ready_for_database_apply_now"] is False + assert envelope["rejects_direct_database_apply"] is True + assert envelope["requires_post_apply_verifier"] is True + assert "same_run_production_truth_required" in check_keys + assert "secret_boundary_rejects_secret_material" in check_keys + assert "post_apply_verifier_reference" in input_keys + assert "direct_database_apply_requested_from_decision_preflight" in preflight["decision_rejection_policy"] + assert preflight["safety"]["reads_secret_in_preview"] is False + assert preflight["safety"]["executes_script"] is False + assert preflight["safety"]["executes_sql"] is False + assert preflight["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_decision_closeout_waits_without_ready_preflight(): + closeout = build_pchome_auto_policy_db_apply_authorization_decision_closeout( + _payload(), + batch_size=1, + ) + + decision = closeout["future_authorization_decision_closeout"] + package = closeout["future_authorization_decision_package"] + contract = closeout["decision_closeout_contract"] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_decision_closeout" + ) + assert closeout["result"] == "WAITING_FOR_DB_APPLY_AUTHORIZATION_DECISION_PREFLIGHT" + assert closeout["summary"]["authorization_decision_closeout_ready_count"] == 0 + assert closeout["summary"]["decision_closeout_check_count"] == 12 + assert closeout["summary"]["decision_closeout_waiting_count"] > 0 + assert closeout["summary"]["authorization_decision_preflight_ready_count"] == 0 + assert closeout["summary"]["decision_input_requirement_count"] == 8 + assert closeout["summary"]["decision_rejection_reason_count"] == 10 + assert closeout["summary"]["post_apply_verifier_required_count"] == 1 + assert closeout["summary"]["same_run_truth_required_count"] == 1 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_script_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert decision["ready_for_future_authorization_decision_closeout"] is False + assert decision["ready_for_database_apply_now"] is False + assert decision["issues_database_apply_authorization"] is False + assert package["ready_for_future_authorization_decision_package"] is False + assert package["ready_for_database_apply_now"] is False + assert package["issues_database_apply_authorization"] is False + assert package["requires_post_apply_verifier"] is True + assert package["reads_secret_in_preview"] is False + assert package["writes_database_in_preview"] is False + assert contract["permits_future_authorization_decision_lane"] is False + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["writes_database"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_decision_closeout_ready_after_fake_fetch_but_no_apply(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = build_pchome_auto_policy_db_apply_authorization_decision_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + decision = closeout["future_authorization_decision_closeout"] + package = closeout["future_authorization_decision_package"] + contract = closeout["decision_closeout_contract"] + check_keys = [check["key"] for check in closeout["decision_closeout_checks"]] + assert closeout["result"] == "DB_APPLY_AUTHORIZATION_DECISION_CLOSEOUT_READY" + assert closeout["summary"]["authorization_decision_closeout_ready_count"] == 1 + assert closeout["summary"]["decision_closeout_check_count"] == 12 + assert closeout["summary"]["decision_closeout_pass_count"] == 12 + assert closeout["summary"]["decision_closeout_waiting_count"] == 0 + assert closeout["summary"]["authorization_decision_preflight_ready_count"] == 1 + assert closeout["summary"]["decision_input_requirement_count"] == 8 + assert closeout["summary"]["decision_rejection_reason_count"] == 10 + assert closeout["summary"]["post_apply_verifier_required_count"] == 1 + assert closeout["summary"]["same_run_truth_required_count"] == 1 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_script_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert decision["closeout_id"].startswith("pchome-db-apply-authorization-decision-closeout-") + assert decision["source_preflight_id"].startswith("pchome-db-apply-authorization-decision-") + assert decision["source_lane_guard_id"].startswith("pchome-db-apply-authorization-lane-") + assert decision["source_closeout_package_id"].startswith("pchome-db-apply-authorization-closeout-") + assert decision["ready_for_future_authorization_decision_closeout"] is True + assert decision["ready_for_database_apply_now"] is False + assert decision["issues_database_apply_authorization"] is False + assert package["package_id"].startswith("pchome-db-apply-authorization-decision-closeout-") + assert package["ready_for_future_authorization_decision_package"] is True + assert package["ready_for_database_apply_now"] is False + assert package["issues_database_apply_authorization"] is False + assert package["target_file"] == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + assert package["hash_matches"] is True + assert package["requires_fresh_production_truth_in_same_run"] is True + assert package["requires_post_apply_verifier"] is True + assert package["operator_secret_boundary"] == "future_shell_only" + assert package["reads_secret_in_preview"] is False + assert package["executes_shell_in_preview"] is False + assert package["executes_sql_in_preview"] is False + assert package["writes_database_in_preview"] is False + assert contract["permits_future_authorization_decision_lane"] is True + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["writes_database"] is False + assert "post_apply_verifier_required" in check_keys + assert "direct_apply_still_rejected" in check_keys + assert "manual_review_regression_absent" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_script"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_issuer_gate_waits_without_ready_closeout(): + gate = build_pchome_auto_policy_db_apply_authorization_issuer_gate( + _payload(), + batch_size=1, + ) + + issuer = gate["future_authorization_issuer_gate"] + envelope = gate["final_nonsecret_authorization_envelope"] + contract = gate["issuer_gate_contract"] + assert gate["policy"] == "read_only_pchome_growth_auto_policy_db_apply_authorization_issuer_gate" + assert gate["result"] == "WAITING_FOR_DB_APPLY_AUTHORIZATION_DECISION_CLOSEOUT" + assert gate["summary"]["authorization_issuer_gate_ready_count"] == 0 + assert gate["summary"]["issuer_gate_check_count"] == 12 + assert gate["summary"]["issuer_gate_waiting_count"] > 0 + assert gate["summary"]["authorization_decision_closeout_ready_count"] == 0 + assert gate["summary"]["decision_closeout_check_count"] == 12 + assert gate["summary"]["required_issuer_evidence_count"] == 9 + assert gate["summary"]["nonsecret_authorization_claim_count"] == 8 + assert gate["summary"]["post_apply_verifier_required_count"] == 1 + assert gate["summary"]["same_run_truth_required_count"] == 1 + assert gate["summary"]["reads_secret_count"] == 0 + assert gate["summary"]["executes_script_count"] == 0 + assert gate["summary"]["executes_sql_count"] == 0 + assert gate["summary"]["writes_database_count"] == 0 + assert issuer["ready_for_future_authorization_issuer_lane"] is False + assert issuer["ready_for_database_apply_now"] is False + assert issuer["issues_database_apply_authorization"] is False + assert issuer["signs_database_apply_authorization"] is False + assert envelope["authorization_material_type"] == "nonsecret_request_envelope" + assert envelope["ready_for_future_authorization_issuer_lane"] is False + assert envelope["ready_for_database_apply_now"] is False + assert envelope["issues_database_apply_authorization"] is False + assert envelope["signs_database_apply_authorization"] is False + assert envelope["secret_material_included"] is False + assert envelope["reads_secret_in_preview"] is False + assert envelope["writes_database_in_preview"] is False + assert contract["permits_future_authorization_issuer_lane"] is False + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert gate["safety"]["executes_sql"] is False + assert gate["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_issuer_gate_ready_after_fake_fetch_but_no_apply(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + gate = build_pchome_auto_policy_db_apply_authorization_issuer_gate( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + issuer = gate["future_authorization_issuer_gate"] + envelope = gate["final_nonsecret_authorization_envelope"] + contract = gate["issuer_gate_contract"] + check_keys = [check["key"] for check in gate["issuer_gate_checks"]] + evidence_keys = [item["key"] for item in envelope["required_issuer_evidence"]] + claim_keys = [item["key"] for item in envelope["nonsecret_authorization_claims"]] + assert gate["result"] == "DB_APPLY_AUTHORIZATION_ISSUER_GATE_READY" + assert gate["summary"]["authorization_issuer_gate_ready_count"] == 1 + assert gate["summary"]["issuer_gate_check_count"] == 12 + assert gate["summary"]["issuer_gate_pass_count"] == 12 + assert gate["summary"]["issuer_gate_waiting_count"] == 0 + assert gate["summary"]["authorization_decision_closeout_ready_count"] == 1 + assert gate["summary"]["decision_closeout_check_count"] == 12 + assert gate["summary"]["required_issuer_evidence_count"] == 9 + assert gate["summary"]["nonsecret_authorization_claim_count"] == 8 + assert gate["summary"]["post_apply_verifier_required_count"] == 1 + assert gate["summary"]["same_run_truth_required_count"] == 1 + assert gate["summary"]["decision_rejection_reason_count"] == 10 + assert gate["summary"]["reads_secret_count"] == 0 + assert gate["summary"]["executes_script_count"] == 0 + assert gate["summary"]["executes_sql_count"] == 0 + assert gate["summary"]["writes_database_count"] == 0 + assert issuer["gate_id"].startswith("pchome-db-apply-authorization-issuer-gate-") + assert issuer["source_decision_closeout_id"].startswith( + "pchome-db-apply-authorization-decision-closeout-" + ) + assert issuer["source_decision_preflight_id"].startswith("pchome-db-apply-authorization-decision-") + assert issuer["source_lane_guard_id"].startswith("pchome-db-apply-authorization-lane-") + assert issuer["ready_for_future_authorization_issuer_lane"] is True + assert issuer["ready_for_database_apply_now"] is False + assert issuer["issues_database_apply_authorization"] is False + assert issuer["signs_database_apply_authorization"] is False + assert envelope["envelope_id"].startswith("pchome-db-apply-authorization-issuer-gate-") + assert envelope["authorization_material_type"] == "nonsecret_request_envelope" + assert envelope["decision_scope"] == "future_explicit_db_apply_authorization_issuer_lane_only" + assert envelope["ready_for_future_authorization_issuer_lane"] is True + assert envelope["ready_for_database_apply_now"] is False + assert envelope["issues_database_apply_authorization"] is False + assert envelope["signs_database_apply_authorization"] is False + assert envelope["target_file"] == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + assert envelope["hash_matches"] is True + assert envelope["requires_fresh_production_truth_in_same_run"] is True + assert envelope["requires_post_apply_verifier"] is True + assert envelope["operator_secret_boundary"] == "future_shell_only" + assert envelope["secret_material_included"] is False + assert envelope["reads_secret_in_preview"] is False + assert envelope["executes_shell_in_preview"] is False + assert envelope["executes_sql_in_preview"] is False + assert envelope["writes_database_in_preview"] is False + assert contract["permits_future_authorization_issuer_lane"] is True + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert "issuer_policy_requires_future_explicit_authorization" in check_keys + assert "direct_apply_still_rejected" in check_keys + assert "secret_boundary_rejection" in evidence_keys + assert "no_database_apply_authorization_issued" in claim_keys + assert gate["safety"]["reads_secret_in_preview"] is False + assert gate["safety"]["executes_script"] is False + assert gate["safety"]["executes_sql"] is False + assert gate["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signing_decision_preflight_waits_without_ready_issuer_gate(): + preflight = build_pchome_auto_policy_db_apply_authorization_signing_decision_preflight( + _payload(), + batch_size=1, + ) + + decision = preflight["future_authorization_signing_decision_preflight"] + envelope = preflight["signing_decision_preflight_envelope"] + assert preflight["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signing_decision_preflight" + ) + assert preflight["result"] == "WAITING_FOR_DB_APPLY_AUTHORIZATION_ISSUER_GATE" + assert preflight["summary"]["authorization_signing_decision_preflight_ready_count"] == 0 + assert preflight["summary"]["signing_decision_preflight_check_count"] == 12 + assert preflight["summary"]["signing_decision_preflight_waiting_count"] > 0 + assert preflight["summary"]["authorization_issuer_gate_ready_count"] == 0 + assert preflight["summary"]["issuer_gate_check_count"] == 12 + assert preflight["summary"]["required_issuer_evidence_count"] == 9 + assert preflight["summary"]["nonsecret_authorization_claim_count"] == 8 + assert preflight["summary"]["signing_decision_input_requirement_count"] == 10 + assert preflight["summary"]["signing_decision_rejection_reason_count"] == 11 + assert preflight["summary"]["signs_database_apply_authorization_count"] == 0 + assert preflight["summary"]["reads_secret_count"] == 0 + assert preflight["summary"]["executes_script_count"] == 0 + assert preflight["summary"]["executes_sql_count"] == 0 + assert preflight["summary"]["writes_database_count"] == 0 + assert decision["ready_for_future_signing_decision_preflight"] is False + assert decision["can_enter_authorization_signing_decision_lane"] is False + assert decision["ready_for_database_apply_now"] is False + assert decision["issues_database_apply_authorization"] is False + assert decision["signs_database_apply_authorization"] is False + assert envelope["allows_future_authorization_signing_decision_lane"] is False + assert envelope["issues_database_apply_authorization"] is False + assert envelope["ready_for_database_apply_now"] is False + assert envelope["signs_database_apply_authorization"] is False + assert envelope["secret_material_required_in_preview"] is False + assert preflight["safety"]["signs_database_apply_authorization"] is False + assert preflight["safety"]["executes_sql"] is False + assert preflight["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signing_decision_preflight_ready_after_fake_fetch_but_no_apply(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + preflight = build_pchome_auto_policy_db_apply_authorization_signing_decision_preflight( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + decision = preflight["future_authorization_signing_decision_preflight"] + envelope = preflight["signing_decision_preflight_envelope"] + source_envelope = preflight["source_nonsecret_authorization_envelope"] + check_keys = [check["key"] for check in preflight["signing_decision_preflight_checks"]] + input_keys = [item["key"] for item in preflight["signing_decision_input_requirements"]] + assert preflight["result"] == "DB_APPLY_AUTHORIZATION_SIGNING_DECISION_PREFLIGHT_READY" + assert preflight["summary"]["authorization_signing_decision_preflight_ready_count"] == 1 + assert preflight["summary"]["signing_decision_preflight_check_count"] == 12 + assert preflight["summary"]["signing_decision_preflight_pass_count"] == 12 + assert preflight["summary"]["signing_decision_preflight_waiting_count"] == 0 + assert preflight["summary"]["authorization_issuer_gate_ready_count"] == 1 + assert preflight["summary"]["issuer_gate_check_count"] == 12 + assert preflight["summary"]["required_issuer_evidence_count"] == 9 + assert preflight["summary"]["nonsecret_authorization_claim_count"] == 8 + assert preflight["summary"]["signing_decision_input_requirement_count"] == 10 + assert preflight["summary"]["signing_decision_rejection_reason_count"] == 11 + assert preflight["summary"]["post_apply_verifier_required_count"] == 1 + assert preflight["summary"]["same_run_truth_required_count"] == 1 + assert preflight["summary"]["signs_database_apply_authorization_count"] == 0 + assert preflight["summary"]["reads_secret_count"] == 0 + assert preflight["summary"]["executes_script_count"] == 0 + assert preflight["summary"]["executes_sql_count"] == 0 + assert preflight["summary"]["writes_database_count"] == 0 + assert decision["preflight_id"].startswith("pchome-db-apply-authorization-signing-preflight-") + assert decision["source_issuer_gate_id"].startswith("pchome-db-apply-authorization-issuer-gate-") + assert decision["source_decision_closeout_id"].startswith( + "pchome-db-apply-authorization-decision-closeout-" + ) + assert decision["ready_for_future_signing_decision_preflight"] is True + assert decision["can_enter_authorization_signing_decision_lane"] is True + assert decision["ready_for_database_apply_now"] is False + assert decision["issues_database_apply_authorization"] is False + assert decision["signs_database_apply_authorization"] is False + assert envelope["allows_future_authorization_signing_decision_lane"] is True + assert envelope["issues_database_apply_authorization"] is False + assert envelope["ready_for_database_apply_now"] is False + assert envelope["signs_database_apply_authorization"] is False + assert envelope["rejects_direct_database_apply"] is True + assert envelope["requires_post_apply_verifier"] is True + assert envelope["secret_material_required_in_preview"] is False + assert source_envelope["authorization_material_type"] == "nonsecret_request_envelope" + assert source_envelope["secret_material_included"] is False + assert source_envelope["signs_database_apply_authorization"] is False + assert "nonsecret_envelope_complete" in check_keys + assert "signing_and_direct_apply_still_rejected" in check_keys + assert "no_signing_without_future_explicit_authorization" in input_keys + assert "authorization_signing_requested_from_preflight" in preflight["signing_decision_rejection_policy"] + assert preflight["safety"]["reads_secret_in_preview"] is False + assert preflight["safety"]["signs_database_apply_authorization"] is False + assert preflight["safety"]["executes_script"] is False + assert preflight["safety"]["executes_sql"] is False + assert preflight["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signing_decision_closeout_waits_without_ready_preflight(): + closeout = build_pchome_auto_policy_db_apply_authorization_signing_decision_closeout( + _payload(), + batch_size=1, + ) + + decision = closeout["future_authorization_signing_decision_closeout"] + package = closeout["unsigned_signing_decision_package"] + contract = closeout["signing_decision_closeout_contract"] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signing_decision_closeout" + ) + assert closeout["result"] == "WAITING_FOR_DB_APPLY_AUTHORIZATION_SIGNING_DECISION_PREFLIGHT" + assert closeout["summary"]["authorization_signing_decision_closeout_ready_count"] == 0 + assert closeout["summary"]["signing_decision_closeout_check_count"] == 12 + assert closeout["summary"]["signing_decision_closeout_waiting_count"] > 0 + assert closeout["summary"]["authorization_signing_decision_preflight_ready_count"] == 0 + assert closeout["summary"]["signing_decision_preflight_check_count"] == 12 + assert closeout["summary"]["signing_decision_input_requirement_count"] == 10 + assert closeout["summary"]["signing_decision_rejection_reason_count"] == 11 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_script_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert decision["ready_for_future_signing_decision_closeout"] is False + assert decision["can_enter_unsigned_signing_decision_package_lane"] is False + assert decision["ready_for_database_apply_now"] is False + assert decision["issues_database_apply_authorization"] is False + assert decision["signs_database_apply_authorization"] is False + assert package["authorization_material_type"] == "unsigned_signing_decision_package" + assert package["ready_for_future_unsigned_signing_decision_package"] is False + assert package["ready_for_database_apply_now"] is False + assert package["issues_database_apply_authorization"] is False + assert package["signs_database_apply_authorization"] is False + assert package["secret_material_included"] is False + assert package["secret_material_required_in_preview"] is False + assert package["reads_secret_in_preview"] is False + assert package["writes_database_in_preview"] is False + assert contract["permits_future_unsigned_signing_decision_package_lane"] is False + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert closeout["safety"]["signs_database_apply_authorization"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signing_decision_closeout_ready_after_fake_fetch_but_no_apply(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = build_pchome_auto_policy_db_apply_authorization_signing_decision_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + decision = closeout["future_authorization_signing_decision_closeout"] + package = closeout["unsigned_signing_decision_package"] + contract = closeout["signing_decision_closeout_contract"] + check_keys = [check["key"] for check in closeout["signing_decision_closeout_checks"]] + assert closeout["result"] == "DB_APPLY_AUTHORIZATION_SIGNING_DECISION_CLOSEOUT_READY" + assert closeout["summary"]["authorization_signing_decision_closeout_ready_count"] == 1 + assert closeout["summary"]["signing_decision_closeout_check_count"] == 12 + assert closeout["summary"]["signing_decision_closeout_pass_count"] == 12 + assert closeout["summary"]["signing_decision_closeout_waiting_count"] == 0 + assert closeout["summary"]["authorization_signing_decision_preflight_ready_count"] == 1 + assert closeout["summary"]["signing_decision_preflight_check_count"] == 12 + assert closeout["summary"]["signing_decision_input_requirement_count"] == 10 + assert closeout["summary"]["signing_decision_rejection_reason_count"] == 11 + assert closeout["summary"]["required_issuer_evidence_count"] == 9 + assert closeout["summary"]["nonsecret_authorization_claim_count"] == 8 + assert closeout["summary"]["post_apply_verifier_required_count"] == 1 + assert closeout["summary"]["same_run_truth_required_count"] == 1 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_script_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert decision["closeout_id"].startswith("pchome-db-apply-authorization-signing-closeout-") + assert decision["source_signing_decision_preflight_id"].startswith( + "pchome-db-apply-authorization-signing-preflight-" + ) + assert decision["source_issuer_gate_id"].startswith("pchome-db-apply-authorization-issuer-gate-") + assert decision["ready_for_future_signing_decision_closeout"] is True + assert decision["can_enter_unsigned_signing_decision_package_lane"] is True + assert decision["ready_for_database_apply_now"] is False + assert decision["issues_database_apply_authorization"] is False + assert decision["signs_database_apply_authorization"] is False + assert package["package_id"].startswith("pchome-db-apply-authorization-signing-closeout-") + assert package["authorization_material_type"] == "unsigned_signing_decision_package" + assert package["ready_for_future_unsigned_signing_decision_package"] is True + assert package["ready_for_database_apply_now"] is False + assert package["issues_database_apply_authorization"] is False + assert package["signs_database_apply_authorization"] is False + assert package["target_file"] == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + assert package["hash_matches"] is True + assert package["requires_fresh_production_truth_in_same_run"] is True + assert package["requires_post_apply_verifier"] is True + assert package["operator_secret_boundary"] == "future_shell_only" + assert package["secret_material_included"] is False + assert package["secret_material_required_in_preview"] is False + assert package["reads_secret_in_preview"] is False + assert package["executes_shell_in_preview"] is False + assert package["executes_sql_in_preview"] is False + assert package["writes_database_in_preview"] is False + assert contract["permits_future_unsigned_signing_decision_package_lane"] is True + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert "unsigned_package_source_envelope_complete" in check_keys + assert "preview_has_no_side_effects_and_no_signing" in check_keys + assert "manual_review_regression_absent" in check_keys + assert "authorization_signing_requested_from_preflight" in package[ + "signing_decision_rejection_policy" + ] + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["signs_database_apply_authorization"] is False + assert closeout["safety"]["executes_script"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signing_issuer_guard_waits_without_ready_closeout(): + guard = build_pchome_auto_policy_db_apply_authorization_signing_issuer_guard( + _payload(), + batch_size=1, + ) + + issuer_guard = guard["future_authorization_signing_issuer_guard"] + boundary = guard["signable_request_boundary"] + contract = guard["signing_issuer_guard_contract"] + assert guard["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signing_issuer_guard" + ) + assert guard["result"] == "WAITING_FOR_DB_APPLY_AUTHORIZATION_SIGNING_DECISION_CLOSEOUT" + assert guard["summary"]["authorization_signing_issuer_guard_ready_count"] == 0 + assert guard["summary"]["signing_issuer_guard_check_count"] == 12 + assert guard["summary"]["signing_issuer_guard_waiting_count"] > 0 + assert guard["summary"]["authorization_signing_decision_closeout_ready_count"] == 0 + assert guard["summary"]["signing_decision_closeout_check_count"] == 12 + assert guard["summary"]["signing_decision_input_requirement_count"] == 10 + assert guard["summary"]["signing_decision_rejection_reason_count"] == 11 + assert guard["summary"]["signs_database_apply_authorization_count"] == 0 + assert guard["summary"]["reads_secret_count"] == 0 + assert guard["summary"]["executes_script_count"] == 0 + assert guard["summary"]["executes_sql_count"] == 0 + assert guard["summary"]["writes_database_count"] == 0 + assert issuer_guard["ready_for_future_signing_issuer_guard"] is False + assert issuer_guard["can_enter_future_authorization_signing_issuer_lane"] is False + assert issuer_guard["ready_for_database_apply_now"] is False + assert issuer_guard["issues_database_apply_authorization"] is False + assert issuer_guard["signs_database_apply_authorization"] is False + assert boundary["request_boundary_type"] == "future_signable_request_boundary" + assert boundary["ready_for_future_signable_request_boundary"] is False + assert boundary["can_enter_future_authorization_signing_issuer_lane"] is False + assert boundary["ready_for_database_apply_now"] is False + assert boundary["issues_database_apply_authorization"] is False + assert boundary["signs_database_apply_authorization"] is False + assert boundary["secret_material_included"] is False + assert boundary["secret_material_required_in_preview"] is False + assert boundary["reads_secret_in_preview"] is False + assert boundary["writes_database_in_preview"] is False + assert contract["permits_future_authorization_signing_issuer_lane"] is False + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert guard["safety"]["signs_database_apply_authorization"] is False + assert guard["safety"]["executes_sql"] is False + assert guard["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signing_issuer_guard_ready_after_fake_fetch_but_no_apply(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + guard = build_pchome_auto_policy_db_apply_authorization_signing_issuer_guard( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + issuer_guard = guard["future_authorization_signing_issuer_guard"] + boundary = guard["signable_request_boundary"] + contract = guard["signing_issuer_guard_contract"] + check_keys = [check["key"] for check in guard["signing_issuer_guard_checks"]] + assert guard["result"] == "DB_APPLY_AUTHORIZATION_SIGNING_ISSUER_GUARD_READY" + assert guard["summary"]["authorization_signing_issuer_guard_ready_count"] == 1 + assert guard["summary"]["signing_issuer_guard_check_count"] == 12 + assert guard["summary"]["signing_issuer_guard_pass_count"] == 12 + assert guard["summary"]["signing_issuer_guard_waiting_count"] == 0 + assert guard["summary"]["authorization_signing_decision_closeout_ready_count"] == 1 + assert guard["summary"]["signing_decision_closeout_check_count"] == 12 + assert guard["summary"]["signing_decision_input_requirement_count"] == 10 + assert guard["summary"]["signing_decision_rejection_reason_count"] == 11 + assert guard["summary"]["required_issuer_evidence_count"] == 9 + assert guard["summary"]["nonsecret_authorization_claim_count"] == 8 + assert guard["summary"]["post_apply_verifier_required_count"] == 1 + assert guard["summary"]["same_run_truth_required_count"] == 1 + assert guard["summary"]["signs_database_apply_authorization_count"] == 0 + assert guard["summary"]["reads_secret_count"] == 0 + assert guard["summary"]["executes_script_count"] == 0 + assert guard["summary"]["executes_sql_count"] == 0 + assert guard["summary"]["writes_database_count"] == 0 + assert issuer_guard["guard_id"].startswith("pchome-db-apply-authorization-signing-issuer-") + assert issuer_guard["source_signing_decision_closeout_id"].startswith( + "pchome-db-apply-authorization-signing-closeout-" + ) + assert issuer_guard["source_signing_decision_preflight_id"].startswith( + "pchome-db-apply-authorization-signing-preflight-" + ) + assert issuer_guard["ready_for_future_signing_issuer_guard"] is True + assert issuer_guard["can_enter_future_authorization_signing_issuer_lane"] is True + assert issuer_guard["ready_for_database_apply_now"] is False + assert issuer_guard["issues_database_apply_authorization"] is False + assert issuer_guard["signs_database_apply_authorization"] is False + assert boundary["boundary_id"].startswith("pchome-db-apply-authorization-signing-issuer-") + assert boundary["request_boundary_type"] == "future_signable_request_boundary" + assert boundary["ready_for_future_signable_request_boundary"] is True + assert boundary["can_enter_future_authorization_signing_issuer_lane"] is True + assert boundary["ready_for_database_apply_now"] is False + assert boundary["issues_database_apply_authorization"] is False + assert boundary["signs_database_apply_authorization"] is False + assert boundary["target_file"] == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + assert boundary["hash_matches"] is True + assert boundary["requires_fresh_production_truth_in_same_run"] is True + assert boundary["requires_post_apply_verifier"] is True + assert boundary["operator_secret_boundary"] == "future_shell_only" + assert boundary["secret_material_included"] is False + assert boundary["secret_material_required_in_preview"] is False + assert boundary["reads_secret_in_preview"] is False + assert boundary["executes_shell_in_preview"] is False + assert boundary["executes_sql_in_preview"] is False + assert boundary["writes_database_in_preview"] is False + assert contract["permits_future_authorization_signing_issuer_lane"] is True + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert "signable_boundary_is_future_only" in check_keys + assert "preview_has_no_side_effects_and_no_signing" in check_keys + assert guard["safety"]["reads_secret_in_preview"] is False + assert guard["safety"]["signs_database_apply_authorization"] is False + assert guard["safety"]["executes_script"] is False + assert guard["safety"]["executes_sql"] is False + assert guard["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signing_issuer_closeout_waits_without_ready_guard(): + closeout = build_pchome_auto_policy_db_apply_authorization_signing_issuer_closeout( + _payload(), + batch_size=1, + ) + + issuer_closeout = closeout["future_authorization_signing_issuer_closeout"] + final_package = closeout["final_signable_request_package"] + contract = closeout["signing_issuer_closeout_contract"] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signing_issuer_closeout" + ) + assert closeout["result"] == "WAITING_FOR_DB_APPLY_AUTHORIZATION_SIGNING_ISSUER_GUARD" + assert closeout["summary"]["authorization_signing_issuer_closeout_ready_count"] == 0 + assert closeout["summary"]["signing_issuer_closeout_check_count"] == 12 + assert closeout["summary"]["signing_issuer_closeout_waiting_count"] > 0 + assert closeout["summary"]["authorization_signing_issuer_guard_ready_count"] == 0 + assert closeout["summary"]["signing_issuer_guard_check_count"] == 12 + assert closeout["summary"]["signing_decision_input_requirement_count"] == 10 + assert closeout["summary"]["signing_decision_rejection_reason_count"] == 11 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_script_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert issuer_closeout["ready_for_future_signing_issuer_closeout"] is False + assert issuer_closeout["can_enter_future_final_signable_request_package_lane"] is False + assert issuer_closeout["ready_for_database_apply_now"] is False + assert issuer_closeout["issues_database_apply_authorization"] is False + assert issuer_closeout["signs_database_apply_authorization"] is False + assert final_package["authorization_material_type"] == "final_signable_request_package" + assert final_package["ready_for_future_final_signable_request_package"] is False + assert final_package["ready_for_database_apply_now"] is False + assert final_package["issues_database_apply_authorization"] is False + assert final_package["signs_database_apply_authorization"] is False + assert final_package["secret_material_included"] is False + assert final_package["secret_material_required_in_preview"] is False + assert final_package["reads_secret_in_preview"] is False + assert contract["permits_future_final_signable_request_package_lane"] is False + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert closeout["safety"]["signs_database_apply_authorization"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signing_issuer_closeout_ready_after_fake_fetch_but_no_apply(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = build_pchome_auto_policy_db_apply_authorization_signing_issuer_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + issuer_closeout = closeout["future_authorization_signing_issuer_closeout"] + final_package = closeout["final_signable_request_package"] + contract = closeout["signing_issuer_closeout_contract"] + check_keys = [check["key"] for check in closeout["signing_issuer_closeout_checks"]] + assert closeout["result"] == "DB_APPLY_AUTHORIZATION_SIGNING_ISSUER_CLOSEOUT_READY" + assert closeout["summary"]["authorization_signing_issuer_closeout_ready_count"] == 1 + assert closeout["summary"]["signing_issuer_closeout_check_count"] == 12 + assert closeout["summary"]["signing_issuer_closeout_pass_count"] == 12 + assert closeout["summary"]["signing_issuer_closeout_waiting_count"] == 0 + assert closeout["summary"]["authorization_signing_issuer_guard_ready_count"] == 1 + assert closeout["summary"]["signing_issuer_guard_check_count"] == 12 + assert closeout["summary"]["signing_decision_input_requirement_count"] == 10 + assert closeout["summary"]["signing_decision_rejection_reason_count"] == 11 + assert closeout["summary"]["required_issuer_evidence_count"] == 9 + assert closeout["summary"]["nonsecret_authorization_claim_count"] == 8 + assert closeout["summary"]["post_apply_verifier_required_count"] == 1 + assert closeout["summary"]["same_run_truth_required_count"] == 1 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_script_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert issuer_closeout["closeout_id"].startswith( + "pchome-db-apply-authorization-signing-issuer-closeout-" + ) + assert issuer_closeout["source_signing_issuer_guard_id"].startswith( + "pchome-db-apply-authorization-signing-issuer-" + ) + assert issuer_closeout["ready_for_future_signing_issuer_closeout"] is True + assert issuer_closeout["can_enter_future_final_signable_request_package_lane"] is True + assert issuer_closeout["ready_for_database_apply_now"] is False + assert issuer_closeout["issues_database_apply_authorization"] is False + assert issuer_closeout["signs_database_apply_authorization"] is False + assert final_package["package_id"].startswith( + "pchome-db-apply-authorization-signing-issuer-closeout-" + ) + assert final_package["authorization_material_type"] == "final_signable_request_package" + assert final_package["ready_for_future_final_signable_request_package"] is True + assert final_package["ready_for_database_apply_now"] is False + assert final_package["issues_database_apply_authorization"] is False + assert final_package["signs_database_apply_authorization"] is False + assert final_package["target_file"] == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + assert final_package["hash_matches"] is True + assert final_package["requires_fresh_production_truth_in_same_run"] is True + assert final_package["requires_post_apply_verifier"] is True + assert final_package["operator_secret_boundary"] == "future_shell_only" + assert final_package["secret_material_included"] is False + assert final_package["secret_material_required_in_preview"] is False + assert final_package["reads_secret_in_preview"] is False + assert final_package["executes_shell_in_preview"] is False + assert final_package["executes_sql_in_preview"] is False + assert final_package["writes_database_in_preview"] is False + assert contract["permits_future_final_signable_request_package_lane"] is True + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert "final_package_does_not_authorize_sign_or_apply" in check_keys + assert "preview_has_no_side_effects_and_no_signing" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["signs_database_apply_authorization"] is False + assert closeout["safety"]["executes_script"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signing_execution_preflight_waits_without_ready_closeout(): + preflight = build_pchome_auto_policy_db_apply_authorization_signing_execution_preflight( + _payload(), + batch_size=1, + ) + + future_preflight = preflight["future_authorization_signing_execution_preflight"] + package = preflight["signing_execution_preflight_package"] + boundary = preflight["operator_held_secret_boundary_contract"] + contract = preflight["signing_execution_preflight_contract"] + assert preflight["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signing_execution_preflight" + ) + assert preflight["result"] == "WAITING_FOR_DB_APPLY_AUTHORIZATION_SIGNING_ISSUER_CLOSEOUT" + assert preflight["summary"]["authorization_signing_execution_preflight_ready_count"] == 0 + assert preflight["summary"]["signing_execution_preflight_check_count"] == 12 + assert preflight["summary"]["signing_execution_preflight_waiting_count"] > 0 + assert preflight["summary"]["authorization_signing_issuer_closeout_ready_count"] == 0 + assert preflight["summary"]["signing_issuer_closeout_check_count"] == 12 + assert preflight["summary"]["final_signable_request_package_ready_count"] == 0 + assert preflight["summary"]["operator_held_secret_boundary_count"] == 1 + assert preflight["summary"]["signing_execution_input_requirement_count"] == 10 + assert preflight["summary"]["signing_execution_abort_condition_count"] == 8 + assert preflight["summary"]["rollback_boundary_count"] == 4 + assert preflight["summary"]["signs_database_apply_authorization_count"] == 0 + assert preflight["summary"]["reads_secret_count"] == 0 + assert preflight["summary"]["executes_script_count"] == 0 + assert preflight["summary"]["executes_sql_count"] == 0 + assert preflight["summary"]["writes_database_count"] == 0 + assert future_preflight["ready_for_future_signing_execution_preflight"] is False + assert future_preflight["can_enter_future_authorization_signing_execution_lane"] is False + assert future_preflight["ready_for_database_apply_now"] is False + assert future_preflight["issues_database_apply_authorization"] is False + assert future_preflight["signs_database_apply_authorization"] is False + assert future_preflight["secret_material_included"] is False + assert future_preflight["secret_material_required_in_preview"] is False + assert future_preflight["reads_secret_in_preview"] is False + assert package["authorization_material_type"] == "signing_execution_preflight_package" + assert package["required_nonsecret_input_count"] == 10 + assert package["ready_for_database_apply_now"] is False + assert package["issues_database_apply_authorization"] is False + assert package["signs_database_apply_authorization"] is False + assert package["secret_material_included"] is False + assert package["secret_material_required_in_preview"] is False + assert package["reads_secret_in_preview"] is False + assert package["executes_shell_in_preview"] is False + assert package["executes_sql_in_preview"] is False + assert package["writes_database_in_preview"] is False + assert boundary["secret_reference_mode"] == "external_runtime_reference_only" + assert boundary["secret_material_included"] is False + assert boundary["secret_material_required_in_preview"] is False + assert boundary["reads_secret_in_preview"] is False + assert boundary["accepts_plaintext_secret"] is False + assert boundary["permits_secret_value_logging"] is False + assert contract["permits_future_explicit_authorization_signing_execution_lane"] is False + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert preflight["safety"]["reads_secret_in_preview"] is False + assert preflight["safety"]["signs_database_apply_authorization"] is False + assert preflight["safety"]["executes_sql"] is False + assert preflight["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signing_execution_preflight_ready_after_fake_fetch_but_no_signing(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + preflight = build_pchome_auto_policy_db_apply_authorization_signing_execution_preflight( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + future_preflight = preflight["future_authorization_signing_execution_preflight"] + package = preflight["signing_execution_preflight_package"] + boundary = preflight["operator_held_secret_boundary_contract"] + contract = preflight["signing_execution_preflight_contract"] + check_keys = [check["key"] for check in preflight["signing_execution_preflight_checks"]] + assert preflight["result"] == "DB_APPLY_AUTHORIZATION_SIGNING_EXECUTION_PREFLIGHT_READY" + assert preflight["summary"]["authorization_signing_execution_preflight_ready_count"] == 1 + assert preflight["summary"]["signing_execution_preflight_check_count"] == 12 + assert preflight["summary"]["signing_execution_preflight_pass_count"] == 12 + assert preflight["summary"]["signing_execution_preflight_waiting_count"] == 0 + assert preflight["summary"]["authorization_signing_issuer_closeout_ready_count"] == 1 + assert preflight["summary"]["signing_issuer_closeout_check_count"] == 12 + assert preflight["summary"]["final_signable_request_package_ready_count"] == 1 + assert preflight["summary"]["operator_held_secret_boundary_count"] == 1 + assert preflight["summary"]["signing_execution_input_requirement_count"] == 10 + assert preflight["summary"]["signing_execution_abort_condition_count"] == 8 + assert preflight["summary"]["rollback_boundary_count"] == 4 + assert preflight["summary"]["post_apply_verifier_required_count"] == 1 + assert preflight["summary"]["same_run_truth_required_count"] == 1 + assert preflight["summary"]["signs_database_apply_authorization_count"] == 0 + assert preflight["summary"]["reads_secret_count"] == 0 + assert preflight["summary"]["executes_script_count"] == 0 + assert preflight["summary"]["executes_sql_count"] == 0 + assert preflight["summary"]["writes_database_count"] == 0 + assert future_preflight["preflight_id"].startswith( + "pchome-db-apply-authorization-signing-execution-preflight-" + ) + assert future_preflight["source_final_signable_request_package_id"].startswith( + "pchome-db-apply-authorization-signing-issuer-closeout-" + ) + assert future_preflight["ready_for_future_signing_execution_preflight"] is True + assert future_preflight["can_enter_future_authorization_signing_execution_lane"] is True + assert future_preflight["ready_for_database_apply_now"] is False + assert future_preflight["issues_database_apply_authorization"] is False + assert future_preflight["signs_database_apply_authorization"] is False + assert package["package_id"].startswith( + "pchome-db-apply-authorization-signing-execution-preflight-" + ) + assert package["authorization_material_type"] == "signing_execution_preflight_package" + assert package["ready_for_future_signing_execution_preflight"] is True + assert package["required_nonsecret_input_count"] == 10 + assert all(item["secret"] is False for item in package["required_nonsecret_inputs"]) + assert package["target_file"] == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + assert package["hash_matches"] is True + assert package["requires_fresh_production_truth_in_same_run"] is True + assert package["requires_post_apply_verifier"] is True + assert package["secret_material_included"] is False + assert package["secret_material_required_in_preview"] is False + assert package["reads_secret_in_preview"] is False + assert package["executes_shell_in_preview"] is False + assert package["executes_sql_in_preview"] is False + assert package["writes_database_in_preview"] is False + assert package["command_preview"]["redacts_secret_values"] is True + assert package["command_preview"]["executes_in_preview"] is False + assert package["command_preview"]["signs_database_apply_authorization"] is False + assert boundary["secret_reference_mode"] == "external_runtime_reference_only" + assert boundary["secret_material_included"] is False + assert boundary["secret_material_required_in_preview"] is False + assert boundary["reads_secret_in_preview"] is False + assert boundary["accepts_plaintext_secret"] is False + assert boundary["permits_secret_value_logging"] is False + assert contract["permits_future_explicit_authorization_signing_execution_lane"] is True + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert "operator_held_secret_boundary_is_externalized" in check_keys + assert "future_command_preview_is_non_executing_and_redacted" in check_keys + assert "preview_has_no_side_effects_and_no_signing" in check_keys + assert preflight["safety"]["reads_secret_in_preview"] is False + assert preflight["safety"]["signs_database_apply_authorization"] is False + assert preflight["safety"]["executes_script"] is False + assert preflight["safety"]["executes_sql"] is False + assert preflight["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signing_execution_closeout_waits_without_ready_preflight(): + closeout = build_pchome_auto_policy_db_apply_authorization_signing_execution_closeout( + _payload(), + batch_size=1, + ) + + future_closeout = closeout["future_authorization_signing_execution_closeout"] + boundary = closeout["unsigned_signed_authorization_receipt_boundary"] + contract = closeout["signing_execution_closeout_contract"] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signing_execution_closeout" + ) + assert closeout["result"] == "WAITING_FOR_DB_APPLY_AUTHORIZATION_SIGNING_EXECUTION_PREFLIGHT" + assert closeout["summary"]["authorization_signing_execution_closeout_ready_count"] == 0 + assert closeout["summary"]["signing_execution_closeout_check_count"] == 12 + assert closeout["summary"]["signing_execution_closeout_waiting_count"] > 0 + assert closeout["summary"]["authorization_signing_execution_preflight_ready_count"] == 0 + assert closeout["summary"]["signing_execution_preflight_check_count"] == 12 + assert closeout["summary"]["unsigned_signed_authorization_receipt_boundary_count"] == 1 + assert closeout["summary"]["operator_held_secret_boundary_count"] == 1 + assert closeout["summary"]["signing_execution_input_requirement_count"] == 10 + assert closeout["summary"]["signing_execution_abort_condition_count"] == 8 + assert closeout["summary"]["rollback_boundary_count"] == 4 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_script_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert future_closeout["ready_for_future_signing_execution_closeout"] is False + assert ( + future_closeout["can_enter_future_unsigned_signed_authorization_receipt_boundary"] + is False + ) + assert future_closeout["ready_for_database_apply_now"] is False + assert future_closeout["issues_database_apply_authorization"] is False + assert future_closeout["signs_database_apply_authorization"] is False + assert future_closeout["secret_material_included"] is False + assert future_closeout["secret_material_required_in_preview"] is False + assert future_closeout["reads_secret_in_preview"] is False + assert boundary["authorization_material_type"] == ( + "unsigned_signed_authorization_receipt_boundary" + ) + assert boundary["ready_for_future_unsigned_signed_authorization_receipt_boundary"] is False + assert boundary["ready_for_future_signed_authorization_receipt_lane"] is False + assert boundary["ready_for_database_apply_now"] is False + assert boundary["issues_database_apply_authorization"] is False + assert boundary["signs_database_apply_authorization"] is False + assert boundary["signed_authorization_receipt_included"] is False + assert boundary["signature_material_included"] is False + assert boundary["secret_material_included"] is False + assert boundary["secret_material_required_in_preview"] is False + assert boundary["reads_secret_in_preview"] is False + assert boundary["executes_shell_in_preview"] is False + assert boundary["executes_sql_in_preview"] is False + assert boundary["writes_database_in_preview"] is False + assert contract["permits_future_unsigned_signed_authorization_receipt_boundary"] is False + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["signs_database_apply_authorization"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signing_execution_closeout_ready_after_fake_fetch_but_no_signing(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = build_pchome_auto_policy_db_apply_authorization_signing_execution_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + future_closeout = closeout["future_authorization_signing_execution_closeout"] + boundary = closeout["unsigned_signed_authorization_receipt_boundary"] + contract = closeout["signing_execution_closeout_contract"] + check_keys = [check["key"] for check in closeout["signing_execution_closeout_checks"]] + assert closeout["result"] == "DB_APPLY_AUTHORIZATION_SIGNING_EXECUTION_CLOSEOUT_READY" + assert closeout["summary"]["authorization_signing_execution_closeout_ready_count"] == 1 + assert closeout["summary"]["signing_execution_closeout_check_count"] == 12 + assert closeout["summary"]["signing_execution_closeout_pass_count"] == 12 + assert closeout["summary"]["signing_execution_closeout_waiting_count"] == 0 + assert closeout["summary"]["authorization_signing_execution_preflight_ready_count"] == 1 + assert closeout["summary"]["signing_execution_preflight_check_count"] == 12 + assert closeout["summary"]["unsigned_signed_authorization_receipt_boundary_count"] == 1 + assert closeout["summary"]["operator_held_secret_boundary_count"] == 1 + assert closeout["summary"]["signing_execution_input_requirement_count"] == 10 + assert closeout["summary"]["signing_execution_abort_condition_count"] == 8 + assert closeout["summary"]["rollback_boundary_count"] == 4 + assert closeout["summary"]["post_apply_verifier_required_count"] == 1 + assert closeout["summary"]["same_run_truth_required_count"] == 1 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_script_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert future_closeout["closeout_id"].startswith( + "pchome-db-apply-authorization-signing-execution-closeout-" + ) + assert future_closeout["source_signing_execution_preflight_id"].startswith( + "pchome-db-apply-authorization-signing-execution-preflight-" + ) + assert future_closeout["source_final_signable_request_package_id"].startswith( + "pchome-db-apply-authorization-signing-issuer-closeout-" + ) + assert future_closeout["ready_for_future_signing_execution_closeout"] is True + assert ( + future_closeout["can_enter_future_unsigned_signed_authorization_receipt_boundary"] + is True + ) + assert future_closeout["ready_for_database_apply_now"] is False + assert future_closeout["issues_database_apply_authorization"] is False + assert future_closeout["signs_database_apply_authorization"] is False + assert boundary["boundary_id"].startswith( + "pchome-db-apply-authorization-signing-execution-closeout-" + ) + assert boundary["authorization_material_type"] == ( + "unsigned_signed_authorization_receipt_boundary" + ) + assert boundary["ready_for_future_unsigned_signed_authorization_receipt_boundary"] is True + assert boundary["ready_for_future_signed_authorization_receipt_lane"] is True + assert boundary["ready_for_database_apply_now"] is False + assert boundary["issues_database_apply_authorization"] is False + assert boundary["signs_database_apply_authorization"] is False + assert boundary["signed_authorization_receipt_included"] is False + assert boundary["signature_material_included"] is False + assert boundary["secret_material_included"] is False + assert boundary["secret_material_required_in_preview"] is False + assert boundary["reads_secret_in_preview"] is False + assert boundary["executes_shell_in_preview"] is False + assert boundary["executes_sql_in_preview"] is False + assert boundary["writes_database_in_preview"] is False + assert boundary["operator_held_secret_boundary_contract"]["secret_reference_mode"] == ( + "external_runtime_reference_only" + ) + assert boundary["command_preview"]["redacts_secret_values"] is True + assert boundary["command_preview"]["executes_in_preview"] is False + assert boundary["command_preview"]["signs_database_apply_authorization"] is False + assert boundary["target_file"] == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + assert boundary["hash_matches"] is True + assert boundary["requires_fresh_production_truth_in_same_run"] is True + assert boundary["requires_post_apply_verifier"] is True + assert contract["permits_future_unsigned_signed_authorization_receipt_boundary"] is True + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert "operator_held_secret_boundary_carried_forward" in check_keys + assert "closeout_does_not_authorize_sign_or_apply" in check_keys + assert "preview_has_no_side_effects_and_no_signing" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["signs_database_apply_authorization"] is False + assert closeout["safety"]["executes_script"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signed_receipt_preflight_waits_without_ready_closeout(): + preflight = build_pchome_auto_policy_db_apply_authorization_signed_receipt_preflight( + _payload(), + batch_size=1, + ) + + future_preflight = preflight["future_authorization_signed_receipt_preflight"] + boundary = preflight["external_signing_receipt_evidence_boundary"] + contract = preflight["signed_receipt_preflight_contract"] + assert preflight["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signed_receipt_preflight" + ) + assert preflight["result"] == "WAITING_FOR_DB_APPLY_AUTHORIZATION_SIGNING_EXECUTION_CLOSEOUT" + assert preflight["summary"]["authorization_signed_receipt_preflight_ready_count"] == 0 + assert preflight["summary"]["signed_receipt_preflight_check_count"] == 12 + assert preflight["summary"]["signed_receipt_preflight_waiting_count"] > 0 + assert preflight["summary"]["authorization_signing_execution_closeout_ready_count"] == 0 + assert preflight["summary"]["signing_execution_closeout_check_count"] == 12 + assert preflight["summary"]["unsigned_signed_authorization_receipt_boundary_count"] == 1 + assert preflight["summary"]["external_signing_receipt_evidence_boundary_count"] == 1 + assert preflight["summary"]["required_external_receipt_evidence_count"] == 10 + assert preflight["summary"]["external_receipt_acceptance_gate_count"] == 8 + assert preflight["summary"]["operator_held_secret_boundary_count"] == 1 + assert preflight["summary"]["signing_execution_input_requirement_count"] == 10 + assert preflight["summary"]["signing_execution_abort_condition_count"] == 8 + assert preflight["summary"]["rollback_boundary_count"] == 4 + assert preflight["summary"]["signs_database_apply_authorization_count"] == 0 + assert preflight["summary"]["reads_secret_count"] == 0 + assert preflight["summary"]["executes_script_count"] == 0 + assert preflight["summary"]["executes_sql_count"] == 0 + assert preflight["summary"]["writes_database_count"] == 0 + assert future_preflight["ready_for_future_signed_authorization_receipt_preflight"] is False + assert ( + future_preflight["can_enter_future_external_signing_receipt_evidence_boundary"] + is False + ) + assert future_preflight["ready_for_database_apply_now"] is False + assert future_preflight["issues_database_apply_authorization"] is False + assert future_preflight["signs_database_apply_authorization"] is False + assert future_preflight["signed_authorization_receipt_included"] is False + assert future_preflight["signature_material_included"] is False + assert future_preflight["secret_material_included"] is False + assert future_preflight["reads_secret_in_preview"] is False + assert boundary["authorization_material_type"] == "external_signing_receipt_evidence_boundary" + assert boundary["ready_for_future_external_signing_receipt_evidence_boundary"] is False + assert boundary["ready_for_future_signed_authorization_receipt_lane"] is False + assert boundary["required_external_receipt_evidence_count"] == 10 + assert boundary["external_receipt_acceptance_gate_count"] == 8 + assert boundary["external_signed_authorization_receipt_required_in_future"] is True + assert boundary["external_signed_authorization_receipt_included"] is False + assert boundary["signed_authorization_receipt_included"] is False + assert boundary["signature_material_included"] is False + assert boundary["secret_material_included"] is False + assert boundary["secret_material_required_in_preview"] is False + assert boundary["reads_secret_in_preview"] is False + assert boundary["executes_shell_in_preview"] is False + assert boundary["executes_sql_in_preview"] is False + assert boundary["writes_database_in_preview"] is False + assert boundary["ready_for_database_apply_now"] is False + assert boundary["signs_database_apply_authorization"] is False + assert contract["permits_future_external_signing_receipt_evidence_boundary"] is False + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert preflight["safety"]["reads_secret_in_preview"] is False + assert preflight["safety"]["signs_database_apply_authorization"] is False + assert preflight["safety"]["executes_sql"] is False + assert preflight["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signed_receipt_preflight_ready_after_fake_fetch_but_no_signed_receipt(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + preflight = build_pchome_auto_policy_db_apply_authorization_signed_receipt_preflight( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + future_preflight = preflight["future_authorization_signed_receipt_preflight"] + boundary = preflight["external_signing_receipt_evidence_boundary"] + contract = preflight["signed_receipt_preflight_contract"] + check_keys = [check["key"] for check in preflight["signed_receipt_preflight_checks"]] + assert preflight["result"] == "DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_PREFLIGHT_READY" + assert preflight["summary"]["authorization_signed_receipt_preflight_ready_count"] == 1 + assert preflight["summary"]["signed_receipt_preflight_check_count"] == 12 + assert preflight["summary"]["signed_receipt_preflight_pass_count"] == 12 + assert preflight["summary"]["signed_receipt_preflight_waiting_count"] == 0 + assert preflight["summary"]["authorization_signing_execution_closeout_ready_count"] == 1 + assert preflight["summary"]["signing_execution_closeout_check_count"] == 12 + assert preflight["summary"]["unsigned_signed_authorization_receipt_boundary_count"] == 1 + assert preflight["summary"]["external_signing_receipt_evidence_boundary_count"] == 1 + assert preflight["summary"]["required_external_receipt_evidence_count"] == 10 + assert preflight["summary"]["external_receipt_acceptance_gate_count"] == 8 + assert preflight["summary"]["operator_held_secret_boundary_count"] == 1 + assert preflight["summary"]["signing_execution_input_requirement_count"] == 10 + assert preflight["summary"]["signing_execution_abort_condition_count"] == 8 + assert preflight["summary"]["rollback_boundary_count"] == 4 + assert preflight["summary"]["post_apply_verifier_required_count"] == 1 + assert preflight["summary"]["same_run_truth_required_count"] == 1 + assert preflight["summary"]["signs_database_apply_authorization_count"] == 0 + assert preflight["summary"]["reads_secret_count"] == 0 + assert preflight["summary"]["executes_script_count"] == 0 + assert preflight["summary"]["executes_sql_count"] == 0 + assert preflight["summary"]["writes_database_count"] == 0 + assert future_preflight["preflight_id"].startswith( + "pchome-db-apply-authorization-signed-receipt-preflight-" + ) + assert future_preflight["source_signing_execution_closeout_id"].startswith( + "pchome-db-apply-authorization-signing-execution-closeout-" + ) + assert future_preflight["ready_for_future_signed_authorization_receipt_preflight"] is True + assert ( + future_preflight["can_enter_future_external_signing_receipt_evidence_boundary"] + is True + ) + assert future_preflight["ready_for_database_apply_now"] is False + assert future_preflight["issues_database_apply_authorization"] is False + assert future_preflight["signs_database_apply_authorization"] is False + assert future_preflight["signed_authorization_receipt_included"] is False + assert future_preflight["signature_material_included"] is False + assert boundary["boundary_id"].startswith( + "pchome-db-apply-authorization-signed-receipt-preflight-" + ) + assert boundary["authorization_material_type"] == "external_signing_receipt_evidence_boundary" + assert boundary["ready_for_future_external_signing_receipt_evidence_boundary"] is True + assert boundary["ready_for_future_signed_authorization_receipt_lane"] is True + assert boundary["required_external_receipt_evidence_count"] == 10 + assert boundary["external_receipt_acceptance_gate_count"] == 8 + assert "detached_signature_verification_status" in boundary["required_external_receipt_evidence"] + assert boundary["external_signed_authorization_receipt_required_in_future"] is True + assert boundary["external_signed_authorization_receipt_included"] is False + assert boundary["signed_authorization_receipt_included"] is False + assert boundary["signature_material_included"] is False + assert boundary["secret_material_included"] is False + assert boundary["secret_material_required_in_preview"] is False + assert boundary["reads_secret_in_preview"] is False + assert boundary["executes_shell_in_preview"] is False + assert boundary["executes_sql_in_preview"] is False + assert boundary["writes_database_in_preview"] is False + assert boundary["ready_for_database_apply_now"] is False + assert boundary["signs_database_apply_authorization"] is False + assert boundary["target_file"] == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + assert boundary["hash_matches"] is True + assert boundary["requires_fresh_production_truth_in_same_run"] is True + assert boundary["requires_post_apply_verifier"] is True + assert contract["permits_future_external_signing_receipt_evidence_boundary"] is True + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert "external_receipt_evidence_contract_complete" in check_keys + assert "preflight_has_no_signed_receipt_signature_or_authorization" in check_keys + assert "preview_has_no_side_effects_and_no_signing" in check_keys + assert preflight["safety"]["reads_secret_in_preview"] is False + assert preflight["safety"]["signs_database_apply_authorization"] is False + assert preflight["safety"]["executes_script"] is False + assert preflight["safety"]["executes_sql"] is False + assert preflight["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signed_receipt_closeout_waits_without_ready_preflight(): + closeout = build_pchome_auto_policy_db_apply_authorization_signed_receipt_closeout( + _payload(), + batch_size=1, + ) + + future_closeout = closeout["future_authorization_signed_receipt_closeout"] + boundary = closeout["detached_receipt_verification_boundary"] + contract = closeout["signed_receipt_closeout_contract"] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signed_receipt_closeout" + ) + assert closeout["result"] == "WAITING_FOR_DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_PREFLIGHT" + assert closeout["summary"]["authorization_signed_receipt_closeout_ready_count"] == 0 + assert closeout["summary"]["signed_receipt_closeout_check_count"] == 12 + assert closeout["summary"]["signed_receipt_closeout_waiting_count"] > 0 + assert closeout["summary"]["authorization_signed_receipt_preflight_ready_count"] == 0 + assert closeout["summary"]["signed_receipt_preflight_check_count"] == 12 + assert closeout["summary"]["external_signing_receipt_evidence_boundary_count"] == 1 + assert closeout["summary"]["detached_receipt_verification_boundary_count"] == 1 + assert closeout["summary"]["required_external_receipt_evidence_count"] == 10 + assert closeout["summary"]["external_receipt_acceptance_gate_count"] == 8 + assert closeout["summary"]["detached_receipt_verification_check_count"] == 10 + assert closeout["summary"]["operator_held_secret_boundary_count"] == 1 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_script_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert future_closeout["ready_for_future_signed_authorization_receipt_closeout"] is False + assert future_closeout["can_enter_future_detached_receipt_verification_boundary"] is False + assert future_closeout["ready_for_database_apply_now"] is False + assert future_closeout["issues_database_apply_authorization"] is False + assert future_closeout["signs_database_apply_authorization"] is False + assert future_closeout["external_signed_authorization_receipt_included"] is False + assert future_closeout["signed_authorization_receipt_included"] is False + assert future_closeout["signature_material_included"] is False + assert future_closeout["secret_material_included"] is False + assert boundary["authorization_material_type"] == "detached_receipt_verification_boundary" + assert boundary["ready_for_future_detached_receipt_verification_boundary"] is False + assert boundary["ready_for_future_signed_authorization_receipt_verification_lane"] is False + assert boundary["detached_receipt_verification_check_count"] == 10 + assert boundary["requires_detached_signature_verification"] is True + assert boundary["detached_signature_verification_performed"] is False + assert boundary["external_signed_authorization_receipt_included"] is False + assert boundary["signed_authorization_receipt_included"] is False + assert boundary["signature_material_included"] is False + assert boundary["secret_material_included"] is False + assert boundary["secret_material_required_in_preview"] is False + assert boundary["reads_secret_in_preview"] is False + assert boundary["executes_shell_in_preview"] is False + assert boundary["executes_sql_in_preview"] is False + assert boundary["writes_database_in_preview"] is False + assert boundary["ready_for_database_apply_now"] is False + assert boundary["signs_database_apply_authorization"] is False + assert contract["permits_future_detached_receipt_verification_boundary"] is False + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["signs_database_apply_authorization"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signed_receipt_closeout_ready_after_fake_fetch_but_no_signed_receipt(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = build_pchome_auto_policy_db_apply_authorization_signed_receipt_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + future_closeout = closeout["future_authorization_signed_receipt_closeout"] + boundary = closeout["detached_receipt_verification_boundary"] + contract = closeout["signed_receipt_closeout_contract"] + check_keys = [check["key"] for check in closeout["signed_receipt_closeout_checks"]] + assert closeout["result"] == "DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_CLOSEOUT_READY" + assert closeout["summary"]["authorization_signed_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["signed_receipt_closeout_check_count"] == 12 + assert closeout["summary"]["signed_receipt_closeout_pass_count"] == 12 + assert closeout["summary"]["signed_receipt_closeout_waiting_count"] == 0 + assert closeout["summary"]["authorization_signed_receipt_preflight_ready_count"] == 1 + assert closeout["summary"]["signed_receipt_preflight_check_count"] == 12 + assert closeout["summary"]["external_signing_receipt_evidence_boundary_count"] == 1 + assert closeout["summary"]["detached_receipt_verification_boundary_count"] == 1 + assert closeout["summary"]["required_external_receipt_evidence_count"] == 10 + assert closeout["summary"]["external_receipt_acceptance_gate_count"] == 8 + assert closeout["summary"]["detached_receipt_verification_check_count"] == 10 + assert closeout["summary"]["post_apply_verifier_required_count"] == 1 + assert closeout["summary"]["same_run_truth_required_count"] == 1 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_script_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert future_closeout["closeout_id"].startswith( + "pchome-db-apply-authorization-signed-receipt-closeout-" + ) + assert future_closeout["source_signed_receipt_preflight_id"].startswith( + "pchome-db-apply-authorization-signed-receipt-preflight-" + ) + assert future_closeout["ready_for_future_signed_authorization_receipt_closeout"] is True + assert future_closeout["can_enter_future_detached_receipt_verification_boundary"] is True + assert future_closeout["ready_for_database_apply_now"] is False + assert future_closeout["issues_database_apply_authorization"] is False + assert future_closeout["signs_database_apply_authorization"] is False + assert future_closeout["external_signed_authorization_receipt_included"] is False + assert future_closeout["signed_authorization_receipt_included"] is False + assert future_closeout["signature_material_included"] is False + assert boundary["boundary_id"].startswith( + "pchome-db-apply-authorization-signed-receipt-closeout-" + ) + assert boundary["authorization_material_type"] == "detached_receipt_verification_boundary" + assert boundary["ready_for_future_detached_receipt_verification_boundary"] is True + assert boundary["ready_for_future_signed_authorization_receipt_verification_lane"] is True + assert boundary["required_external_receipt_evidence_count"] == 10 + assert boundary["external_receipt_acceptance_gate_count"] == 8 + assert boundary["detached_receipt_verification_check_count"] == 10 + assert "detached_signature_verification_status_passed" in ( + boundary["detached_receipt_verification_checks"] + ) + assert boundary["requires_detached_signature_verification"] is True + assert boundary["detached_signature_verification_performed"] is False + assert boundary["external_signed_authorization_receipt_included"] is False + assert boundary["signed_authorization_receipt_included"] is False + assert boundary["signature_material_included"] is False + assert boundary["secret_material_included"] is False + assert boundary["secret_material_required_in_preview"] is False + assert boundary["reads_secret_in_preview"] is False + assert boundary["executes_shell_in_preview"] is False + assert boundary["executes_sql_in_preview"] is False + assert boundary["writes_database_in_preview"] is False + assert boundary["ready_for_database_apply_now"] is False + assert boundary["signs_database_apply_authorization"] is False + assert boundary["target_file"] == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + assert boundary["hash_matches"] is True + assert boundary["requires_fresh_production_truth_in_same_run"] is True + assert boundary["requires_post_apply_verifier"] is True + assert contract["permits_future_detached_receipt_verification_boundary"] is True + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert "detached_receipt_verification_boundary_contract_complete" in check_keys + assert "closeout_has_no_signed_receipt_signature_or_authorization" in check_keys + assert "preview_has_no_side_effects_and_no_signing" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["signs_database_apply_authorization"] is False + assert closeout["safety"]["executes_script"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signed_receipt_evidence_intake_waits_without_ready_closeout(): + intake = build_pchome_auto_policy_db_apply_authorization_signed_receipt_evidence_intake( + _payload(), + batch_size=1, + ) + + future_intake = intake["future_signed_authorization_receipt_evidence_intake"] + schema = intake["detached_verification_evidence_schema"] + contract = intake["signed_receipt_evidence_intake_contract"] + assert intake["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signed_receipt_evidence_intake" + ) + assert intake["result"] == "WAITING_FOR_DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_CLOSEOUT" + assert intake["summary"]["authorization_signed_receipt_evidence_intake_ready_count"] == 0 + assert intake["summary"]["signed_receipt_evidence_intake_check_count"] == 12 + assert intake["summary"]["signed_receipt_evidence_intake_waiting_count"] > 0 + assert intake["summary"]["authorization_signed_receipt_closeout_ready_count"] == 0 + assert intake["summary"]["signed_receipt_closeout_check_count"] == 12 + assert intake["summary"]["detached_receipt_verification_boundary_count"] == 1 + assert intake["summary"]["detached_verification_evidence_schema_count"] == 1 + assert intake["summary"]["detached_verification_evidence_field_count"] == 12 + assert intake["summary"]["detached_verification_acceptance_gate_count"] == 10 + assert intake["summary"]["signs_database_apply_authorization_count"] == 0 + assert intake["summary"]["reads_secret_count"] == 0 + assert intake["summary"]["executes_script_count"] == 0 + assert intake["summary"]["executes_sql_count"] == 0 + assert intake["summary"]["writes_database_count"] == 0 + assert ( + future_intake["ready_for_future_signed_authorization_receipt_evidence_intake"] + is False + ) + assert future_intake["can_enter_future_detached_verification_evidence_validation"] is False + assert future_intake["external_signed_authorization_receipt_evidence_schema_ready"] is False + assert future_intake["ready_for_database_apply_now"] is False + assert future_intake["issues_database_apply_authorization"] is False + assert future_intake["signs_database_apply_authorization"] is False + assert future_intake["detached_signature_verification_performed"] is False + assert future_intake["external_signed_authorization_receipt_included"] is False + assert future_intake["signed_authorization_receipt_included"] is False + assert future_intake["signature_material_included"] is False + assert future_intake["secret_material_included"] is False + assert schema["authorization_material_type"] == "detached_verification_evidence_schema" + assert schema["ready_for_future_detached_verification_evidence_schema"] is False + assert schema["detached_verification_evidence_field_count"] == 12 + assert schema["detached_verification_acceptance_gate_count"] == 10 + assert schema["requires_detached_signature_verification"] is True + assert schema["detached_signature_verification_performed"] is False + assert schema["external_signed_authorization_receipt_required_in_future"] is True + assert schema["external_signed_authorization_receipt_included"] is False + assert schema["signed_authorization_receipt_included"] is False + assert schema["signature_material_included"] is False + assert schema["secret_material_included"] is False + assert schema["accepts_plaintext_secret"] is False + assert schema["reads_secret_in_preview"] is False + assert schema["executes_shell_in_preview"] is False + assert schema["executes_sql_in_preview"] is False + assert schema["writes_database_in_preview"] is False + assert schema["ready_for_database_apply_now"] is False + assert schema["signs_database_apply_authorization"] is False + assert contract["permits_future_detached_verification_evidence_validation"] is False + assert contract["accepts_plaintext_secret"] is False + assert contract["detached_signature_verification_performed"] is False + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert intake["safety"]["reads_secret_in_preview"] is False + assert intake["safety"]["performs_detached_signature_verification"] is False + assert intake["safety"]["signs_database_apply_authorization"] is False + assert intake["safety"]["executes_sql"] is False + assert intake["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signed_receipt_evidence_intake_ready_after_fake_fetch_but_no_signed_receipt(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + intake = build_pchome_auto_policy_db_apply_authorization_signed_receipt_evidence_intake( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + future_intake = intake["future_signed_authorization_receipt_evidence_intake"] + schema = intake["detached_verification_evidence_schema"] + contract = intake["signed_receipt_evidence_intake_contract"] + check_keys = [check["key"] for check in intake["signed_receipt_evidence_intake_checks"]] + assert intake["result"] == "DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_EVIDENCE_INTAKE_READY" + assert intake["summary"]["authorization_signed_receipt_evidence_intake_ready_count"] == 1 + assert intake["summary"]["signed_receipt_evidence_intake_check_count"] == 12 + assert intake["summary"]["signed_receipt_evidence_intake_pass_count"] == 12 + assert intake["summary"]["signed_receipt_evidence_intake_waiting_count"] == 0 + assert intake["summary"]["authorization_signed_receipt_closeout_ready_count"] == 1 + assert intake["summary"]["signed_receipt_closeout_check_count"] == 12 + assert intake["summary"]["detached_receipt_verification_boundary_count"] == 1 + assert intake["summary"]["detached_verification_evidence_schema_count"] == 1 + assert intake["summary"]["required_external_receipt_evidence_count"] == 10 + assert intake["summary"]["external_receipt_acceptance_gate_count"] == 8 + assert intake["summary"]["detached_receipt_verification_check_count"] == 10 + assert intake["summary"]["detached_verification_evidence_field_count"] == 12 + assert intake["summary"]["detached_verification_acceptance_gate_count"] == 10 + assert intake["summary"]["post_apply_verifier_required_count"] == 1 + assert intake["summary"]["same_run_truth_required_count"] == 1 + assert intake["summary"]["signs_database_apply_authorization_count"] == 0 + assert intake["summary"]["reads_secret_count"] == 0 + assert intake["summary"]["executes_script_count"] == 0 + assert intake["summary"]["executes_sql_count"] == 0 + assert intake["summary"]["writes_database_count"] == 0 + assert future_intake["intake_id"].startswith( + "pchome-db-apply-authorization-signed-receipt-evidence-intake-" + ) + assert future_intake["source_signed_receipt_closeout_id"].startswith( + "pchome-db-apply-authorization-signed-receipt-closeout-" + ) + assert ( + future_intake["ready_for_future_signed_authorization_receipt_evidence_intake"] + is True + ) + assert future_intake["can_enter_future_detached_verification_evidence_validation"] is True + assert future_intake["external_signed_authorization_receipt_evidence_schema_ready"] is True + assert future_intake["ready_for_database_apply_now"] is False + assert future_intake["issues_database_apply_authorization"] is False + assert future_intake["signs_database_apply_authorization"] is False + assert future_intake["detached_signature_verification_performed"] is False + assert future_intake["external_signed_authorization_receipt_included"] is False + assert future_intake["signed_authorization_receipt_included"] is False + assert future_intake["signature_material_included"] is False + assert future_intake["secret_material_included"] is False + assert schema["schema_id"].startswith( + "pchome-db-apply-authorization-signed-receipt-evidence-intake-" + ) + assert schema["authorization_material_type"] == "detached_verification_evidence_schema" + assert schema["ready_for_future_detached_verification_evidence_schema"] is True + assert schema["required_external_receipt_evidence_count"] == 10 + assert schema["external_receipt_acceptance_gate_count"] == 8 + assert schema["detached_receipt_verification_check_count"] == 10 + assert schema["detached_verification_evidence_field_count"] == 12 + assert schema["detached_verification_acceptance_gate_count"] == 10 + assert "verifier_receipt_sha256" in schema["detached_verification_evidence_fields"] + assert "detached_signature_verification_status_is_passed" in ( + schema["detached_verification_acceptance_gates"] + ) + assert schema["requires_detached_signature_verification"] is True + assert schema["detached_signature_verification_performed"] is False + assert schema["external_signed_authorization_receipt_required_in_future"] is True + assert schema["external_signed_authorization_receipt_included"] is False + assert schema["signed_authorization_receipt_included"] is False + assert schema["signature_material_included"] is False + assert schema["secret_material_included"] is False + assert schema["accepts_plaintext_secret"] is False + assert schema["reads_secret_in_preview"] is False + assert schema["executes_shell_in_preview"] is False + assert schema["executes_sql_in_preview"] is False + assert schema["writes_database_in_preview"] is False + assert schema["ready_for_database_apply_now"] is False + assert schema["signs_database_apply_authorization"] is False + assert schema["target_file"] == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + assert schema["hash_matches"] is True + assert schema["requires_fresh_production_truth_in_same_run"] is True + assert schema["requires_post_apply_verifier"] is True + assert contract["permits_future_detached_verification_evidence_validation"] is True + assert contract["accepts_plaintext_secret"] is False + assert contract["detached_signature_verification_performed"] is False + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert "detached_verification_evidence_schema_complete" in check_keys + assert "no_signed_receipt_signature_secret_or_verification_execution" in check_keys + assert "preview_has_no_side_effects_and_no_signing" in check_keys + assert intake["safety"]["reads_secret_in_preview"] is False + assert intake["safety"]["performs_detached_signature_verification"] is False + assert intake["safety"]["signs_database_apply_authorization"] is False + assert intake["safety"]["executes_script"] is False + assert intake["safety"]["executes_sql"] is False + assert intake["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_detached_verification_evidence_validation_waits_without_ready_intake(): + validation = ( + build_pchome_auto_policy_db_apply_authorization_detached_verification_evidence_validation( + _payload(), + batch_size=1, + ) + ) + + future_validation = validation["future_detached_verification_evidence_validation"] + boundary = validation["verifier_receipt_closeout_boundary"] + contract = validation["detached_verification_evidence_validation_contract"] + assert validation["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_detached_verification_evidence_validation" + ) + assert ( + validation["result"] + == "WAITING_FOR_DB_APPLY_AUTHORIZATION_SIGNED_RECEIPT_EVIDENCE_INTAKE" + ) + assert ( + validation["summary"][ + "authorization_detached_verification_evidence_validation_ready_count" + ] + == 0 + ) + assert validation["summary"]["detached_verification_evidence_validation_check_count"] == 12 + assert validation["summary"]["detached_verification_evidence_validation_waiting_count"] > 0 + assert validation["summary"]["authorization_signed_receipt_evidence_intake_ready_count"] == 0 + assert validation["summary"]["signed_receipt_evidence_intake_check_count"] == 12 + assert validation["summary"]["detached_verification_evidence_schema_count"] == 1 + assert validation["summary"]["verifier_receipt_closeout_boundary_count"] == 1 + assert validation["summary"]["detached_verification_evidence_field_count"] == 12 + assert validation["summary"]["detached_verification_acceptance_gate_count"] == 10 + assert validation["summary"]["verifier_receipt_field_count"] == 12 + assert validation["summary"]["verifier_receipt_acceptance_gate_count"] == 10 + assert validation["summary"]["signs_database_apply_authorization_count"] == 0 + assert validation["summary"]["reads_secret_count"] == 0 + assert validation["summary"]["executes_script_count"] == 0 + assert validation["summary"]["executes_sql_count"] == 0 + assert validation["summary"]["writes_database_count"] == 0 + assert ( + future_validation["ready_for_future_detached_verification_evidence_validation"] + is False + ) + assert future_validation["can_enter_future_verifier_receipt_closeout"] is False + assert future_validation["verifier_receipt_closeout_boundary_ready"] is False + assert future_validation["ready_for_database_apply_now"] is False + assert future_validation["issues_database_apply_authorization"] is False + assert future_validation["signs_database_apply_authorization"] is False + assert future_validation["detached_signature_verification_performed"] is False + assert future_validation["verifier_receipt_persisted"] is False + assert future_validation["external_signed_authorization_receipt_included"] is False + assert future_validation["signed_authorization_receipt_included"] is False + assert future_validation["signature_material_included"] is False + assert future_validation["secret_material_included"] is False + assert boundary["authorization_material_type"] == "verifier_receipt_closeout_boundary" + assert boundary["ready_for_future_verifier_receipt_closeout_boundary"] is False + assert boundary["verifier_receipt_field_count"] == 12 + assert boundary["verifier_receipt_acceptance_gate_count"] == 10 + assert boundary["requires_detached_signature_verification"] is True + assert boundary["detached_signature_verification_performed"] is False + assert boundary["verifier_receipt_persisted"] is False + assert boundary["external_signed_authorization_receipt_included"] is False + assert boundary["signed_authorization_receipt_included"] is False + assert boundary["signature_material_included"] is False + assert boundary["secret_material_included"] is False + assert boundary["accepts_plaintext_secret"] is False + assert boundary["reads_secret_in_preview"] is False + assert boundary["executes_shell_in_preview"] is False + assert boundary["executes_sql_in_preview"] is False + assert boundary["writes_database_in_preview"] is False + assert boundary["ready_for_database_apply_now"] is False + assert boundary["signs_database_apply_authorization"] is False + assert contract["permits_future_verifier_receipt_closeout"] is False + assert contract["accepts_plaintext_secret"] is False + assert contract["performs_detached_signature_verification"] is False + assert contract["persists_verifier_receipt"] is False + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert validation["safety"]["reads_secret_in_preview"] is False + assert validation["safety"]["performs_detached_signature_verification"] is False + assert validation["safety"]["persists_verifier_receipt"] is False + assert validation["safety"]["signs_database_apply_authorization"] is False + assert validation["safety"]["executes_sql"] is False + assert validation["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_detached_verification_evidence_validation_ready_after_fake_fetch_but_no_verification(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + validation = ( + build_pchome_auto_policy_db_apply_authorization_detached_verification_evidence_validation( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future_validation = validation["future_detached_verification_evidence_validation"] + boundary = validation["verifier_receipt_closeout_boundary"] + contract = validation["detached_verification_evidence_validation_contract"] + check_keys = [ + check["key"] + for check in validation["detached_verification_evidence_validation_checks"] + ] + assert ( + validation["result"] + == "DB_APPLY_AUTHORIZATION_DETACHED_VERIFICATION_EVIDENCE_VALIDATION_READY" + ) + assert ( + validation["summary"][ + "authorization_detached_verification_evidence_validation_ready_count" + ] + == 1 + ) + assert validation["summary"]["detached_verification_evidence_validation_check_count"] == 12 + assert validation["summary"]["detached_verification_evidence_validation_pass_count"] == 12 + assert validation["summary"]["detached_verification_evidence_validation_waiting_count"] == 0 + assert validation["summary"]["authorization_signed_receipt_evidence_intake_ready_count"] == 1 + assert validation["summary"]["signed_receipt_evidence_intake_check_count"] == 12 + assert validation["summary"]["detached_verification_evidence_schema_count"] == 1 + assert validation["summary"]["verifier_receipt_closeout_boundary_count"] == 1 + assert validation["summary"]["required_external_receipt_evidence_count"] == 10 + assert validation["summary"]["external_receipt_acceptance_gate_count"] == 8 + assert validation["summary"]["detached_receipt_verification_check_count"] == 10 + assert validation["summary"]["detached_verification_evidence_field_count"] == 12 + assert validation["summary"]["detached_verification_acceptance_gate_count"] == 10 + assert validation["summary"]["verifier_receipt_field_count"] == 12 + assert validation["summary"]["verifier_receipt_acceptance_gate_count"] == 10 + assert validation["summary"]["post_apply_verifier_required_count"] == 1 + assert validation["summary"]["same_run_truth_required_count"] == 1 + assert validation["summary"]["signs_database_apply_authorization_count"] == 0 + assert validation["summary"]["reads_secret_count"] == 0 + assert validation["summary"]["executes_script_count"] == 0 + assert validation["summary"]["executes_sql_count"] == 0 + assert validation["summary"]["writes_database_count"] == 0 + assert future_validation["validation_id"].startswith( + "pchome-db-apply-authorization-detached-verification-evidence-validation-" + ) + assert future_validation["source_signed_receipt_evidence_intake_id"].startswith( + "pchome-db-apply-authorization-signed-receipt-evidence-intake-" + ) + assert ( + future_validation["ready_for_future_detached_verification_evidence_validation"] + is True + ) + assert future_validation["can_enter_future_verifier_receipt_closeout"] is True + assert future_validation["verifier_receipt_closeout_boundary_ready"] is True + assert future_validation["ready_for_database_apply_now"] is False + assert future_validation["issues_database_apply_authorization"] is False + assert future_validation["signs_database_apply_authorization"] is False + assert future_validation["detached_signature_verification_performed"] is False + assert future_validation["verifier_receipt_persisted"] is False + assert future_validation["external_signed_authorization_receipt_included"] is False + assert future_validation["signed_authorization_receipt_included"] is False + assert future_validation["signature_material_included"] is False + assert future_validation["secret_material_included"] is False + assert boundary["boundary_id"].startswith( + "pchome-db-apply-authorization-detached-verification-evidence-validation-" + ) + assert boundary["authorization_material_type"] == "verifier_receipt_closeout_boundary" + assert boundary["ready_for_future_verifier_receipt_closeout_boundary"] is True + assert boundary["required_external_receipt_evidence_count"] == 10 + assert boundary["external_receipt_acceptance_gate_count"] == 8 + assert boundary["detached_receipt_verification_check_count"] == 10 + assert boundary["detached_verification_evidence_field_count"] == 12 + assert boundary["detached_verification_acceptance_gate_count"] == 10 + assert boundary["verifier_receipt_field_count"] == 12 + assert boundary["verifier_receipt_acceptance_gate_count"] == 10 + assert "verifier_receipt_sha256" in boundary["verifier_receipt_fields"] + assert "detached_signature_verification_status_passed" in ( + boundary["verifier_receipt_acceptance_gates"] + ) + assert boundary["requires_detached_signature_verification"] is True + assert boundary["detached_signature_verification_performed"] is False + assert boundary["verifier_receipt_persisted"] is False + assert boundary["external_signed_authorization_receipt_required_in_future"] is True + assert boundary["external_signed_authorization_receipt_included"] is False + assert boundary["signed_authorization_receipt_included"] is False + assert boundary["signature_material_included"] is False + assert boundary["secret_material_included"] is False + assert boundary["accepts_plaintext_secret"] is False + assert boundary["reads_secret_in_preview"] is False + assert boundary["executes_shell_in_preview"] is False + assert boundary["executes_sql_in_preview"] is False + assert boundary["writes_database_in_preview"] is False + assert boundary["ready_for_database_apply_now"] is False + assert boundary["signs_database_apply_authorization"] is False + assert boundary["target_file"] == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + assert boundary["hash_matches"] is True + assert boundary["requires_fresh_production_truth_in_same_run"] is True + assert boundary["requires_post_apply_verifier"] is True + assert contract["permits_future_verifier_receipt_closeout"] is True + assert contract["accepts_plaintext_secret"] is False + assert contract["performs_detached_signature_verification"] is False + assert contract["persists_verifier_receipt"] is False + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert "verifier_receipt_closeout_boundary_contract_complete" in check_keys + assert "secret_and_signed_material_boundary_enforced" in check_keys + assert "preview_has_no_side_effects_no_verification_no_signing" in check_keys + assert validation["safety"]["reads_secret_in_preview"] is False + assert validation["safety"]["performs_detached_signature_verification"] is False + assert validation["safety"]["persists_verifier_receipt"] is False + assert validation["safety"]["signs_database_apply_authorization"] is False + assert validation["safety"]["executes_script"] is False + assert validation["safety"]["executes_sql"] is False + assert validation["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_verifier_receipt_closeout_waits_without_ready_validation(): + closeout = build_pchome_auto_policy_db_apply_authorization_verifier_receipt_closeout( + _payload(), + batch_size=1, + ) + + future_closeout = closeout["future_verifier_receipt_closeout"] + handoff = closeout["verifier_receipt_evidence_handoff"] + contract = closeout["verifier_receipt_closeout_contract"] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_verifier_receipt_closeout" + ) + assert ( + closeout["result"] + == "WAITING_FOR_DB_APPLY_AUTHORIZATION_DETACHED_VERIFICATION_EVIDENCE_VALIDATION" + ) + assert closeout["summary"]["authorization_verifier_receipt_closeout_ready_count"] == 0 + assert closeout["summary"]["verifier_receipt_closeout_check_count"] == 12 + assert closeout["summary"]["verifier_receipt_closeout_waiting_count"] > 0 + assert ( + closeout["summary"][ + "authorization_detached_verification_evidence_validation_ready_count" + ] + == 0 + ) + assert closeout["summary"]["detached_verification_evidence_validation_check_count"] == 12 + assert closeout["summary"]["verifier_receipt_closeout_boundary_count"] == 1 + assert closeout["summary"]["verifier_receipt_evidence_handoff_count"] == 1 + assert closeout["summary"]["verifier_receipt_field_count"] == 12 + assert closeout["summary"]["verifier_receipt_acceptance_gate_count"] == 10 + assert closeout["summary"]["verifier_receipt_evidence_handoff_field_count"] == 12 + assert closeout["summary"]["verifier_receipt_handoff_acceptance_gate_count"] == 10 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_script_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert future_closeout["ready_for_future_verifier_receipt_closeout"] is False + assert ( + future_closeout[ + "can_enter_future_database_apply_authorization_verifier_handoff" + ] + is False + ) + assert future_closeout["verifier_receipt_evidence_handoff_ready"] is False + assert future_closeout["ready_for_database_apply_now"] is False + assert future_closeout["issues_database_apply_authorization"] is False + assert future_closeout["signs_database_apply_authorization"] is False + assert future_closeout["detached_signature_verification_performed"] is False + assert future_closeout["verifier_receipt_persisted"] is False + assert future_closeout["external_signed_authorization_receipt_included"] is False + assert future_closeout["signed_authorization_receipt_included"] is False + assert future_closeout["signature_material_included"] is False + assert future_closeout["secret_material_included"] is False + assert handoff["authorization_material_type"] == "verifier_receipt_evidence_handoff" + assert handoff["ready_for_future_verifier_receipt_evidence_handoff"] is False + assert handoff["verifier_receipt_field_count"] == 12 + assert handoff["verifier_receipt_acceptance_gate_count"] == 10 + assert handoff["verifier_receipt_evidence_handoff_field_count"] == 12 + assert handoff["verifier_receipt_handoff_acceptance_gate_count"] == 10 + assert handoff["requires_detached_signature_verification"] is True + assert handoff["detached_signature_verification_performed"] is False + assert handoff["verifier_receipt_persisted"] is False + assert handoff["external_signed_authorization_receipt_included"] is False + assert handoff["signed_authorization_receipt_included"] is False + assert handoff["signature_material_included"] is False + assert handoff["secret_material_included"] is False + assert handoff["accepts_plaintext_secret"] is False + assert handoff["reads_secret_in_preview"] is False + assert handoff["executes_shell_in_preview"] is False + assert handoff["executes_sql_in_preview"] is False + assert handoff["writes_database_in_preview"] is False + assert handoff["ready_for_database_apply_now"] is False + assert handoff["signs_database_apply_authorization"] is False + assert contract["permits_future_database_apply_authorization_verifier_handoff"] is False + assert contract["accepts_plaintext_secret"] is False + assert contract["performs_detached_signature_verification"] is False + assert contract["persists_verifier_receipt"] is False + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["performs_detached_signature_verification"] is False + assert closeout["safety"]["persists_verifier_receipt"] is False + assert closeout["safety"]["signs_database_apply_authorization"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_verifier_receipt_closeout_ready_after_fake_fetch_but_no_receipt_persist(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = build_pchome_auto_policy_db_apply_authorization_verifier_receipt_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + future_closeout = closeout["future_verifier_receipt_closeout"] + handoff = closeout["verifier_receipt_evidence_handoff"] + contract = closeout["verifier_receipt_closeout_contract"] + check_keys = [check["key"] for check in closeout["verifier_receipt_closeout_checks"]] + assert closeout["result"] == "DB_APPLY_AUTHORIZATION_VERIFIER_RECEIPT_CLOSEOUT_READY" + assert closeout["summary"]["authorization_verifier_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["verifier_receipt_closeout_check_count"] == 12 + assert closeout["summary"]["verifier_receipt_closeout_pass_count"] == 12 + assert closeout["summary"]["verifier_receipt_closeout_waiting_count"] == 0 + assert ( + closeout["summary"][ + "authorization_detached_verification_evidence_validation_ready_count" + ] + == 1 + ) + assert closeout["summary"]["detached_verification_evidence_validation_check_count"] == 12 + assert closeout["summary"]["authorization_signed_receipt_evidence_intake_ready_count"] == 1 + assert closeout["summary"]["signed_receipt_evidence_intake_check_count"] == 12 + assert closeout["summary"]["verifier_receipt_closeout_boundary_count"] == 1 + assert closeout["summary"]["verifier_receipt_evidence_handoff_count"] == 1 + assert closeout["summary"]["required_external_receipt_evidence_count"] == 10 + assert closeout["summary"]["external_receipt_acceptance_gate_count"] == 8 + assert closeout["summary"]["verifier_receipt_field_count"] == 12 + assert closeout["summary"]["verifier_receipt_acceptance_gate_count"] == 10 + assert closeout["summary"]["verifier_receipt_evidence_handoff_field_count"] == 12 + assert closeout["summary"]["verifier_receipt_handoff_acceptance_gate_count"] == 10 + assert closeout["summary"]["detached_verification_evidence_field_count"] == 12 + assert closeout["summary"]["detached_verification_acceptance_gate_count"] == 10 + assert closeout["summary"]["post_apply_verifier_required_count"] == 1 + assert closeout["summary"]["same_run_truth_required_count"] == 1 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_script_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert future_closeout["closeout_id"].startswith( + "pchome-db-apply-authorization-verifier-receipt-closeout-" + ) + assert future_closeout["source_detached_verification_evidence_validation_id"].startswith( + "pchome-db-apply-authorization-detached-verification-evidence-validation-" + ) + assert future_closeout["ready_for_future_verifier_receipt_closeout"] is True + assert ( + future_closeout[ + "can_enter_future_database_apply_authorization_verifier_handoff" + ] + is True + ) + assert future_closeout["verifier_receipt_evidence_handoff_ready"] is True + assert future_closeout["ready_for_database_apply_now"] is False + assert future_closeout["issues_database_apply_authorization"] is False + assert future_closeout["signs_database_apply_authorization"] is False + assert future_closeout["detached_signature_verification_performed"] is False + assert future_closeout["verifier_receipt_persisted"] is False + assert future_closeout["external_signed_authorization_receipt_included"] is False + assert future_closeout["signed_authorization_receipt_included"] is False + assert future_closeout["signature_material_included"] is False + assert future_closeout["secret_material_included"] is False + assert handoff["handoff_id"].startswith( + "pchome-db-apply-authorization-verifier-receipt-closeout-" + ) + assert handoff["authorization_material_type"] == "verifier_receipt_evidence_handoff" + assert handoff["ready_for_future_verifier_receipt_evidence_handoff"] is True + assert handoff["required_external_receipt_evidence_count"] == 10 + assert handoff["external_receipt_acceptance_gate_count"] == 8 + assert handoff["verifier_receipt_field_count"] == 12 + assert handoff["verifier_receipt_acceptance_gate_count"] == 10 + assert handoff["verifier_receipt_evidence_handoff_field_count"] == 12 + assert handoff["verifier_receipt_handoff_acceptance_gate_count"] == 10 + assert "verifier_receipt_sha256" in handoff["verifier_receipt_evidence_handoff_fields"] + assert "verifier_receipt_not_persisted_by_preview" in ( + handoff["verifier_receipt_handoff_acceptance_gates"] + ) + assert handoff["requires_detached_signature_verification"] is True + assert handoff["detached_signature_verification_performed"] is False + assert handoff["verifier_receipt_persisted"] is False + assert handoff["external_signed_authorization_receipt_required_in_future"] is True + assert handoff["external_signed_authorization_receipt_included"] is False + assert handoff["signed_authorization_receipt_included"] is False + assert handoff["signature_material_included"] is False + assert handoff["secret_material_included"] is False + assert handoff["accepts_plaintext_secret"] is False + assert handoff["reads_secret_in_preview"] is False + assert handoff["executes_shell_in_preview"] is False + assert handoff["executes_sql_in_preview"] is False + assert handoff["writes_database_in_preview"] is False + assert handoff["ready_for_database_apply_now"] is False + assert handoff["signs_database_apply_authorization"] is False + assert handoff["target_file"] == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + assert handoff["hash_matches"] is True + assert handoff["requires_fresh_production_truth_in_same_run"] is True + assert handoff["requires_post_apply_verifier"] is True + assert contract["permits_future_database_apply_authorization_verifier_handoff"] is True + assert contract["accepts_plaintext_secret"] is False + assert contract["performs_detached_signature_verification"] is False + assert contract["persists_verifier_receipt"] is False + assert contract["issues_database_apply_authorization"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert "verifier_receipt_evidence_handoff_contract_complete" in check_keys + assert "secret_signed_material_and_receipt_persistence_boundary_enforced" in check_keys + assert "preview_has_no_side_effects_no_verification_no_signing" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["performs_detached_signature_verification"] is False + assert closeout["safety"]["persists_verifier_receipt"] is False + assert closeout["safety"]["signs_database_apply_authorization"] is False + assert closeout["safety"]["executes_script"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_evidence_execution_preflight_waits_without_ready_closeout(): + preflight = build_pchome_auto_policy_db_apply_authorization_evidence_execution_preflight( + _payload(), + batch_size=1, + ) + + handoff = preflight["future_database_apply_authorization_verifier_handoff"] + package = preflight["authorization_evidence_execution_preflight"] + contract = preflight["authorization_evidence_execution_preflight_contract"] + check_keys = [ + check["key"] + for check in preflight["authorization_evidence_execution_preflight_checks"] + ] + assert preflight["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_evidence_execution_preflight" + ) + assert ( + preflight["result"] + == "WAITING_FOR_DB_APPLY_AUTHORIZATION_VERIFIER_RECEIPT_CLOSEOUT" + ) + assert preflight["summary"]["authorization_evidence_execution_preflight_ready_count"] == 0 + assert preflight["summary"]["authorization_evidence_execution_preflight_check_count"] == 12 + assert preflight["summary"]["authorization_evidence_execution_preflight_waiting_count"] > 0 + assert preflight["summary"]["authorization_verifier_receipt_closeout_ready_count"] == 0 + assert preflight["summary"]["verifier_receipt_closeout_check_count"] == 12 + assert ( + preflight["summary"][ + "authorization_detached_verification_evidence_validation_ready_count" + ] + == 0 + ) + assert preflight["summary"]["detached_verification_evidence_validation_check_count"] == 12 + assert preflight["summary"]["verifier_receipt_evidence_handoff_count"] == 1 + assert preflight["summary"]["authorization_evidence_execution_preflight_count"] == 1 + assert preflight["summary"]["authorization_evidence_execution_field_count"] == 12 + assert preflight["summary"]["authorization_evidence_execution_acceptance_gate_count"] == 10 + assert preflight["summary"]["verifier_receipt_field_count"] == 12 + assert preflight["summary"]["verifier_receipt_acceptance_gate_count"] == 10 + assert preflight["summary"]["verifier_receipt_evidence_handoff_field_count"] == 12 + assert preflight["summary"]["verifier_receipt_handoff_acceptance_gate_count"] == 10 + assert preflight["summary"]["reads_secret_count"] == 0 + assert preflight["summary"]["executes_script_count"] == 0 + assert preflight["summary"]["executes_migration_count"] == 0 + assert preflight["summary"]["executes_endpoint_count"] == 0 + assert preflight["summary"]["executes_sql_count"] == 0 + assert preflight["summary"]["writes_database_count"] == 0 + assert preflight["summary"]["signs_database_apply_authorization_count"] == 0 + assert handoff["preflight_id"].startswith( + "pchome-db-apply-authorization-evidence-execution-preflight-" + ) + assert ( + handoff["ready_for_future_database_apply_authorization_verifier_handoff"] + is False + ) + assert ( + handoff["can_enter_future_authorization_evidence_execution_closeout"] + is False + ) + assert handoff["authorization_evidence_execution_preflight_ready"] is False + assert handoff["ready_for_database_apply_now"] is False + assert handoff["issues_database_apply_authorization"] is False + assert handoff["signs_database_apply_authorization"] is False + assert handoff["executes_authorization_evidence"] is False + assert handoff["detached_signature_verification_performed"] is False + assert handoff["verifier_receipt_persisted"] is False + assert handoff["external_signed_authorization_receipt_included"] is False + assert handoff["signed_authorization_receipt_included"] is False + assert handoff["signature_material_included"] is False + assert handoff["secret_material_included"] is False + assert package["authorization_material_type"] == ( + "authorization_evidence_execution_preflight" + ) + assert package["ready_for_future_authorization_evidence_execution_preflight"] is False + assert package["authorization_evidence_execution_field_count"] == 12 + assert package["authorization_evidence_execution_acceptance_gate_count"] == 10 + assert package["verifier_receipt_field_count"] == 12 + assert package["verifier_receipt_acceptance_gate_count"] == 10 + assert package["verifier_receipt_evidence_handoff_field_count"] == 12 + assert package["verifier_receipt_handoff_acceptance_gate_count"] == 10 + assert package["requires_detached_signature_verification"] is True + assert package["detached_signature_verification_performed"] is False + assert package["verifier_receipt_persisted"] is False + assert package["external_signed_authorization_receipt_included"] is False + assert package["signed_authorization_receipt_included"] is False + assert package["signature_material_included"] is False + assert package["secret_material_included"] is False + assert package["accepts_plaintext_secret"] is False + assert package["reads_secret_in_preview"] is False + assert package["executes_shell_in_preview"] is False + assert package["executes_sql_in_preview"] is False + assert package["writes_database_in_preview"] is False + assert package["executes_authorization_evidence"] is False + assert package["ready_for_database_apply_now"] is False + assert package["signs_database_apply_authorization"] is False + assert contract["permits_future_authorization_evidence_execution_closeout"] is False + assert contract["accepts_plaintext_secret"] is False + assert contract["performs_detached_signature_verification"] is False + assert contract["persists_verifier_receipt"] is False + assert contract["executes_authorization_evidence"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert "verifier_receipt_closeout_ready" in check_keys + assert "authorization_evidence_execution_preflight_contract_complete" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert preflight["safety"]["reads_secret_in_preview"] is False + assert preflight["safety"]["performs_detached_signature_verification"] is False + assert preflight["safety"]["persists_verifier_receipt"] is False + assert preflight["safety"]["executes_authorization_evidence"] is False + assert preflight["safety"]["signs_database_apply_authorization"] is False + assert preflight["safety"]["executes_sql"] is False + assert preflight["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_evidence_execution_preflight_ready_after_fake_fetch_but_no_execution(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + preflight = build_pchome_auto_policy_db_apply_authorization_evidence_execution_preflight( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + handoff = preflight["future_database_apply_authorization_verifier_handoff"] + package = preflight["authorization_evidence_execution_preflight"] + contract = preflight["authorization_evidence_execution_preflight_contract"] + check_keys = [ + check["key"] + for check in preflight["authorization_evidence_execution_preflight_checks"] + ] + assert ( + preflight["result"] + == "DB_APPLY_AUTHORIZATION_EVIDENCE_EXECUTION_PREFLIGHT_READY" + ) + assert preflight["summary"]["authorization_evidence_execution_preflight_ready_count"] == 1 + assert preflight["summary"]["authorization_evidence_execution_preflight_check_count"] == 12 + assert preflight["summary"]["authorization_evidence_execution_preflight_pass_count"] == 12 + assert preflight["summary"]["authorization_evidence_execution_preflight_waiting_count"] == 0 + assert preflight["summary"]["authorization_verifier_receipt_closeout_ready_count"] == 1 + assert preflight["summary"]["verifier_receipt_closeout_check_count"] == 12 + assert ( + preflight["summary"][ + "authorization_detached_verification_evidence_validation_ready_count" + ] + == 1 + ) + assert preflight["summary"]["detached_verification_evidence_validation_check_count"] == 12 + assert preflight["summary"]["authorization_evidence_execution_preflight_count"] == 1 + assert preflight["summary"]["authorization_evidence_execution_field_count"] == 12 + assert preflight["summary"]["authorization_evidence_execution_acceptance_gate_count"] == 10 + assert preflight["summary"]["verifier_receipt_field_count"] == 12 + assert preflight["summary"]["verifier_receipt_acceptance_gate_count"] == 10 + assert preflight["summary"]["verifier_receipt_evidence_handoff_field_count"] == 12 + assert preflight["summary"]["verifier_receipt_handoff_acceptance_gate_count"] == 10 + assert preflight["summary"]["required_external_receipt_evidence_count"] == 10 + assert preflight["summary"]["external_receipt_acceptance_gate_count"] == 8 + assert preflight["summary"]["post_apply_verifier_required_count"] == 1 + assert preflight["summary"]["same_run_truth_required_count"] == 1 + assert preflight["summary"]["reads_secret_count"] == 0 + assert preflight["summary"]["executes_script_count"] == 0 + assert preflight["summary"]["executes_migration_count"] == 0 + assert preflight["summary"]["executes_endpoint_count"] == 0 + assert preflight["summary"]["executes_sql_count"] == 0 + assert preflight["summary"]["writes_database_count"] == 0 + assert preflight["summary"]["signs_database_apply_authorization_count"] == 0 + assert handoff["preflight_id"].startswith( + "pchome-db-apply-authorization-evidence-execution-preflight-" + ) + assert handoff["source_verifier_receipt_closeout_id"].startswith( + "pchome-db-apply-authorization-verifier-receipt-closeout-" + ) + assert ( + handoff["source_verifier_receipt_evidence_handoff_id"].startswith( + "pchome-db-apply-authorization-verifier-receipt-closeout-" + ) + ) + assert ( + handoff["ready_for_future_database_apply_authorization_verifier_handoff"] + is True + ) + assert ( + handoff["can_enter_future_authorization_evidence_execution_closeout"] + is True + ) + assert handoff["authorization_evidence_execution_preflight_ready"] is True + assert handoff["ready_for_database_apply_now"] is False + assert handoff["issues_database_apply_authorization"] is False + assert handoff["signs_database_apply_authorization"] is False + assert handoff["executes_authorization_evidence"] is False + assert handoff["detached_signature_verification_performed"] is False + assert handoff["verifier_receipt_persisted"] is False + assert handoff["external_signed_authorization_receipt_included"] is False + assert handoff["signed_authorization_receipt_included"] is False + assert handoff["signature_material_included"] is False + assert handoff["secret_material_included"] is False + assert package["preflight_id"].startswith( + "pchome-db-apply-authorization-evidence-execution-preflight-" + ) + assert package["authorization_material_type"] == ( + "authorization_evidence_execution_preflight" + ) + assert package["ready_for_future_authorization_evidence_execution_preflight"] is True + assert package["authorization_evidence_execution_field_count"] == 12 + assert package["authorization_evidence_execution_acceptance_gate_count"] == 10 + assert package["verifier_receipt_field_count"] == 12 + assert package["verifier_receipt_acceptance_gate_count"] == 10 + assert package["verifier_receipt_evidence_handoff_field_count"] == 12 + assert package["verifier_receipt_handoff_acceptance_gate_count"] == 10 + assert "verifier_receipt_sha256" in package["authorization_evidence_execution_fields"] + assert "no_secret_signature_or_database_write_in_preflight" in ( + package["authorization_evidence_execution_acceptance_gates"] + ) + assert package["requires_detached_signature_verification"] is True + assert package["detached_signature_verification_performed"] is False + assert package["verifier_receipt_persisted"] is False + assert package["external_signed_authorization_receipt_included"] is False + assert package["signed_authorization_receipt_included"] is False + assert package["signature_material_included"] is False + assert package["secret_material_included"] is False + assert package["accepts_plaintext_secret"] is False + assert package["reads_secret_in_preview"] is False + assert package["executes_shell_in_preview"] is False + assert package["executes_sql_in_preview"] is False + assert package["writes_database_in_preview"] is False + assert package["executes_authorization_evidence"] is False + assert package["ready_for_database_apply_now"] is False + assert package["signs_database_apply_authorization"] is False + assert package["target_file"] == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + assert package["hash_matches"] is True + assert package["requires_fresh_production_truth_in_same_run"] is True + assert package["requires_post_apply_verifier"] is True + assert contract["permits_future_authorization_evidence_execution_closeout"] is True + assert contract["accepts_plaintext_secret"] is False + assert contract["performs_detached_signature_verification"] is False + assert contract["persists_verifier_receipt"] is False + assert contract["executes_authorization_evidence"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert "verifier_receipt_closeout_ready" in check_keys + assert "source_chain_ids_present" in check_keys + assert "closeout_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert preflight["safety"]["reads_secret_in_preview"] is False + assert preflight["safety"]["performs_detached_signature_verification"] is False + assert preflight["safety"]["persists_verifier_receipt"] is False + assert preflight["safety"]["executes_authorization_evidence"] is False + assert preflight["safety"]["signs_database_apply_authorization"] is False + assert preflight["safety"]["executes_sql"] is False + assert preflight["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_evidence_execution_closeout_waits_without_ready_preflight(): + closeout = build_pchome_auto_policy_db_apply_authorization_evidence_execution_closeout( + _payload(), + batch_size=1, + ) + + final_gate = closeout["future_database_apply_authorization_final_verifier_gate"] + package = closeout["authorization_evidence_execution_closeout"] + contract = closeout["authorization_evidence_execution_closeout_contract"] + check_keys = [ + check["key"] + for check in closeout["authorization_evidence_execution_closeout_checks"] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_evidence_execution_closeout" + ) + assert ( + closeout["result"] + == "WAITING_FOR_DB_APPLY_AUTHORIZATION_EVIDENCE_EXECUTION_PREFLIGHT" + ) + assert closeout["summary"]["authorization_evidence_execution_closeout_ready_count"] == 0 + assert closeout["summary"]["authorization_evidence_execution_closeout_check_count"] == 12 + assert closeout["summary"]["authorization_evidence_execution_closeout_waiting_count"] > 0 + assert closeout["summary"]["authorization_evidence_execution_preflight_ready_count"] == 0 + assert closeout["summary"]["authorization_evidence_execution_preflight_check_count"] == 12 + assert closeout["summary"]["authorization_verifier_receipt_closeout_ready_count"] == 0 + assert closeout["summary"]["authorization_evidence_execution_closeout_count"] == 1 + assert closeout["summary"]["database_apply_final_verifier_gate_count"] == 1 + assert closeout["summary"]["database_apply_authorization_final_verifier_gate_ready_count"] == 0 + assert closeout["summary"]["authorization_evidence_execution_closeout_field_count"] == 12 + assert closeout["summary"]["authorization_evidence_execution_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["authorization_evidence_execution_field_count"] == 12 + assert closeout["summary"]["authorization_evidence_execution_acceptance_gate_count"] == 10 + assert closeout["summary"]["verifier_receipt_field_count"] == 12 + assert closeout["summary"]["verifier_receipt_acceptance_gate_count"] == 10 + assert closeout["summary"]["verifier_receipt_evidence_handoff_field_count"] == 12 + assert closeout["summary"]["verifier_receipt_handoff_acceptance_gate_count"] == 10 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_script_count"] == 0 + assert closeout["summary"]["executes_migration_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert final_gate["final_verifier_gate_id"].startswith( + "pchome-db-apply-authorization-evidence-execution-closeout-" + ) + assert ( + final_gate["ready_for_future_database_apply_authorization_final_verifier_gate"] + is False + ) + assert ( + final_gate["can_enter_future_database_apply_controlled_apply_final_preflight"] + is False + ) + assert final_gate["authorization_evidence_execution_closeout_ready"] is False + assert final_gate["final_verifier_gate_ready"] is False + assert final_gate["final_verifier_gate_executed"] is False + assert final_gate["ready_for_database_apply_now"] is False + assert final_gate["database_apply_authorized"] is False + assert final_gate["issues_database_apply_authorization"] is False + assert final_gate["signs_database_apply_authorization"] is False + assert final_gate["executes_authorization_evidence"] is False + assert final_gate["executes_database_apply"] is False + assert final_gate["detached_signature_verification_performed"] is False + assert final_gate["verifier_receipt_persisted"] is False + assert final_gate["external_signed_authorization_receipt_included"] is False + assert final_gate["signed_authorization_receipt_included"] is False + assert final_gate["signature_material_included"] is False + assert final_gate["secret_material_included"] is False + assert package["authorization_material_type"] == ( + "authorization_evidence_execution_closeout" + ) + assert package["ready_for_future_authorization_evidence_execution_closeout"] is False + assert package["authorization_evidence_execution_closeout_field_count"] == 12 + assert package["authorization_evidence_execution_closeout_acceptance_gate_count"] == 10 + assert package["authorization_evidence_execution_field_count"] == 12 + assert package["authorization_evidence_execution_acceptance_gate_count"] == 10 + assert "final_verifier_gate_endpoint" in package[ + "authorization_evidence_execution_closeout_fields" + ] + assert "no_database_apply_authorized_by_closeout" in package[ + "authorization_evidence_execution_closeout_acceptance_gates" + ] + assert package["requires_detached_signature_verification"] is True + assert package["detached_signature_verification_performed"] is False + assert package["verifier_receipt_persisted"] is False + assert package["external_signed_authorization_receipt_included"] is False + assert package["signed_authorization_receipt_included"] is False + assert package["signature_material_included"] is False + assert package["secret_material_included"] is False + assert package["accepts_plaintext_secret"] is False + assert package["reads_secret_in_preview"] is False + assert package["executes_endpoint_in_preview"] is False + assert package["executes_sql_in_preview"] is False + assert package["writes_database_in_preview"] is False + assert package["executes_authorization_evidence"] is False + assert package["executes_database_apply"] is False + assert package["ready_for_database_apply_now"] is False + assert package["database_apply_authorized"] is False + assert package["signs_database_apply_authorization"] is False + assert contract["permits_future_database_apply_authorization_final_verifier_gate"] is False + assert contract["permits_future_database_apply_controlled_apply_final_preflight"] is False + assert contract["executes_authorization_evidence"] is False + assert contract["executes_database_apply"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert "authorization_evidence_execution_preflight_ready" in check_keys + assert "authorization_evidence_execution_closeout_contract_complete" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["performs_detached_signature_verification"] is False + assert closeout["safety"]["persists_verifier_receipt"] is False + assert closeout["safety"]["executes_authorization_evidence"] is False + assert closeout["safety"]["executes_database_apply"] is False + assert closeout["safety"]["signs_database_apply_authorization"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_evidence_execution_closeout_ready_after_fake_fetch_but_no_execution(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = build_pchome_auto_policy_db_apply_authorization_evidence_execution_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + final_gate = closeout["future_database_apply_authorization_final_verifier_gate"] + package = closeout["authorization_evidence_execution_closeout"] + contract = closeout["authorization_evidence_execution_closeout_contract"] + check_keys = [ + check["key"] + for check in closeout["authorization_evidence_execution_closeout_checks"] + ] + assert ( + closeout["result"] + == "DB_APPLY_AUTHORIZATION_EVIDENCE_EXECUTION_CLOSEOUT_READY" + ) + assert closeout["summary"]["authorization_evidence_execution_closeout_ready_count"] == 1 + assert closeout["summary"]["authorization_evidence_execution_closeout_check_count"] == 12 + assert closeout["summary"]["authorization_evidence_execution_closeout_pass_count"] == 12 + assert closeout["summary"]["authorization_evidence_execution_closeout_waiting_count"] == 0 + assert closeout["summary"]["authorization_evidence_execution_preflight_ready_count"] == 1 + assert closeout["summary"]["authorization_evidence_execution_preflight_check_count"] == 12 + assert closeout["summary"]["authorization_verifier_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["verifier_receipt_closeout_check_count"] == 12 + assert ( + closeout["summary"][ + "authorization_detached_verification_evidence_validation_ready_count" + ] + == 1 + ) + assert closeout["summary"]["detached_verification_evidence_validation_check_count"] == 12 + assert closeout["summary"]["authorization_evidence_execution_preflight_count"] == 1 + assert closeout["summary"]["authorization_evidence_execution_closeout_count"] == 1 + assert closeout["summary"]["database_apply_final_verifier_gate_count"] == 1 + assert closeout["summary"]["database_apply_authorization_final_verifier_gate_ready_count"] == 1 + assert closeout["summary"]["authorization_evidence_execution_closeout_field_count"] == 12 + assert closeout["summary"]["authorization_evidence_execution_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["authorization_evidence_execution_field_count"] == 12 + assert closeout["summary"]["authorization_evidence_execution_acceptance_gate_count"] == 10 + assert closeout["summary"]["verifier_receipt_field_count"] == 12 + assert closeout["summary"]["verifier_receipt_acceptance_gate_count"] == 10 + assert closeout["summary"]["verifier_receipt_evidence_handoff_field_count"] == 12 + assert closeout["summary"]["verifier_receipt_handoff_acceptance_gate_count"] == 10 + assert closeout["summary"]["required_external_receipt_evidence_count"] == 10 + assert closeout["summary"]["external_receipt_acceptance_gate_count"] == 8 + assert closeout["summary"]["post_apply_verifier_required_count"] == 1 + assert closeout["summary"]["same_run_truth_required_count"] == 1 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_script_count"] == 0 + assert closeout["summary"]["executes_migration_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert final_gate["final_verifier_gate_id"].startswith( + "pchome-db-apply-authorization-evidence-execution-closeout-" + ) + assert ( + final_gate["source_authorization_evidence_execution_preflight_id"].startswith( + "pchome-db-apply-authorization-evidence-execution-preflight-" + ) + ) + assert final_gate["source_verifier_receipt_closeout_id"].startswith( + "pchome-db-apply-authorization-verifier-receipt-closeout-" + ) + assert ( + final_gate["ready_for_future_database_apply_authorization_final_verifier_gate"] + is True + ) + assert ( + final_gate["can_enter_future_database_apply_controlled_apply_final_preflight"] + is True + ) + assert final_gate["authorization_evidence_execution_closeout_ready"] is True + assert final_gate["final_verifier_gate_ready"] is True + assert final_gate["final_verifier_gate_executed"] is False + assert final_gate["ready_for_database_apply_now"] is False + assert final_gate["database_apply_authorized"] is False + assert final_gate["issues_database_apply_authorization"] is False + assert final_gate["signs_database_apply_authorization"] is False + assert final_gate["executes_authorization_evidence"] is False + assert final_gate["executes_database_apply"] is False + assert final_gate["detached_signature_verification_performed"] is False + assert final_gate["verifier_receipt_persisted"] is False + assert final_gate["external_signed_authorization_receipt_included"] is False + assert final_gate["signed_authorization_receipt_included"] is False + assert final_gate["signature_material_included"] is False + assert final_gate["secret_material_included"] is False + assert package["closeout_id"].startswith( + "pchome-db-apply-authorization-evidence-execution-closeout-" + ) + assert package["authorization_material_type"] == ( + "authorization_evidence_execution_closeout" + ) + assert package["ready_for_future_authorization_evidence_execution_closeout"] is True + assert package["authorization_evidence_execution_closeout_field_count"] == 12 + assert package["authorization_evidence_execution_closeout_acceptance_gate_count"] == 10 + assert package["authorization_evidence_execution_field_count"] == 12 + assert package["authorization_evidence_execution_acceptance_gate_count"] == 10 + assert "final_verifier_gate_endpoint" in package[ + "authorization_evidence_execution_closeout_fields" + ] + assert "no_database_apply_authorized_by_closeout" in package[ + "authorization_evidence_execution_closeout_acceptance_gates" + ] + assert package["requires_detached_signature_verification"] is True + assert package["detached_signature_verification_performed"] is False + assert package["verifier_receipt_persisted"] is False + assert package["external_signed_authorization_receipt_included"] is False + assert package["signed_authorization_receipt_included"] is False + assert package["signature_material_included"] is False + assert package["secret_material_included"] is False + assert package["accepts_plaintext_secret"] is False + assert package["reads_secret_in_preview"] is False + assert package["executes_endpoint_in_preview"] is False + assert package["executes_sql_in_preview"] is False + assert package["writes_database_in_preview"] is False + assert package["executes_authorization_evidence"] is False + assert package["executes_database_apply"] is False + assert package["ready_for_database_apply_now"] is False + assert package["database_apply_authorized"] is False + assert package["signs_database_apply_authorization"] is False + assert package["target_file"] == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + assert package["hash_matches"] is True + assert package["requires_fresh_production_truth_in_same_run"] is True + assert package["requires_post_apply_verifier"] is True + assert contract["permits_future_database_apply_authorization_final_verifier_gate"] is True + assert contract["permits_future_database_apply_controlled_apply_final_preflight"] is True + assert contract["executes_authorization_evidence"] is False + assert contract["executes_database_apply"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert "authorization_evidence_execution_preflight_ready" in check_keys + assert "final_verifier_handoff_ready" in check_keys + assert "verifier_hash_and_receipt_chain_locked" in check_keys + assert "preflight_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["performs_detached_signature_verification"] is False + assert closeout["safety"]["persists_verifier_receipt"] is False + assert closeout["safety"]["executes_authorization_evidence"] is False + assert closeout["safety"]["executes_database_apply"] is False + assert closeout["safety"]["signs_database_apply_authorization"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_apply_final_preflight_waits_without_ready_final_gate(): + preflight = build_pchome_auto_policy_db_apply_controlled_apply_final_preflight( + _payload(), + batch_size=1, + ) + + future_preflight = preflight["future_database_apply_controlled_apply_final_preflight"] + package = preflight["controlled_apply_final_preflight"] + contract = preflight["controlled_apply_final_preflight_contract"] + rollback_binding = package["rollback_binding"] + verifier_binding = package["post_apply_verifier_binding"] + check_keys = [check["key"] for check in preflight["controlled_apply_final_preflight_checks"]] + assert preflight["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_apply_final_preflight" + ) + assert ( + preflight["result"] + == "WAITING_FOR_DB_APPLY_AUTHORIZATION_EVIDENCE_EXECUTION_CLOSEOUT" + ) + assert preflight["summary"]["controlled_apply_final_preflight_ready_count"] == 0 + assert preflight["summary"]["controlled_apply_final_preflight_check_count"] == 12 + assert preflight["summary"]["controlled_apply_final_preflight_waiting_count"] > 0 + assert preflight["summary"]["authorization_evidence_execution_closeout_ready_count"] == 0 + assert preflight["summary"]["authorization_evidence_execution_closeout_check_count"] == 12 + assert preflight["summary"]["database_apply_final_verifier_gate_count"] == 1 + assert preflight["summary"]["database_apply_authorization_final_verifier_gate_ready_count"] == 0 + assert preflight["summary"]["controlled_apply_final_preflight_count"] == 1 + assert preflight["summary"]["controlled_apply_final_preflight_field_count"] == 12 + assert preflight["summary"]["controlled_apply_final_preflight_acceptance_gate_count"] == 10 + assert preflight["summary"]["rollback_binding_count"] == 1 + assert preflight["summary"]["rollback_binding_field_count"] == 8 + assert preflight["summary"]["post_apply_verifier_binding_count"] == 1 + assert preflight["summary"]["post_apply_verifier_binding_field_count"] == 8 + assert preflight["summary"]["reads_secret_count"] == 0 + assert preflight["summary"]["executes_script_count"] == 0 + assert preflight["summary"]["executes_migration_count"] == 0 + assert preflight["summary"]["executes_endpoint_count"] == 0 + assert preflight["summary"]["executes_sql_count"] == 0 + assert preflight["summary"]["writes_database_count"] == 0 + assert preflight["summary"]["signs_database_apply_authorization_count"] == 0 + assert future_preflight["controlled_apply_preflight_id"].startswith( + "pchome-db-apply-controlled-apply-final-preflight-" + ) + assert ( + future_preflight["ready_for_future_database_apply_controlled_apply_final_preflight"] + is False + ) + assert ( + future_preflight["can_enter_future_database_apply_controlled_dry_run_package"] + is False + ) + assert future_preflight["controlled_apply_final_preflight_ready"] is False + assert future_preflight["rollback_binding_ready"] is False + assert future_preflight["post_apply_verifier_binding_ready"] is False + assert future_preflight["ready_for_database_apply_now"] is False + assert future_preflight["database_apply_authorized"] is False + assert future_preflight["issues_database_apply_authorization"] is False + assert future_preflight["signs_database_apply_authorization"] is False + assert future_preflight["executes_authorization_evidence"] is False + assert future_preflight["executes_database_apply"] is False + assert future_preflight["executes_endpoint"] is False + assert future_preflight["executes_sql"] is False + assert future_preflight["writes_database"] is False + assert package["authorization_material_type"] == "controlled_apply_final_preflight" + assert ( + package["ready_for_future_database_apply_controlled_apply_final_preflight"] + is False + ) + assert package["controlled_apply_final_preflight_field_count"] == 12 + assert package["controlled_apply_final_preflight_acceptance_gate_count"] == 10 + assert package["rollback_binding_count"] == 1 + assert package["rollback_binding_field_count"] == 8 + assert package["post_apply_verifier_binding_count"] == 1 + assert package["post_apply_verifier_binding_field_count"] == 8 + assert rollback_binding["rollback_execution_authorized"] is False + assert rollback_binding["rollback_executes_sql"] is False + assert rollback_binding["rollback_writes_database"] is False + assert rollback_binding["rollback_reads_secret"] is False + assert verifier_binding["verifier_must_run_after_apply"] is True + assert verifier_binding["verifier_execution_authorized_in_preview"] is False + assert verifier_binding["database_apply_authorized"] is False + assert package["dry_run_only"] is True + assert package["check_mode_only"] is True + assert package["rollback_bound"] is False + assert package["post_apply_verifier_bound"] is False + assert package["accepts_plaintext_secret"] is False + assert package["reads_secret_in_preview"] is False + assert package["signature_material_included"] is False + assert package["secret_material_included"] is False + assert package["signs_database_apply_authorization"] is False + assert package["executes_authorization_evidence"] is False + assert package["executes_database_apply"] is False + assert package["executes_endpoint_in_preview"] is False + assert package["executes_sql_in_preview"] is False + assert package["writes_database_in_preview"] is False + assert package["ready_for_database_apply_now"] is False + assert package["database_apply_authorized"] is False + assert contract["permits_future_database_apply_controlled_dry_run_package"] is False + assert contract["executes_authorization_evidence"] is False + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert "final_verifier_gate_ready" in check_keys + assert "rollback_binding_complete" in check_keys + assert "post_apply_verifier_binding_complete" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert preflight["safety"]["reads_secret_in_preview"] is False + assert preflight["safety"]["executes_endpoint"] is False + assert preflight["safety"]["executes_sql"] is False + assert preflight["safety"]["writes_database"] is False + assert preflight["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_apply_final_preflight_ready_after_fake_fetch_but_no_apply(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + preflight = build_pchome_auto_policy_db_apply_controlled_apply_final_preflight( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + future_preflight = preflight["future_database_apply_controlled_apply_final_preflight"] + package = preflight["controlled_apply_final_preflight"] + contract = preflight["controlled_apply_final_preflight_contract"] + rollback_binding = package["rollback_binding"] + verifier_binding = package["post_apply_verifier_binding"] + check_keys = [check["key"] for check in preflight["controlled_apply_final_preflight_checks"]] + assert preflight["result"] == "DB_APPLY_CONTROLLED_APPLY_FINAL_PREFLIGHT_READY" + assert preflight["summary"]["controlled_apply_final_preflight_ready_count"] == 1 + assert preflight["summary"]["controlled_apply_final_preflight_check_count"] == 12 + assert preflight["summary"]["controlled_apply_final_preflight_pass_count"] == 12 + assert preflight["summary"]["controlled_apply_final_preflight_waiting_count"] == 0 + assert preflight["summary"]["authorization_evidence_execution_closeout_ready_count"] == 1 + assert preflight["summary"]["authorization_evidence_execution_closeout_check_count"] == 12 + assert preflight["summary"]["authorization_evidence_execution_preflight_ready_count"] == 1 + assert preflight["summary"]["authorization_evidence_execution_preflight_check_count"] == 12 + assert preflight["summary"]["authorization_verifier_receipt_closeout_ready_count"] == 1 + assert preflight["summary"]["verifier_receipt_closeout_check_count"] == 12 + assert preflight["summary"]["database_apply_final_verifier_gate_count"] == 1 + assert preflight["summary"]["database_apply_authorization_final_verifier_gate_ready_count"] == 1 + assert preflight["summary"]["controlled_apply_final_preflight_count"] == 1 + assert preflight["summary"]["controlled_apply_final_preflight_field_count"] == 12 + assert preflight["summary"]["controlled_apply_final_preflight_acceptance_gate_count"] == 10 + assert preflight["summary"]["rollback_binding_count"] == 1 + assert preflight["summary"]["rollback_binding_field_count"] == 8 + assert preflight["summary"]["post_apply_verifier_binding_count"] == 1 + assert preflight["summary"]["post_apply_verifier_binding_field_count"] == 8 + assert preflight["summary"]["post_apply_verifier_required_count"] == 1 + assert preflight["summary"]["same_run_truth_required_count"] == 1 + assert preflight["summary"]["reads_secret_count"] == 0 + assert preflight["summary"]["executes_script_count"] == 0 + assert preflight["summary"]["executes_migration_count"] == 0 + assert preflight["summary"]["executes_endpoint_count"] == 0 + assert preflight["summary"]["executes_sql_count"] == 0 + assert preflight["summary"]["writes_database_count"] == 0 + assert preflight["summary"]["signs_database_apply_authorization_count"] == 0 + assert future_preflight["controlled_apply_preflight_id"].startswith( + "pchome-db-apply-controlled-apply-final-preflight-" + ) + assert future_preflight["source_final_verifier_gate_id"].startswith( + "pchome-db-apply-authorization-evidence-execution-closeout-" + ) + assert ( + future_preflight["ready_for_future_database_apply_controlled_apply_final_preflight"] + is True + ) + assert ( + future_preflight["can_enter_future_database_apply_controlled_dry_run_package"] + is True + ) + assert future_preflight["controlled_apply_final_preflight_ready"] is True + assert future_preflight["rollback_binding_ready"] is True + assert future_preflight["post_apply_verifier_binding_ready"] is True + assert future_preflight["ready_for_database_apply_now"] is False + assert future_preflight["database_apply_authorized"] is False + assert future_preflight["issues_database_apply_authorization"] is False + assert future_preflight["signs_database_apply_authorization"] is False + assert future_preflight["executes_authorization_evidence"] is False + assert future_preflight["executes_database_apply"] is False + assert future_preflight["executes_endpoint"] is False + assert future_preflight["executes_sql"] is False + assert future_preflight["writes_database"] is False + assert package["controlled_apply_preflight_id"].startswith( + "pchome-db-apply-controlled-apply-final-preflight-" + ) + assert package["authorization_material_type"] == "controlled_apply_final_preflight" + assert ( + package["ready_for_future_database_apply_controlled_apply_final_preflight"] + is True + ) + assert package["controlled_apply_final_preflight_field_count"] == 12 + assert package["controlled_apply_final_preflight_acceptance_gate_count"] == 10 + assert "rollback_binding_id" in package["controlled_apply_final_preflight_fields"] + assert "post_apply_verifier_bound" in ( + package["controlled_apply_final_preflight_acceptance_gates"] + ) + assert package["rollback_binding_count"] == 1 + assert package["rollback_binding_field_count"] == 8 + assert package["post_apply_verifier_binding_count"] == 1 + assert package["post_apply_verifier_binding_field_count"] == 8 + assert rollback_binding["rollback_execution_authorized"] is False + assert rollback_binding["rollback_executes_sql"] is False + assert rollback_binding["rollback_writes_database"] is False + assert rollback_binding["rollback_reads_secret"] is False + assert verifier_binding["verifier_must_run_after_apply"] is True + assert verifier_binding["verifier_execution_authorized_in_preview"] is False + assert verifier_binding["database_apply_authorized"] is False + assert package["target_file"] == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + assert package["hash_matches"] is True + assert package["dry_run_only"] is True + assert package["check_mode_only"] is True + assert package["rollback_bound"] is True + assert package["post_apply_verifier_bound"] is True + assert package["requires_fresh_production_truth_in_same_run"] is True + assert package["requires_post_apply_verifier"] is True + assert package["accepts_plaintext_secret"] is False + assert package["reads_secret_in_preview"] is False + assert package["signature_material_included"] is False + assert package["secret_material_included"] is False + assert package["signs_database_apply_authorization"] is False + assert package["executes_authorization_evidence"] is False + assert package["executes_database_apply"] is False + assert package["executes_endpoint_in_preview"] is False + assert package["executes_sql_in_preview"] is False + assert package["writes_database_in_preview"] is False + assert package["ready_for_database_apply_now"] is False + assert package["database_apply_authorized"] is False + assert contract["permits_future_database_apply_controlled_dry_run_package"] is True + assert contract["executes_authorization_evidence"] is False + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert "final_verifier_gate_ready" in check_keys + assert "rollback_binding_complete" in check_keys + assert "post_apply_verifier_binding_complete" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "final_verifier_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert preflight["safety"]["reads_secret_in_preview"] is False + assert preflight["safety"]["executes_endpoint"] is False + assert preflight["safety"]["executes_sql"] is False + assert preflight["safety"]["writes_database"] is False + assert preflight["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_package_waits_without_ready_final_preflight(): + package = build_pchome_auto_policy_db_apply_controlled_dry_run_package( + _payload(), + batch_size=1, + ) + + future_receipt = package[ + "future_database_apply_controlled_dry_run_execution_receipt" + ] + dry_run_package = package["controlled_dry_run_package"] + receipt = dry_run_package["dry_run_execution_receipt_preview"] + command_shape = dry_run_package["dry_run_command_shape"] + contract = package["controlled_dry_run_package_contract"] + check_keys = [check["key"] for check in package["controlled_dry_run_package_checks"]] + assert package["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_package" + ) + assert package["result"] == "WAITING_FOR_DB_APPLY_CONTROLLED_APPLY_FINAL_PREFLIGHT" + assert package["summary"]["controlled_dry_run_package_ready_count"] == 0 + assert package["summary"]["controlled_dry_run_package_check_count"] == 12 + assert package["summary"]["controlled_dry_run_package_waiting_count"] > 0 + assert package["summary"]["controlled_apply_final_preflight_check_count"] == 12 + assert package["summary"]["controlled_apply_final_preflight_ready_count"] == 0 + assert package["summary"]["controlled_dry_run_package_count"] == 1 + assert package["summary"]["controlled_dry_run_package_field_count"] == 12 + assert package["summary"]["controlled_dry_run_acceptance_gate_count"] == 10 + assert package["summary"]["dry_run_execution_receipt_preview_count"] == 1 + assert package["summary"]["dry_run_execution_receipt_field_count"] == 8 + assert package["summary"]["rollback_binding_count"] == 1 + assert package["summary"]["post_apply_verifier_binding_count"] == 1 + assert package["summary"]["reads_secret_count"] == 0 + assert package["summary"]["executes_script_count"] == 0 + assert package["summary"]["executes_migration_count"] == 0 + assert package["summary"]["executes_endpoint_count"] == 0 + assert package["summary"]["executes_sql_count"] == 0 + assert package["summary"]["writes_database_count"] == 0 + assert package["summary"]["signs_database_apply_authorization_count"] == 0 + assert future_receipt["dry_run_package_id"].startswith( + "pchome-db-apply-controlled-dry-run-package-" + ) + assert ( + future_receipt[ + "ready_for_future_database_apply_controlled_dry_run_execution_receipt" + ] + is False + ) + assert ( + future_receipt[ + "can_enter_future_database_apply_controlled_dry_run_receipt_closeout" + ] + is False + ) + assert future_receipt["controlled_dry_run_package_ready"] is False + assert future_receipt["dry_run_execution_performed"] is False + assert future_receipt["ready_for_database_apply_now"] is False + assert future_receipt["database_apply_authorized"] is False + assert future_receipt["issues_database_apply_authorization"] is False + assert future_receipt["signs_database_apply_authorization"] is False + assert future_receipt["executes_authorization_evidence"] is False + assert future_receipt["executes_database_apply"] is False + assert future_receipt["executes_endpoint"] is False + assert future_receipt["executes_sql"] is False + assert future_receipt["writes_database"] is False + assert dry_run_package["authorization_material_type"] == "controlled_dry_run_package" + assert ( + dry_run_package["ready_for_future_database_apply_controlled_dry_run_package"] + is False + ) + assert dry_run_package["controlled_dry_run_package_field_count"] == 12 + assert dry_run_package["controlled_dry_run_acceptance_gate_count"] == 10 + assert dry_run_package["dry_run_execution_receipt_preview_count"] == 1 + assert dry_run_package["dry_run_execution_receipt_field_count"] == 8 + assert dry_run_package["rollback_binding_count"] == 1 + assert dry_run_package["post_apply_verifier_binding_count"] == 1 + assert command_shape["dry_run_only"] is True + assert command_shape["check_mode_only"] is True + assert command_shape["execution_allowed"] is False + assert command_shape["shell_command_included"] is False + assert command_shape["sql_included"] is False + assert command_shape["endpoint_execution_included"] is False + assert command_shape["database_write_included"] is False + assert command_shape["requires_fresh_production_truth_in_same_run"] is True + assert command_shape["requires_rollback_binding"] is True + assert command_shape["requires_post_apply_verifier_binding"] is True + assert receipt["receipt_id"].endswith("-dry-run-receipt-preview") + assert receipt["dry_run_status"] == "preview_only_not_executed" + assert receipt["execution_performed"] is False + assert receipt["stdout_included"] is False + assert receipt["stderr_included"] is False + assert receipt["database_apply_authorized"] is False + assert receipt["executes_shell"] is False + assert receipt["executes_endpoint"] is False + assert receipt["executes_sql"] is False + assert receipt["writes_database"] is False + assert receipt["reads_secret"] is False + assert receipt["receipt_field_count"] == 8 + assert dry_run_package["dry_run_only"] is True + assert dry_run_package["check_mode_only"] is True + assert dry_run_package["accepts_plaintext_secret"] is False + assert dry_run_package["reads_secret_in_preview"] is False + assert dry_run_package["signature_material_included"] is False + assert dry_run_package["secret_material_included"] is False + assert dry_run_package["signs_database_apply_authorization"] is False + assert dry_run_package["executes_authorization_evidence"] is False + assert dry_run_package["executes_database_apply"] is False + assert dry_run_package["executes_endpoint_in_preview"] is False + assert dry_run_package["executes_sql_in_preview"] is False + assert dry_run_package["writes_database_in_preview"] is False + assert dry_run_package["ready_for_database_apply_now"] is False + assert dry_run_package["database_apply_authorized"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_execution_receipt" + ] + is False + ) + assert contract["executes_authorization_evidence"] is False + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert "controlled_apply_final_preflight_ready" in check_keys + assert "dry_run_command_shape_preview_only" in check_keys + assert "dry_run_execution_receipt_preview_only" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert package["safety"]["reads_secret_in_preview"] is False + assert package["safety"]["executes_endpoint"] is False + assert package["safety"]["executes_sql"] is False + assert package["safety"]["writes_database"] is False + assert package["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_package_ready_after_fake_fetch_but_no_execution(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + package = build_pchome_auto_policy_db_apply_controlled_dry_run_package( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + future_receipt = package[ + "future_database_apply_controlled_dry_run_execution_receipt" + ] + dry_run_package = package["controlled_dry_run_package"] + receipt = dry_run_package["dry_run_execution_receipt_preview"] + command_shape = dry_run_package["dry_run_command_shape"] + contract = package["controlled_dry_run_package_contract"] + check_keys = [check["key"] for check in package["controlled_dry_run_package_checks"]] + assert package["result"] == "DB_APPLY_CONTROLLED_DRY_RUN_PACKAGE_READY" + assert package["summary"]["controlled_dry_run_package_ready_count"] == 1 + assert package["summary"]["controlled_dry_run_package_check_count"] == 12 + assert package["summary"]["controlled_dry_run_package_pass_count"] == 12 + assert package["summary"]["controlled_dry_run_package_waiting_count"] == 0 + assert package["summary"]["controlled_apply_final_preflight_ready_count"] == 1 + assert package["summary"]["controlled_apply_final_preflight_check_count"] == 12 + assert package["summary"]["authorization_evidence_execution_closeout_ready_count"] == 1 + assert package["summary"]["authorization_evidence_execution_closeout_check_count"] == 12 + assert package["summary"]["authorization_evidence_execution_preflight_ready_count"] == 1 + assert package["summary"]["authorization_evidence_execution_preflight_check_count"] == 12 + assert package["summary"]["authorization_verifier_receipt_closeout_ready_count"] == 1 + assert package["summary"]["verifier_receipt_closeout_check_count"] == 12 + assert package["summary"]["database_apply_final_verifier_gate_count"] == 1 + assert package["summary"]["database_apply_authorization_final_verifier_gate_ready_count"] == 1 + assert package["summary"]["controlled_dry_run_package_count"] == 1 + assert package["summary"]["controlled_dry_run_package_field_count"] == 12 + assert package["summary"]["controlled_dry_run_acceptance_gate_count"] == 10 + assert package["summary"]["dry_run_execution_receipt_preview_count"] == 1 + assert package["summary"]["dry_run_execution_receipt_field_count"] == 8 + assert package["summary"]["controlled_apply_final_preflight_count"] == 1 + assert package["summary"]["rollback_binding_count"] == 1 + assert package["summary"]["post_apply_verifier_binding_count"] == 1 + assert package["summary"]["post_apply_verifier_required_count"] == 1 + assert package["summary"]["same_run_truth_required_count"] == 1 + assert package["summary"]["reads_secret_count"] == 0 + assert package["summary"]["executes_script_count"] == 0 + assert package["summary"]["executes_migration_count"] == 0 + assert package["summary"]["executes_endpoint_count"] == 0 + assert package["summary"]["executes_sql_count"] == 0 + assert package["summary"]["writes_database_count"] == 0 + assert package["summary"]["signs_database_apply_authorization_count"] == 0 + assert future_receipt["dry_run_package_id"].startswith( + "pchome-db-apply-controlled-dry-run-package-" + ) + assert ( + future_receipt[ + "ready_for_future_database_apply_controlled_dry_run_execution_receipt" + ] + is True + ) + assert ( + future_receipt[ + "can_enter_future_database_apply_controlled_dry_run_receipt_closeout" + ] + is True + ) + assert future_receipt["controlled_dry_run_package_ready"] is True + assert future_receipt["dry_run_execution_performed"] is False + assert future_receipt["ready_for_database_apply_now"] is False + assert future_receipt["database_apply_authorized"] is False + assert future_receipt["issues_database_apply_authorization"] is False + assert future_receipt["signs_database_apply_authorization"] is False + assert future_receipt["executes_authorization_evidence"] is False + assert future_receipt["executes_database_apply"] is False + assert future_receipt["executes_endpoint"] is False + assert future_receipt["executes_sql"] is False + assert future_receipt["writes_database"] is False + assert dry_run_package["authorization_material_type"] == "controlled_dry_run_package" + assert ( + dry_run_package["ready_for_future_database_apply_controlled_dry_run_package"] + is True + ) + assert dry_run_package["controlled_dry_run_package_field_count"] == 12 + assert dry_run_package["controlled_dry_run_acceptance_gate_count"] == 10 + assert "dry_run_execution_receipt_id" in ( + dry_run_package["controlled_dry_run_package_fields"] + ) + assert "dry_run_receipt_preview_only" in ( + dry_run_package["controlled_dry_run_acceptance_gates"] + ) + assert dry_run_package["dry_run_execution_receipt_preview_count"] == 1 + assert dry_run_package["dry_run_execution_receipt_field_count"] == 8 + assert dry_run_package["rollback_binding_count"] == 1 + assert dry_run_package["post_apply_verifier_binding_count"] == 1 + assert dry_run_package["target_file"] == ( + "migrations/045_pchome_auto_policy_evidence_receipts.sql" + ) + assert dry_run_package["hash_matches"] is True + assert dry_run_package["target_migration_hash_locked"] is True + assert command_shape["dry_run_only"] is True + assert command_shape["check_mode_only"] is True + assert command_shape["execution_allowed"] is False + assert command_shape["shell_command_included"] is False + assert command_shape["sql_included"] is False + assert command_shape["endpoint_execution_included"] is False + assert command_shape["database_write_included"] is False + assert command_shape["requires_fresh_production_truth_in_same_run"] is True + assert command_shape["requires_rollback_binding"] is True + assert command_shape["requires_post_apply_verifier_binding"] is True + assert receipt["receipt_id"].endswith("-dry-run-receipt-preview") + assert receipt["source_dry_run_package_id"] == dry_run_package["dry_run_package_id"] + assert receipt["dry_run_status"] == "preview_only_not_executed" + assert receipt["dry_run_command_shape_hash"] == ( + dry_run_package["dry_run_command_shape_hash"] + ) + assert receipt["execution_performed"] is False + assert receipt["stdout_included"] is False + assert receipt["stderr_included"] is False + assert receipt["database_apply_authorized"] is False + assert receipt["executes_shell"] is False + assert receipt["executes_endpoint"] is False + assert receipt["executes_sql"] is False + assert receipt["writes_database"] is False + assert receipt["reads_secret"] is False + assert receipt["receipt_field_count"] == 8 + assert dry_run_package["dry_run_only"] is True + assert dry_run_package["check_mode_only"] is True + assert dry_run_package["requires_fresh_production_truth_in_same_run"] is True + assert dry_run_package["requires_post_apply_verifier"] is True + assert dry_run_package["accepts_plaintext_secret"] is False + assert dry_run_package["reads_secret_in_preview"] is False + assert dry_run_package["signature_material_included"] is False + assert dry_run_package["secret_material_included"] is False + assert dry_run_package["signs_database_apply_authorization"] is False + assert dry_run_package["executes_authorization_evidence"] is False + assert dry_run_package["executes_database_apply"] is False + assert dry_run_package["executes_endpoint_in_preview"] is False + assert dry_run_package["executes_sql_in_preview"] is False + assert dry_run_package["writes_database_in_preview"] is False + assert dry_run_package["ready_for_database_apply_now"] is False + assert dry_run_package["database_apply_authorized"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_execution_receipt" + ] + is True + ) + assert contract["executes_authorization_evidence"] is False + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert "controlled_apply_final_preflight_ready" in check_keys + assert "rollback_binding_carried_forward" in check_keys + assert "post_apply_verifier_binding_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "dry_run_command_shape_preview_only" in check_keys + assert "dry_run_execution_receipt_preview_only" in check_keys + assert "final_preflight_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert package["safety"]["reads_secret_in_preview"] is False + assert package["safety"]["executes_endpoint"] is False + assert package["safety"]["executes_sql"] is False + assert package["safety"]["writes_database"] is False + assert package["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_receipt_closeout_waits_without_ready_package(): + closeout = build_pchome_auto_policy_db_apply_controlled_dry_run_receipt_closeout( + _payload(), + batch_size=1, + ) + + future_verification = closeout[ + "future_database_apply_controlled_dry_run_result_parser_verification" + ] + receipt_closeout = closeout["controlled_dry_run_receipt_closeout"] + parser = receipt_closeout["dry_run_result_parser"] + validation = receipt_closeout["receipt_validation_report"] + contract = closeout["controlled_dry_run_receipt_closeout_contract"] + check_keys = [ + check["key"] for check in closeout["controlled_dry_run_receipt_closeout_checks"] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_receipt_closeout" + ) + assert closeout["result"] == "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_PACKAGE" + assert closeout["summary"]["controlled_dry_run_receipt_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_waiting_count"] > 0 + assert closeout["summary"]["controlled_dry_run_package_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_package_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_field_count"] == 12 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["dry_run_result_parser_count"] == 1 + assert closeout["summary"]["dry_run_result_parser_field_count"] == 10 + assert closeout["summary"]["receipt_validation_report_count"] == 1 + assert closeout["summary"]["receipt_validation_field_count"] == 8 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future_verification["receipt_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-receipt-closeout-" + ) + assert ( + future_verification[ + "ready_for_future_database_apply_controlled_dry_run_result_parser_verification" + ] + is False + ) + assert ( + future_verification[ + "can_enter_future_database_apply_controlled_dry_run_runner_readiness" + ] + is False + ) + assert future_verification["controlled_dry_run_receipt_closeout_ready"] is False + assert future_verification["dry_run_execution_performed"] is False + assert future_verification["ready_for_database_apply_now"] is False + assert future_verification["database_apply_authorized"] is False + assert future_verification["executes_database_apply"] is False + assert future_verification["executes_endpoint"] is False + assert future_verification["executes_sql"] is False + assert future_verification["writes_database"] is False + assert receipt_closeout["authorization_material_type"] == ( + "controlled_dry_run_receipt_closeout" + ) + assert ( + receipt_closeout[ + "ready_for_future_database_apply_controlled_dry_run_receipt_closeout" + ] + is False + ) + assert receipt_closeout["controlled_dry_run_receipt_closeout_field_count"] == 12 + assert ( + receipt_closeout[ + "controlled_dry_run_receipt_closeout_acceptance_gate_count" + ] + == 10 + ) + assert parser["expected_receipt_status"] == "preview_only_not_executed" + assert parser["execution_required"] is False + assert parser["stdout_allowed"] is False + assert parser["stderr_allowed"] is False + assert parser["database_apply_authorized"] is False + assert parser["parser_field_count"] == 10 + assert validation["receipt_validation_field_count"] == 8 + assert validation["execution_performed"] is False + assert validation["stdout_included"] is False + assert validation["stderr_included"] is False + assert validation["database_apply_authorized"] is False + assert validation["executes_shell"] is False + assert validation["executes_endpoint"] is False + assert validation["executes_sql"] is False + assert validation["writes_database"] is False + assert validation["reads_secret"] is False + assert receipt_closeout["receipt_preview_only"] is True + assert receipt_closeout["dry_run_only"] is True + assert receipt_closeout["check_mode_only"] is True + assert receipt_closeout["accepts_plaintext_secret"] is False + assert receipt_closeout["reads_secret_in_preview"] is False + assert receipt_closeout["signature_material_included"] is False + assert receipt_closeout["secret_material_included"] is False + assert receipt_closeout["signs_database_apply_authorization"] is False + assert receipt_closeout["executes_authorization_evidence"] is False + assert receipt_closeout["executes_database_apply"] is False + assert receipt_closeout["executes_endpoint_in_preview"] is False + assert receipt_closeout["executes_sql_in_preview"] is False + assert receipt_closeout["writes_database_in_preview"] is False + assert receipt_closeout["ready_for_database_apply_now"] is False + assert receipt_closeout["database_apply_authorized"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_runner_readiness" + ] + is False + ) + assert contract["executes_authorization_evidence"] is False + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert "controlled_dry_run_package_ready" in check_keys + assert "dry_run_result_parser_schema_complete" in check_keys + assert "receipt_preview_schema_matches_parser" in check_keys + assert "command_shape_hash_matches_receipt" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_receipt_closeout_ready_after_fake_fetch_but_no_execution(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = build_pchome_auto_policy_db_apply_controlled_dry_run_receipt_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + future_verification = closeout[ + "future_database_apply_controlled_dry_run_result_parser_verification" + ] + receipt_closeout = closeout["controlled_dry_run_receipt_closeout"] + parser = receipt_closeout["dry_run_result_parser"] + validation = receipt_closeout["receipt_validation_report"] + contract = closeout["controlled_dry_run_receipt_closeout_contract"] + check_keys = [ + check["key"] for check in closeout["controlled_dry_run_receipt_closeout_checks"] + ] + assert closeout["result"] == "DB_APPLY_CONTROLLED_DRY_RUN_RECEIPT_CLOSEOUT_READY" + assert closeout["summary"]["controlled_dry_run_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_pass_count"] == 12 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_waiting_count"] == 0 + assert closeout["summary"]["controlled_dry_run_package_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_package_check_count"] == 12 + assert closeout["summary"]["controlled_apply_final_preflight_ready_count"] == 1 + assert closeout["summary"]["controlled_apply_final_preflight_check_count"] == 12 + assert closeout["summary"]["authorization_evidence_execution_closeout_ready_count"] == 1 + assert closeout["summary"]["authorization_evidence_execution_closeout_check_count"] == 12 + assert closeout["summary"]["authorization_evidence_execution_preflight_ready_count"] == 1 + assert closeout["summary"]["authorization_evidence_execution_preflight_check_count"] == 12 + assert closeout["summary"]["authorization_verifier_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["verifier_receipt_closeout_check_count"] == 12 + assert closeout["summary"]["database_apply_final_verifier_gate_count"] == 1 + assert closeout["summary"]["database_apply_authorization_final_verifier_gate_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_field_count"] == 12 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["dry_run_result_parser_count"] == 1 + assert closeout["summary"]["dry_run_result_parser_field_count"] == 10 + assert closeout["summary"]["receipt_validation_report_count"] == 1 + assert closeout["summary"]["receipt_validation_field_count"] == 8 + assert closeout["summary"]["dry_run_execution_receipt_preview_count"] == 1 + assert closeout["summary"]["dry_run_execution_receipt_field_count"] == 8 + assert closeout["summary"]["controlled_dry_run_package_count"] == 1 + assert closeout["summary"]["rollback_binding_count"] == 1 + assert closeout["summary"]["post_apply_verifier_binding_count"] == 1 + assert closeout["summary"]["post_apply_verifier_required_count"] == 1 + assert closeout["summary"]["same_run_truth_required_count"] == 1 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future_verification["receipt_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-receipt-closeout-" + ) + assert ( + future_verification[ + "ready_for_future_database_apply_controlled_dry_run_result_parser_verification" + ] + is True + ) + assert ( + future_verification[ + "can_enter_future_database_apply_controlled_dry_run_runner_readiness" + ] + is True + ) + assert future_verification["controlled_dry_run_receipt_closeout_ready"] is True + assert future_verification["receipt_validation_status"] == ( + "preview_validated_not_executed" + ) + assert future_verification["dry_run_execution_performed"] is False + assert future_verification["ready_for_database_apply_now"] is False + assert future_verification["database_apply_authorized"] is False + assert future_verification["executes_database_apply"] is False + assert future_verification["executes_endpoint"] is False + assert future_verification["executes_sql"] is False + assert future_verification["writes_database"] is False + assert receipt_closeout["authorization_material_type"] == ( + "controlled_dry_run_receipt_closeout" + ) + assert ( + receipt_closeout[ + "ready_for_future_database_apply_controlled_dry_run_receipt_closeout" + ] + is True + ) + assert receipt_closeout["controlled_dry_run_receipt_closeout_field_count"] == 12 + assert ( + receipt_closeout[ + "controlled_dry_run_receipt_closeout_acceptance_gate_count" + ] + == 10 + ) + assert "dry_run_result_parser_id" in ( + receipt_closeout["controlled_dry_run_receipt_closeout_fields"] + ) + assert "receipt_preview_schema_match" in ( + receipt_closeout["controlled_dry_run_receipt_closeout_acceptance_gates"] + ) + assert parser["parser_id"] == receipt_closeout["dry_run_result_parser_id"] + assert parser["expected_receipt_status"] == "preview_only_not_executed" + assert parser["required_command_shape_hash"] == ( + receipt_closeout["dry_run_command_shape_hash"] + ) + assert parser["execution_required"] is False + assert parser["stdout_allowed"] is False + assert parser["stderr_allowed"] is False + assert parser["database_apply_authorized"] is False + assert parser["parser_field_count"] == 10 + assert validation["receipt_validation_status"] == "preview_validated_not_executed" + assert validation["receipt_validation_field_count"] == 8 + assert validation["dry_run_command_shape_hash"] == ( + receipt_closeout["dry_run_command_shape_hash"] + ) + assert validation["execution_performed"] is False + assert validation["stdout_included"] is False + assert validation["stderr_included"] is False + assert validation["database_apply_authorized"] is False + assert validation["executes_shell"] is False + assert validation["executes_endpoint"] is False + assert validation["executes_sql"] is False + assert validation["writes_database"] is False + assert validation["reads_secret"] is False + assert receipt_closeout["target_file"] == ( + "migrations/045_pchome_auto_policy_evidence_receipts.sql" + ) + assert receipt_closeout["hash_matches"] is True + assert receipt_closeout["target_migration_hash_locked"] is True + assert receipt_closeout["receipt_preview_only"] is True + assert receipt_closeout["dry_run_only"] is True + assert receipt_closeout["check_mode_only"] is True + assert receipt_closeout["requires_fresh_production_truth_in_same_run"] is True + assert receipt_closeout["requires_post_apply_verifier"] is True + assert receipt_closeout["accepts_plaintext_secret"] is False + assert receipt_closeout["reads_secret_in_preview"] is False + assert receipt_closeout["signature_material_included"] is False + assert receipt_closeout["secret_material_included"] is False + assert receipt_closeout["signs_database_apply_authorization"] is False + assert receipt_closeout["executes_authorization_evidence"] is False + assert receipt_closeout["executes_database_apply"] is False + assert receipt_closeout["executes_endpoint_in_preview"] is False + assert receipt_closeout["executes_sql_in_preview"] is False + assert receipt_closeout["writes_database_in_preview"] is False + assert receipt_closeout["ready_for_database_apply_now"] is False + assert receipt_closeout["database_apply_authorized"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_runner_readiness" + ] + is True + ) + assert contract["executes_authorization_evidence"] is False + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["signs_database_apply_authorization"] is False + assert contract["writes_database"] is False + assert "controlled_dry_run_package_ready" in check_keys + assert "dry_run_result_parser_schema_complete" in check_keys + assert "receipt_preview_schema_matches_parser" in check_keys + assert "command_shape_hash_matches_receipt" in check_keys + assert "receipt_preview_only_not_executed" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "package_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_runner_readiness_waits_without_ready_receipt_closeout(): + readiness = build_pchome_auto_policy_db_apply_controlled_dry_run_runner_readiness( + _payload(), + batch_size=1, + ) + + future_plan = readiness[ + "future_database_apply_controlled_dry_run_execution_plan_binding" + ] + runner = readiness["controlled_dry_run_runner_readiness"] + plan = runner["execution_plan_binding"] + validation = runner["receipt_validation_report"] + parser = runner["dry_run_result_parser"] + contract = readiness["controlled_dry_run_runner_readiness_contract"] + check_keys = [ + check["key"] for check in readiness["controlled_dry_run_runner_readiness_checks"] + ] + assert readiness["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_runner_readiness" + ) + assert readiness["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_RECEIPT_CLOSEOUT" + ) + assert readiness["summary"]["controlled_dry_run_runner_readiness_ready_count"] == 0 + assert readiness["summary"]["controlled_dry_run_runner_readiness_check_count"] == 12 + assert readiness["summary"]["controlled_dry_run_runner_readiness_waiting_count"] > 0 + assert readiness["summary"]["controlled_dry_run_receipt_closeout_check_count"] == 12 + assert readiness["summary"]["controlled_dry_run_receipt_closeout_ready_count"] == 0 + assert readiness["summary"]["controlled_dry_run_runner_readiness_count"] == 1 + assert readiness["summary"]["controlled_dry_run_runner_readiness_field_count"] == 12 + assert ( + readiness["summary"][ + "controlled_dry_run_runner_readiness_acceptance_gate_count" + ] + == 10 + ) + assert readiness["summary"]["execution_plan_binding_count"] == 1 + assert readiness["summary"]["execution_plan_binding_field_count"] == 12 + assert readiness["summary"]["dry_run_result_parser_count"] == 1 + assert readiness["summary"]["dry_run_result_parser_field_count"] == 10 + assert readiness["summary"]["receipt_validation_report_count"] == 1 + assert readiness["summary"]["receipt_validation_field_count"] == 8 + assert readiness["summary"]["reads_secret_count"] == 0 + assert readiness["summary"]["executes_endpoint_count"] == 0 + assert readiness["summary"]["executes_sql_count"] == 0 + assert readiness["summary"]["writes_database_count"] == 0 + assert readiness["summary"]["signs_database_apply_authorization_count"] == 0 + assert future_plan["runner_readiness_id"].startswith( + "pchome-db-apply-controlled-dry-run-runner-readiness-" + ) + assert ( + future_plan[ + "ready_for_future_database_apply_controlled_dry_run_execution_plan_binding" + ] + is False + ) + assert ( + future_plan[ + "can_enter_future_database_apply_controlled_dry_run_execution_plan_closeout" + ] + is False + ) + assert future_plan["controlled_dry_run_runner_readiness_ready"] is False + assert future_plan["execution_plan_bound"] is False + assert future_plan["dry_run_execution_performed"] is False + assert future_plan["runner_execution_authorized"] is False + assert future_plan["dry_run_execution_authorized"] is False + assert future_plan["ready_for_database_apply_now"] is False + assert future_plan["database_apply_authorized"] is False + assert future_plan["executes_database_apply"] is False + assert future_plan["executes_endpoint"] is False + assert future_plan["executes_sql"] is False + assert future_plan["writes_database"] is False + assert runner["authorization_material_type"] == "controlled_dry_run_runner_readiness" + assert ( + runner["ready_for_future_database_apply_controlled_dry_run_runner_readiness"] + is False + ) + assert runner["controlled_dry_run_runner_readiness_field_count"] == 12 + assert runner["controlled_dry_run_runner_readiness_acceptance_gate_count"] == 10 + assert runner["execution_plan_binding_count"] == 1 + assert runner["execution_plan_binding_field_count"] == 12 + assert plan["runner_mode"] == "future_controlled_dry_run_runner_readiness_only" + assert plan["plan_status"] == "plan_binding_preview_not_executable" + assert plan["dry_run_only"] is True + assert plan["check_mode_only"] is True + assert plan["execution_authorized"] is False + assert plan["dry_run_execution_authorized"] is False + assert plan["runner_execution_authorized"] is False + assert plan["shell_execution_included"] is False + assert plan["endpoint_execution_included"] is False + assert plan["sql_execution_included"] is False + assert plan["database_write_included"] is False + assert plan["stdout_capture_allowed"] is False + assert plan["stderr_capture_allowed"] is False + assert plan["database_apply_authorized"] is False + assert plan["ready_for_database_apply_now"] is False + assert parser["execution_required"] is False + assert parser["database_apply_authorized"] is False + assert validation["execution_performed"] is False + assert validation["database_apply_authorized"] is False + assert validation["executes_endpoint"] is False + assert validation["executes_sql"] is False + assert validation["writes_database"] is False + assert runner["runner_readiness_only"] is True + assert runner["execution_plan_preview_only"] is True + assert runner["runner_execution_authorized"] is False + assert runner["dry_run_execution_authorized"] is False + assert runner["accepts_plaintext_secret"] is False + assert runner["reads_secret_in_preview"] is False + assert runner["signature_material_included"] is False + assert runner["secret_material_included"] is False + assert runner["signs_database_apply_authorization"] is False + assert runner["executes_authorization_evidence"] is False + assert runner["executes_database_apply"] is False + assert runner["executes_endpoint_in_preview"] is False + assert runner["executes_sql_in_preview"] is False + assert runner["writes_database_in_preview"] is False + assert runner["ready_for_database_apply_now"] is False + assert runner["database_apply_authorized"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_execution_plan_binding" + ] + is False + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["writes_database"] is False + assert "receipt_closeout_ready" in check_keys + assert "execution_plan_binding_preview_only" in check_keys + assert "runner_execution_gate_closed" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert readiness["safety"]["reads_secret_in_preview"] is False + assert readiness["safety"]["executes_endpoint"] is False + assert readiness["safety"]["executes_sql"] is False + assert readiness["safety"]["writes_database"] is False + assert readiness["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_runner_readiness_ready_after_fake_fetch_but_no_execution(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + readiness = build_pchome_auto_policy_db_apply_controlled_dry_run_runner_readiness( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + + future_plan = readiness[ + "future_database_apply_controlled_dry_run_execution_plan_binding" + ] + runner = readiness["controlled_dry_run_runner_readiness"] + plan = runner["execution_plan_binding"] + validation = runner["receipt_validation_report"] + parser = runner["dry_run_result_parser"] + contract = readiness["controlled_dry_run_runner_readiness_contract"] + check_keys = [ + check["key"] for check in readiness["controlled_dry_run_runner_readiness_checks"] + ] + assert readiness["result"] == "DB_APPLY_CONTROLLED_DRY_RUN_RUNNER_READINESS_READY" + assert readiness["summary"]["controlled_dry_run_runner_readiness_ready_count"] == 1 + assert readiness["summary"]["controlled_dry_run_runner_readiness_check_count"] == 12 + assert readiness["summary"]["controlled_dry_run_runner_readiness_pass_count"] == 12 + assert readiness["summary"]["controlled_dry_run_runner_readiness_waiting_count"] == 0 + assert readiness["summary"]["controlled_dry_run_receipt_closeout_ready_count"] == 1 + assert readiness["summary"]["controlled_dry_run_receipt_closeout_check_count"] == 12 + assert readiness["summary"]["controlled_dry_run_package_ready_count"] == 1 + assert readiness["summary"]["controlled_dry_run_package_check_count"] == 12 + assert readiness["summary"]["controlled_apply_final_preflight_ready_count"] == 1 + assert readiness["summary"]["controlled_apply_final_preflight_check_count"] == 12 + assert readiness["summary"]["authorization_evidence_execution_closeout_ready_count"] == 1 + assert readiness["summary"]["authorization_evidence_execution_closeout_check_count"] == 12 + assert readiness["summary"]["authorization_evidence_execution_preflight_ready_count"] == 1 + assert readiness["summary"]["authorization_evidence_execution_preflight_check_count"] == 12 + assert readiness["summary"]["database_apply_final_verifier_gate_count"] == 1 + assert readiness["summary"]["database_apply_authorization_final_verifier_gate_ready_count"] == 1 + assert readiness["summary"]["controlled_dry_run_runner_readiness_count"] == 1 + assert readiness["summary"]["controlled_dry_run_runner_readiness_field_count"] == 12 + assert ( + readiness["summary"][ + "controlled_dry_run_runner_readiness_acceptance_gate_count" + ] + == 10 + ) + assert readiness["summary"]["execution_plan_binding_count"] == 1 + assert readiness["summary"]["execution_plan_binding_field_count"] == 12 + assert readiness["summary"]["dry_run_result_parser_count"] == 1 + assert readiness["summary"]["dry_run_result_parser_field_count"] == 10 + assert readiness["summary"]["receipt_validation_report_count"] == 1 + assert readiness["summary"]["receipt_validation_field_count"] == 8 + assert readiness["summary"]["controlled_dry_run_receipt_closeout_count"] == 1 + assert readiness["summary"]["controlled_dry_run_receipt_closeout_field_count"] == 12 + assert ( + readiness["summary"][ + "controlled_dry_run_receipt_closeout_acceptance_gate_count" + ] + == 10 + ) + assert readiness["summary"]["rollback_binding_count"] == 1 + assert readiness["summary"]["post_apply_verifier_binding_count"] == 1 + assert readiness["summary"]["post_apply_verifier_required_count"] == 1 + assert readiness["summary"]["same_run_truth_required_count"] == 1 + assert readiness["summary"]["reads_secret_count"] == 0 + assert readiness["summary"]["executes_endpoint_count"] == 0 + assert readiness["summary"]["executes_sql_count"] == 0 + assert readiness["summary"]["writes_database_count"] == 0 + assert readiness["summary"]["signs_database_apply_authorization_count"] == 0 + assert future_plan["runner_readiness_id"].startswith( + "pchome-db-apply-controlled-dry-run-runner-readiness-" + ) + assert ( + future_plan[ + "ready_for_future_database_apply_controlled_dry_run_execution_plan_binding" + ] + is True + ) + assert ( + future_plan[ + "can_enter_future_database_apply_controlled_dry_run_execution_plan_closeout" + ] + is True + ) + assert future_plan["controlled_dry_run_runner_readiness_ready"] is True + assert future_plan["execution_plan_bound"] is True + assert future_plan["dry_run_execution_performed"] is False + assert future_plan["runner_execution_authorized"] is False + assert future_plan["dry_run_execution_authorized"] is False + assert future_plan["ready_for_database_apply_now"] is False + assert future_plan["database_apply_authorized"] is False + assert future_plan["executes_database_apply"] is False + assert future_plan["executes_endpoint"] is False + assert future_plan["executes_sql"] is False + assert future_plan["writes_database"] is False + assert runner["authorization_material_type"] == "controlled_dry_run_runner_readiness" + assert ( + runner["ready_for_future_database_apply_controlled_dry_run_runner_readiness"] + is True + ) + assert runner["controlled_dry_run_runner_readiness_field_count"] == 12 + assert runner["controlled_dry_run_runner_readiness_acceptance_gate_count"] == 10 + assert "execution_plan_binding_id" in ( + runner["controlled_dry_run_runner_readiness_fields"] + ) + assert "execution_plan_binding_preview_only" in ( + runner["controlled_dry_run_runner_readiness_acceptance_gates"] + ) + assert runner["execution_plan_binding_count"] == 1 + assert runner["execution_plan_binding_field_count"] == 12 + assert plan["execution_plan_binding_id"] == future_plan["execution_plan_binding_id"] + assert plan["runner_mode"] == "future_controlled_dry_run_runner_readiness_only" + assert plan["plan_status"] == "plan_binding_preview_not_executable" + assert plan["dry_run_only"] is True + assert plan["check_mode_only"] is True + assert plan["execution_authorized"] is False + assert plan["dry_run_execution_authorized"] is False + assert plan["runner_execution_authorized"] is False + assert plan["shell_execution_included"] is False + assert plan["endpoint_execution_included"] is False + assert plan["sql_execution_included"] is False + assert plan["database_write_included"] is False + assert plan["stdout_capture_allowed"] is False + assert plan["stderr_capture_allowed"] is False + assert plan["database_apply_authorized"] is False + assert plan["ready_for_database_apply_now"] is False + assert parser["required_command_shape_hash"] == runner["dry_run_command_shape_hash"] + assert parser["execution_required"] is False + assert parser["database_apply_authorized"] is False + assert validation["receipt_validation_status"] == "preview_validated_not_executed" + assert validation["dry_run_command_shape_hash"] == runner["dry_run_command_shape_hash"] + assert validation["execution_performed"] is False + assert validation["database_apply_authorized"] is False + assert validation["executes_endpoint"] is False + assert validation["executes_sql"] is False + assert validation["writes_database"] is False + assert runner["target_file"] == "migrations/045_pchome_auto_policy_evidence_receipts.sql" + assert runner["hash_matches"] is True + assert runner["target_migration_hash_locked"] is True + assert runner["runner_readiness_only"] is True + assert runner["execution_plan_preview_only"] is True + assert runner["runner_execution_authorized"] is False + assert runner["dry_run_execution_authorized"] is False + assert runner["accepts_plaintext_secret"] is False + assert runner["reads_secret_in_preview"] is False + assert runner["signature_material_included"] is False + assert runner["secret_material_included"] is False + assert runner["signs_database_apply_authorization"] is False + assert runner["executes_authorization_evidence"] is False + assert runner["executes_database_apply"] is False + assert runner["executes_endpoint_in_preview"] is False + assert runner["executes_sql_in_preview"] is False + assert runner["writes_database_in_preview"] is False + assert runner["ready_for_database_apply_now"] is False + assert runner["database_apply_authorized"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_execution_plan_binding" + ] + is True + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["writes_database"] is False + assert "receipt_closeout_ready" in check_keys + assert "dry_run_result_parser_verified" in check_keys + assert "receipt_validation_report_ready" in check_keys + assert "command_shape_hash_bound" in check_keys + assert "execution_plan_binding_preview_only" in check_keys + assert "runner_execution_gate_closed" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "closeout_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert readiness["safety"]["reads_secret_in_preview"] is False + assert readiness["safety"]["executes_endpoint"] is False + assert readiness["safety"]["executes_sql"] is False + assert readiness["safety"]["writes_database"] is False + assert readiness["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_execution_plan_closeout_waits_without_ready_runner_readiness(): + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_execution_plan_closeout( + _payload(), + batch_size=1, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_command_artifact_verification" + ] + plan_closeout = closeout["controlled_dry_run_execution_plan_closeout"] + artifact = plan_closeout["non_executable_command_artifact"] + contract = closeout["controlled_dry_run_execution_plan_closeout_contract"] + check_keys = [ + check["key"] + for check in closeout["controlled_dry_run_execution_plan_closeout_checks"] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_execution_plan_closeout" + ) + assert closeout["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_RUNNER_READINESS" + ) + assert ( + closeout["summary"][ + "controlled_dry_run_execution_plan_closeout_ready_count" + ] + == 0 + ) + assert ( + closeout["summary"][ + "controlled_dry_run_execution_plan_closeout_check_count" + ] + == 12 + ) + assert ( + closeout["summary"][ + "controlled_dry_run_execution_plan_closeout_waiting_count" + ] + > 0 + ) + assert closeout["summary"]["controlled_dry_run_runner_readiness_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_runner_readiness_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_execution_plan_closeout_count"] == 1 + assert ( + closeout["summary"][ + "controlled_dry_run_execution_plan_closeout_field_count" + ] + == 12 + ) + assert ( + closeout["summary"][ + "controlled_dry_run_execution_plan_closeout_acceptance_gate_count" + ] + == 10 + ) + assert closeout["summary"]["non_executable_command_artifact_count"] == 1 + assert closeout["summary"]["non_executable_command_artifact_field_count"] == 10 + assert closeout["summary"]["execution_plan_binding_count"] == 1 + assert closeout["summary"]["execution_plan_binding_field_count"] == 12 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["execution_plan_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-execution-plan-closeout-" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_command_artifact_verification" + ] + is False + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_command_artifact_closeout" + ] + is False + ) + assert future["execution_plan_closeout_ready"] is False + assert future["non_executable_command_artifact_verified"] is False + assert future["runner_execution_authorized"] is False + assert future["dry_run_execution_authorized"] is False + assert future["execution_authorized"] is False + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert plan_closeout["authorization_material_type"] == ( + "controlled_dry_run_execution_plan_closeout" + ) + assert ( + plan_closeout[ + "ready_for_future_database_apply_controlled_dry_run_execution_plan_closeout" + ] + is False + ) + assert plan_closeout["execution_plan_closeout_field_count"] == 12 + assert plan_closeout["execution_plan_closeout_acceptance_gate_count"] == 10 + assert plan_closeout["non_executable_command_artifact_count"] == 1 + assert plan_closeout["non_executable_command_artifact_field_count"] == 10 + assert plan_closeout["execution_plan_closeout_only"] is True + assert plan_closeout["non_executable_command_artifact_only"] is True + assert plan_closeout["runner_execution_authorized"] is False + assert plan_closeout["dry_run_execution_authorized"] is False + assert plan_closeout["execution_authorized"] is False + assert plan_closeout["accepts_plaintext_secret"] is False + assert plan_closeout["reads_secret_in_preview"] is False + assert plan_closeout["signature_material_included"] is False + assert plan_closeout["secret_material_included"] is False + assert plan_closeout["signs_database_apply_authorization"] is False + assert plan_closeout["executes_authorization_evidence"] is False + assert plan_closeout["executes_database_apply"] is False + assert plan_closeout["executes_endpoint_in_preview"] is False + assert plan_closeout["executes_sql_in_preview"] is False + assert plan_closeout["writes_database_in_preview"] is False + assert plan_closeout["ready_for_database_apply_now"] is False + assert plan_closeout["database_apply_authorized"] is False + assert artifact["artifact_type"] == "non_executable_command_artifact_reference" + assert artifact["command_text_included"] is False + assert artifact["argv_included"] is False + assert artifact.get("command_text") is None + assert artifact.get("argv") is None + assert artifact["shell_command_included"] is False + assert artifact["endpoint_execution_included"] is False + assert artifact["sql_execution_included"] is False + assert artifact["database_write_included"] is False + assert artifact["execution_authorized"] is False + assert artifact["database_apply_authorized"] is False + assert len(artifact["non_executable_command_artifact_sha256"]) == 64 + assert contract[ + "permits_future_database_apply_controlled_dry_run_command_artifact_verification" + ] is False + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["writes_database"] is False + assert "runner_readiness_ready" in check_keys + assert "non_executable_command_artifact_bound" in check_keys + assert "command_artifact_hash_locked" in check_keys + assert "runner_execution_gate_closed" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_execution_plan_closeout_ready_after_fake_fetch_but_no_execution(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_execution_plan_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_command_artifact_verification" + ] + plan_closeout = closeout["controlled_dry_run_execution_plan_closeout"] + plan = plan_closeout["execution_plan_binding"] + artifact = plan_closeout["non_executable_command_artifact"] + validation = plan_closeout["receipt_validation_report"] + parser = plan_closeout["dry_run_result_parser"] + contract = closeout["controlled_dry_run_execution_plan_closeout_contract"] + check_keys = [ + check["key"] + for check in closeout["controlled_dry_run_execution_plan_closeout_checks"] + ] + assert ( + closeout["result"] + == "DB_APPLY_CONTROLLED_DRY_RUN_EXECUTION_PLAN_CLOSEOUT_READY" + ) + assert ( + closeout["summary"][ + "controlled_dry_run_execution_plan_closeout_ready_count" + ] + == 1 + ) + assert ( + closeout["summary"][ + "controlled_dry_run_execution_plan_closeout_check_count" + ] + == 12 + ) + assert ( + closeout["summary"][ + "controlled_dry_run_execution_plan_closeout_pass_count" + ] + == 12 + ) + assert ( + closeout["summary"][ + "controlled_dry_run_execution_plan_closeout_waiting_count" + ] + == 0 + ) + assert closeout["summary"]["controlled_dry_run_runner_readiness_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_readiness_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_package_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_package_check_count"] == 12 + assert closeout["summary"]["controlled_apply_final_preflight_ready_count"] == 1 + assert closeout["summary"]["controlled_apply_final_preflight_check_count"] == 12 + assert closeout["summary"]["authorization_evidence_execution_closeout_ready_count"] == 1 + assert closeout["summary"]["authorization_evidence_execution_closeout_check_count"] == 12 + assert closeout["summary"]["authorization_evidence_execution_preflight_ready_count"] == 1 + assert closeout["summary"]["authorization_evidence_execution_preflight_check_count"] == 12 + assert closeout["summary"]["database_apply_final_verifier_gate_count"] == 1 + assert closeout["summary"]["database_apply_authorization_final_verifier_gate_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_execution_plan_closeout_count"] == 1 + assert ( + closeout["summary"][ + "controlled_dry_run_execution_plan_closeout_field_count" + ] + == 12 + ) + assert ( + closeout["summary"][ + "controlled_dry_run_execution_plan_closeout_acceptance_gate_count" + ] + == 10 + ) + assert closeout["summary"]["non_executable_command_artifact_count"] == 1 + assert closeout["summary"]["non_executable_command_artifact_field_count"] == 10 + assert closeout["summary"]["execution_plan_binding_count"] == 1 + assert closeout["summary"]["execution_plan_binding_field_count"] == 12 + assert closeout["summary"]["dry_run_result_parser_count"] == 1 + assert closeout["summary"]["dry_run_result_parser_field_count"] == 10 + assert closeout["summary"]["receipt_validation_report_count"] == 1 + assert closeout["summary"]["receipt_validation_field_count"] == 8 + assert closeout["summary"]["rollback_binding_count"] == 1 + assert closeout["summary"]["post_apply_verifier_binding_count"] == 1 + assert closeout["summary"]["post_apply_verifier_required_count"] == 1 + assert closeout["summary"]["same_run_truth_required_count"] == 1 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["execution_plan_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-execution-plan-closeout-" + ) + assert future["non_executable_command_artifact_id"] == artifact["artifact_id"] + assert ( + future["non_executable_command_artifact_sha256"] + == artifact["non_executable_command_artifact_sha256"] + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_command_artifact_verification" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_command_artifact_closeout" + ] + is True + ) + assert future["execution_plan_closeout_ready"] is True + assert future["non_executable_command_artifact_verified"] is True + assert future["runner_execution_authorized"] is False + assert future["dry_run_execution_authorized"] is False + assert future["execution_authorized"] is False + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert future["issues_database_apply_authorization"] is False + assert future["signs_database_apply_authorization"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert plan_closeout["authorization_material_type"] == ( + "controlled_dry_run_execution_plan_closeout" + ) + assert ( + plan_closeout[ + "ready_for_future_database_apply_controlled_dry_run_execution_plan_closeout" + ] + is True + ) + assert plan_closeout["execution_plan_closeout_field_count"] == 12 + assert plan_closeout["execution_plan_closeout_acceptance_gate_count"] == 10 + assert "non_executable_command_artifact_id" in ( + plan_closeout["execution_plan_closeout_fields"] + ) + assert "non_executable_command_artifact_bound" in ( + plan_closeout["execution_plan_closeout_acceptance_gates"] + ) + assert plan_closeout["non_executable_command_artifact_count"] == 1 + assert plan_closeout["non_executable_command_artifact_field_count"] == 10 + assert len(plan_closeout["non_executable_command_artifact_sha256"]) == 64 + assert plan_closeout["execution_plan_binding_count"] == 1 + assert plan_closeout["execution_plan_binding_field_count"] == 12 + assert plan["execution_plan_binding_id"] == future[ + "source_execution_plan_binding_id" + ] + assert plan["plan_status"] == "plan_binding_preview_not_executable" + assert plan["dry_run_only"] is True + assert plan["check_mode_only"] is True + assert plan["execution_authorized"] is False + assert plan["dry_run_execution_authorized"] is False + assert plan["runner_execution_authorized"] is False + assert plan["shell_execution_included"] is False + assert plan["endpoint_execution_included"] is False + assert plan["sql_execution_included"] is False + assert plan["database_write_included"] is False + assert plan["stdout_capture_allowed"] is False + assert plan["stderr_capture_allowed"] is False + assert plan["database_apply_authorized"] is False + assert plan["ready_for_database_apply_now"] is False + assert artifact["artifact_type"] == "non_executable_command_artifact_reference" + assert artifact["source_execution_plan_binding_id"] == plan[ + "execution_plan_binding_id" + ] + assert artifact["dry_run_command_shape_hash"] == plan_closeout[ + "dry_run_command_shape_hash" + ] + assert artifact["command_text_included"] is False + assert artifact["argv_included"] is False + assert artifact.get("command_text") is None + assert artifact.get("argv") is None + assert artifact["shell_command_included"] is False + assert artifact["endpoint_execution_included"] is False + assert artifact["sql_execution_included"] is False + assert artifact["database_write_included"] is False + assert artifact["stdout_capture_allowed"] is False + assert artifact["stderr_capture_allowed"] is False + assert artifact["execution_authorized"] is False + assert artifact["dry_run_execution_authorized"] is False + assert artifact["runner_execution_authorized"] is False + assert artifact["database_apply_authorized"] is False + assert len(artifact["non_executable_command_artifact_sha256"]) == 64 + assert parser["required_command_shape_hash"] == plan_closeout[ + "dry_run_command_shape_hash" + ] + assert parser["execution_required"] is False + assert parser["database_apply_authorized"] is False + assert validation["receipt_validation_status"] == "preview_validated_not_executed" + assert validation["execution_performed"] is False + assert validation["database_apply_authorized"] is False + assert validation["executes_endpoint"] is False + assert validation["executes_sql"] is False + assert validation["writes_database"] is False + assert plan_closeout["target_file"] == ( + "migrations/045_pchome_auto_policy_evidence_receipts.sql" + ) + assert plan_closeout["hash_matches"] is True + assert plan_closeout["target_migration_hash_locked"] is True + assert plan_closeout["execution_plan_closeout_only"] is True + assert plan_closeout["non_executable_command_artifact_only"] is True + assert plan_closeout["runner_execution_authorized"] is False + assert plan_closeout["dry_run_execution_authorized"] is False + assert plan_closeout["execution_authorized"] is False + assert plan_closeout["accepts_plaintext_secret"] is False + assert plan_closeout["reads_secret_in_preview"] is False + assert plan_closeout["signature_material_included"] is False + assert plan_closeout["secret_material_included"] is False + assert plan_closeout["signs_database_apply_authorization"] is False + assert plan_closeout["executes_authorization_evidence"] is False + assert plan_closeout["executes_database_apply"] is False + assert plan_closeout["executes_endpoint_in_preview"] is False + assert plan_closeout["executes_sql_in_preview"] is False + assert plan_closeout["writes_database_in_preview"] is False + assert plan_closeout["ready_for_database_apply_now"] is False + assert plan_closeout["database_apply_authorized"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_command_artifact_verification" + ] + is True + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["writes_database"] is False + assert "runner_readiness_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "execution_plan_binding_preview_only" in check_keys + assert "non_executable_command_artifact_bound" in check_keys + assert "command_artifact_hash_locked" in check_keys + assert "receipt_validation_and_parser_carried_forward" in check_keys + assert "runner_execution_gate_closed" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bindings_carried_forward" in check_keys + assert "runner_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_command_artifact_closeout_waits_without_ready_execution_plan_closeout(): + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_command_artifact_closeout( + _payload(), + batch_size=1, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_runner_execution_receipt_preflight" + ] + command_closeout = closeout["controlled_dry_run_command_artifact_closeout"] + receipt_preflight = command_closeout["runner_execution_receipt_preflight"] + artifact = command_closeout["non_executable_command_artifact"] + contract = closeout["controlled_dry_run_command_artifact_closeout_contract"] + check_keys = [ + check["key"] + for check in closeout["controlled_dry_run_command_artifact_closeout_checks"] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_command_artifact_closeout" + ) + assert closeout["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_EXECUTION_PLAN_CLOSEOUT" + ) + assert ( + closeout["summary"][ + "controlled_dry_run_command_artifact_closeout_ready_count" + ] + == 0 + ) + assert ( + closeout["summary"][ + "controlled_dry_run_command_artifact_closeout_check_count" + ] + == 12 + ) + assert closeout["summary"]["controlled_dry_run_execution_plan_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_command_artifact_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_command_artifact_closeout_field_count"] == 12 + assert ( + closeout["summary"][ + "controlled_dry_run_command_artifact_closeout_acceptance_gate_count" + ] + == 10 + ) + assert closeout["summary"]["runner_execution_receipt_preflight_count"] == 1 + assert closeout["summary"]["runner_execution_receipt_preflight_field_count"] == 10 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["command_artifact_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-command-artifact-closeout-" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_runner_execution_receipt_preflight" + ] + is False + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_runner_execution_receipt_closeout" + ] + is False + ) + assert future["command_artifact_closeout_ready"] is False + assert future["runner_execution_receipt_preflight_bound"] is False + assert future["runner_execution_authorized"] is False + assert future["dry_run_execution_authorized"] is False + assert future["execution_authorized"] is False + assert future["stdout_capture_allowed"] is False + assert future["stderr_capture_allowed"] is False + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert command_closeout["authorization_material_type"] == ( + "controlled_dry_run_command_artifact_closeout" + ) + assert ( + command_closeout[ + "ready_for_future_database_apply_controlled_dry_run_command_artifact_closeout" + ] + is False + ) + assert command_closeout["command_artifact_closeout_field_count"] == 12 + assert command_closeout["command_artifact_closeout_acceptance_gate_count"] == 10 + assert command_closeout["runner_execution_receipt_preflight_count"] == 1 + assert command_closeout["runner_execution_receipt_preflight_field_count"] == 10 + assert command_closeout["command_artifact_closeout_only"] is True + assert command_closeout["runner_execution_receipt_preflight_only"] is True + assert command_closeout["runner_execution_authorized"] is False + assert command_closeout["dry_run_execution_authorized"] is False + assert command_closeout["execution_authorized"] is False + assert command_closeout["accepts_plaintext_secret"] is False + assert command_closeout["reads_secret_in_preview"] is False + assert command_closeout["signature_material_included"] is False + assert command_closeout["secret_material_included"] is False + assert command_closeout["signs_database_apply_authorization"] is False + assert command_closeout["executes_database_apply"] is False + assert command_closeout["executes_endpoint_in_preview"] is False + assert command_closeout["executes_sql_in_preview"] is False + assert command_closeout["writes_database_in_preview"] is False + assert artifact["command_text_included"] is False + assert artifact["argv_included"] is False + assert artifact.get("command_text") is None + assert artifact.get("argv") is None + assert receipt_preflight["preflight_status"] == "preflight_only_not_executed" + assert receipt_preflight["execution_required"] is False + assert receipt_preflight["execution_authorized"] is False + assert receipt_preflight["dry_run_execution_authorized"] is False + assert receipt_preflight["runner_execution_authorized"] is False + assert receipt_preflight["stdout_capture_allowed"] is False + assert receipt_preflight["stderr_capture_allowed"] is False + assert receipt_preflight["execution_performed"] is False + assert receipt_preflight["database_apply_authorized"] is False + assert receipt_preflight["writes_database"] is False + assert contract[ + "permits_future_database_apply_controlled_dry_run_runner_execution_receipt_preflight" + ] is False + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["writes_database"] is False + assert "execution_plan_closeout_ready" in check_keys + assert "non_executable_command_artifact_hash_verified" in check_keys + assert "runner_execution_receipt_preflight_no_execute" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_command_artifact_closeout_ready_after_fake_fetch_but_no_execution(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_command_artifact_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_runner_execution_receipt_preflight" + ] + command_closeout = closeout["controlled_dry_run_command_artifact_closeout"] + receipt_preflight = command_closeout["runner_execution_receipt_preflight"] + artifact = command_closeout["non_executable_command_artifact"] + validation = command_closeout["receipt_validation_report"] + parser = command_closeout["dry_run_result_parser"] + contract = closeout["controlled_dry_run_command_artifact_closeout_contract"] + check_keys = [ + check["key"] + for check in closeout["controlled_dry_run_command_artifact_closeout_checks"] + ] + assert ( + closeout["result"] + == "DB_APPLY_CONTROLLED_DRY_RUN_COMMAND_ARTIFACT_CLOSEOUT_READY" + ) + assert ( + closeout["summary"][ + "controlled_dry_run_command_artifact_closeout_ready_count" + ] + == 1 + ) + assert ( + closeout["summary"][ + "controlled_dry_run_command_artifact_closeout_check_count" + ] + == 12 + ) + assert ( + closeout["summary"][ + "controlled_dry_run_command_artifact_closeout_pass_count" + ] + == 12 + ) + assert ( + closeout["summary"][ + "controlled_dry_run_command_artifact_closeout_waiting_count" + ] + == 0 + ) + assert closeout["summary"]["controlled_dry_run_execution_plan_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_execution_plan_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_runner_readiness_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_readiness_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_package_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_package_check_count"] == 12 + assert closeout["summary"]["controlled_apply_final_preflight_ready_count"] == 1 + assert closeout["summary"]["controlled_apply_final_preflight_check_count"] == 12 + assert closeout["summary"]["authorization_evidence_execution_closeout_ready_count"] == 1 + assert closeout["summary"]["authorization_evidence_execution_closeout_check_count"] == 12 + assert closeout["summary"]["authorization_evidence_execution_preflight_ready_count"] == 1 + assert closeout["summary"]["authorization_evidence_execution_preflight_check_count"] == 12 + assert closeout["summary"]["database_apply_final_verifier_gate_count"] == 1 + assert closeout["summary"]["database_apply_authorization_final_verifier_gate_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_command_artifact_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_command_artifact_closeout_field_count"] == 12 + assert ( + closeout["summary"][ + "controlled_dry_run_command_artifact_closeout_acceptance_gate_count" + ] + == 10 + ) + assert closeout["summary"]["runner_execution_receipt_preflight_count"] == 1 + assert closeout["summary"]["runner_execution_receipt_preflight_field_count"] == 10 + assert closeout["summary"]["non_executable_command_artifact_count"] == 1 + assert closeout["summary"]["non_executable_command_artifact_field_count"] == 10 + assert closeout["summary"]["execution_plan_binding_count"] == 1 + assert closeout["summary"]["execution_plan_binding_field_count"] == 12 + assert closeout["summary"]["dry_run_result_parser_count"] == 1 + assert closeout["summary"]["dry_run_result_parser_field_count"] == 10 + assert closeout["summary"]["receipt_validation_report_count"] == 1 + assert closeout["summary"]["receipt_validation_field_count"] == 8 + assert closeout["summary"]["rollback_binding_count"] == 1 + assert closeout["summary"]["post_apply_verifier_binding_count"] == 1 + assert closeout["summary"]["post_apply_verifier_required_count"] == 1 + assert closeout["summary"]["same_run_truth_required_count"] == 1 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["command_artifact_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-command-artifact-closeout-" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_runner_execution_receipt_preflight" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_runner_execution_receipt_closeout" + ] + is True + ) + assert future["command_artifact_closeout_ready"] is True + assert future["runner_execution_receipt_preflight_bound"] is True + assert future["runner_execution_authorized"] is False + assert future["dry_run_execution_authorized"] is False + assert future["execution_authorized"] is False + assert future["stdout_capture_allowed"] is False + assert future["stderr_capture_allowed"] is False + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert future["issues_database_apply_authorization"] is False + assert future["signs_database_apply_authorization"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert command_closeout["authorization_material_type"] == ( + "controlled_dry_run_command_artifact_closeout" + ) + assert ( + command_closeout[ + "ready_for_future_database_apply_controlled_dry_run_command_artifact_closeout" + ] + is True + ) + assert command_closeout["command_artifact_closeout_field_count"] == 12 + assert command_closeout["command_artifact_closeout_acceptance_gate_count"] == 10 + assert "runner_execution_receipt_preflight_id" in ( + command_closeout["command_artifact_closeout_fields"] + ) + assert "runner_execution_receipt_preflight_no_execute" in ( + command_closeout["command_artifact_closeout_acceptance_gates"] + ) + assert command_closeout["runner_execution_receipt_preflight_count"] == 1 + assert command_closeout["runner_execution_receipt_preflight_field_count"] == 10 + assert command_closeout["non_executable_command_artifact_count"] == 1 + assert command_closeout["command_artifact_closeout_only"] is True + assert command_closeout["runner_execution_receipt_preflight_only"] is True + assert command_closeout["runner_execution_authorized"] is False + assert command_closeout["dry_run_execution_authorized"] is False + assert command_closeout["execution_authorized"] is False + assert command_closeout["accepts_plaintext_secret"] is False + assert command_closeout["reads_secret_in_preview"] is False + assert command_closeout["signature_material_included"] is False + assert command_closeout["secret_material_included"] is False + assert command_closeout["signs_database_apply_authorization"] is False + assert command_closeout["executes_database_apply"] is False + assert command_closeout["executes_endpoint_in_preview"] is False + assert command_closeout["executes_sql_in_preview"] is False + assert command_closeout["writes_database_in_preview"] is False + assert artifact["command_text_included"] is False + assert artifact["argv_included"] is False + assert artifact.get("command_text") is None + assert artifact.get("argv") is None + assert artifact["shell_command_included"] is False + assert artifact["endpoint_execution_included"] is False + assert artifact["sql_execution_included"] is False + assert artifact["database_write_included"] is False + assert artifact["execution_authorized"] is False + assert artifact["database_apply_authorized"] is False + assert len(artifact["non_executable_command_artifact_sha256"]) == 64 + assert receipt_preflight["preflight_id"] == future[ + "runner_execution_receipt_preflight_id" + ] + assert receipt_preflight["source_non_executable_command_artifact_id"] == artifact[ + "artifact_id" + ] + assert receipt_preflight["preflight_status"] == "preflight_only_not_executed" + assert receipt_preflight["execution_required"] is False + assert receipt_preflight["execution_authorized"] is False + assert receipt_preflight["dry_run_execution_authorized"] is False + assert receipt_preflight["runner_execution_authorized"] is False + assert receipt_preflight["shell_execution_included"] is False + assert receipt_preflight["endpoint_execution_included"] is False + assert receipt_preflight["sql_execution_included"] is False + assert receipt_preflight["database_write_included"] is False + assert receipt_preflight["stdout_capture_allowed"] is False + assert receipt_preflight["stderr_capture_allowed"] is False + assert receipt_preflight["execution_performed"] is False + assert receipt_preflight["stdout_included"] is False + assert receipt_preflight["stderr_included"] is False + assert receipt_preflight["database_apply_authorized"] is False + assert receipt_preflight["writes_database"] is False + assert parser["required_command_shape_hash"] == command_closeout[ + "dry_run_command_shape_hash" + ] + assert parser["execution_required"] is False + assert validation["receipt_validation_status"] == "preview_validated_not_executed" + assert validation["execution_performed"] is False + assert validation["database_apply_authorized"] is False + assert validation["executes_endpoint"] is False + assert validation["executes_sql"] is False + assert validation["writes_database"] is False + assert command_closeout["target_file"] == ( + "migrations/045_pchome_auto_policy_evidence_receipts.sql" + ) + assert command_closeout["hash_matches"] is True + assert command_closeout["target_migration_hash_locked"] is True + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_runner_execution_receipt_preflight" + ] + is True + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["writes_database"] is False + assert "execution_plan_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "non_executable_command_artifact_hash_verified" in check_keys + assert "non_executable_artifact_has_no_command_text_or_argv" in check_keys + assert "runner_execution_receipt_preflight_bound" in check_keys + assert "runner_execution_receipt_preflight_no_execute" in check_keys + assert "result_parser_and_receipt_validation_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bindings_carried_forward" in check_keys + assert "execution_plan_closeout_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_runner_execution_receipt_closeout_waits_without_ready_command_artifact_closeout(): + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_runner_execution_receipt_closeout( + _payload(), + batch_size=1, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_post_receipt_parser_verification" + ] + receipt_closeout = closeout[ + "controlled_dry_run_runner_execution_receipt_closeout" + ] + preview = receipt_closeout["receipt_closeout_preview"] + parser = receipt_closeout["post_receipt_parser_verification"] + preflight = receipt_closeout["runner_execution_receipt_preflight"] + contract = closeout[ + "controlled_dry_run_runner_execution_receipt_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_runner_execution_receipt_closeout_checks" + ] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_runner_execution_receipt_closeout" + ) + assert closeout["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_COMMAND_ARTIFACT_CLOSEOUT" + ) + assert ( + closeout["summary"][ + "controlled_dry_run_runner_execution_receipt_closeout_ready_count" + ] + == 0 + ) + assert ( + closeout["summary"][ + "controlled_dry_run_runner_execution_receipt_closeout_check_count" + ] + == 12 + ) + assert closeout["summary"]["controlled_dry_run_command_artifact_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_runner_execution_receipt_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_execution_receipt_closeout_field_count"] == 12 + assert ( + closeout["summary"][ + "controlled_dry_run_runner_execution_receipt_closeout_acceptance_gate_count" + ] + == 10 + ) + assert closeout["summary"]["post_receipt_parser_verification_count"] == 1 + assert closeout["summary"]["post_receipt_parser_verification_field_count"] == 10 + assert closeout["summary"]["receipt_closeout_preview_count"] == 1 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["runner_execution_receipt_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-runner-execution-receipt-closeout-" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_post_receipt_parser_verification" + ] + is False + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_post_receipt_parser_closeout" + ] + is False + ) + assert future["runner_execution_receipt_closeout_ready"] is False + assert future["post_receipt_parser_verification_bound"] is False + assert future["runner_execution_authorized"] is False + assert future["dry_run_execution_authorized"] is False + assert future["execution_authorized"] is False + assert future["stdout_capture_allowed"] is False + assert future["stderr_capture_allowed"] is False + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert receipt_closeout["authorization_material_type"] == ( + "controlled_dry_run_runner_execution_receipt_closeout" + ) + assert ( + receipt_closeout[ + "ready_for_future_database_apply_controlled_dry_run_runner_execution_receipt_closeout" + ] + is False + ) + assert receipt_closeout["runner_execution_receipt_closeout_field_count"] == 12 + assert receipt_closeout["runner_execution_receipt_closeout_acceptance_gate_count"] == 10 + assert receipt_closeout["post_receipt_parser_verification_count"] == 1 + assert receipt_closeout["post_receipt_parser_verification_field_count"] == 10 + assert receipt_closeout["receipt_closeout_preview_count"] == 1 + assert receipt_closeout["runner_execution_receipt_closeout_only"] is True + assert receipt_closeout["post_receipt_parser_verification_only"] is True + assert receipt_closeout["runner_execution_authorized"] is False + assert receipt_closeout["dry_run_execution_authorized"] is False + assert receipt_closeout["execution_authorized"] is False + assert receipt_closeout["accepts_plaintext_secret"] is False + assert receipt_closeout["reads_secret_in_preview"] is False + assert receipt_closeout["signature_material_included"] is False + assert receipt_closeout["secret_material_included"] is False + assert receipt_closeout["signs_database_apply_authorization"] is False + assert receipt_closeout["executes_database_apply"] is False + assert receipt_closeout["executes_endpoint_in_preview"] is False + assert receipt_closeout["executes_sql_in_preview"] is False + assert receipt_closeout["writes_database_in_preview"] is False + assert preflight["preflight_status"] == "preflight_only_not_executed" + assert preflight["execution_required"] is False + assert preflight["execution_performed"] is False + assert preflight["stdout_capture_allowed"] is False + assert preflight["stderr_capture_allowed"] is False + assert preflight["writes_database"] is False + assert preview["receipt_status"] == "receipt_closeout_preview_not_executed" + assert preview["execution_required"] is False + assert preview["execution_performed"] is False + assert preview["stdout_included"] is False + assert preview["stderr_included"] is False + assert preview["stdout_capture_allowed"] is False + assert preview["stderr_capture_allowed"] is False + assert preview["database_apply_authorized"] is False + assert preview["writes_database"] is False + assert preview["executes_endpoint"] is False + assert preview["executes_sql"] is False + assert parser["expected_preflight_status"] == "preflight_only_not_executed" + assert parser["expected_receipt_status"] == "receipt_closeout_preview_not_executed" + assert parser["expected_execution_performed"] is False + assert parser["expected_stdout_included"] is False + assert parser["expected_stderr_included"] is False + assert parser["execution_required"] is False + assert parser["stdout_allowed"] is False + assert parser["stderr_allowed"] is False + assert parser["database_apply_authorized"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_post_receipt_parser_verification" + ] + is False + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["writes_database"] is False + assert "command_artifact_closeout_ready" in check_keys + assert "runner_execution_receipt_preflight_no_execute" in check_keys + assert "post_receipt_parser_verification_bound" in check_keys + assert "post_receipt_parser_blocks_execution" in check_keys + assert "receipt_closeout_preview_only" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_runner_execution_receipt_closeout_ready_after_fake_fetch_but_no_execution(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_runner_execution_receipt_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_post_receipt_parser_verification" + ] + receipt_closeout = closeout[ + "controlled_dry_run_runner_execution_receipt_closeout" + ] + preview = receipt_closeout["receipt_closeout_preview"] + parser = receipt_closeout["post_receipt_parser_verification"] + preflight = receipt_closeout["runner_execution_receipt_preflight"] + contract = closeout[ + "controlled_dry_run_runner_execution_receipt_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_runner_execution_receipt_closeout_checks" + ] + ] + assert ( + closeout["result"] + == "DB_APPLY_CONTROLLED_DRY_RUN_RUNNER_EXECUTION_RECEIPT_CLOSEOUT_READY" + ) + assert ( + closeout["summary"][ + "controlled_dry_run_runner_execution_receipt_closeout_ready_count" + ] + == 1 + ) + assert ( + closeout["summary"][ + "controlled_dry_run_runner_execution_receipt_closeout_check_count" + ] + == 12 + ) + assert ( + closeout["summary"][ + "controlled_dry_run_runner_execution_receipt_closeout_pass_count" + ] + == 12 + ) + assert ( + closeout["summary"][ + "controlled_dry_run_runner_execution_receipt_closeout_waiting_count" + ] + == 0 + ) + assert closeout["summary"]["controlled_dry_run_command_artifact_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_command_artifact_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_execution_plan_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_execution_plan_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_runner_readiness_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_readiness_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_package_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_package_check_count"] == 12 + assert closeout["summary"]["controlled_apply_final_preflight_ready_count"] == 1 + assert closeout["summary"]["controlled_apply_final_preflight_check_count"] == 12 + assert closeout["summary"]["authorization_evidence_execution_closeout_ready_count"] == 1 + assert closeout["summary"]["authorization_evidence_execution_closeout_check_count"] == 12 + assert closeout["summary"]["authorization_evidence_execution_preflight_ready_count"] == 1 + assert closeout["summary"]["authorization_evidence_execution_preflight_check_count"] == 12 + assert closeout["summary"]["database_apply_final_verifier_gate_count"] == 1 + assert closeout["summary"]["database_apply_authorization_final_verifier_gate_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_execution_receipt_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_execution_receipt_closeout_field_count"] == 12 + assert ( + closeout["summary"][ + "controlled_dry_run_runner_execution_receipt_closeout_acceptance_gate_count" + ] + == 10 + ) + assert closeout["summary"]["post_receipt_parser_verification_count"] == 1 + assert closeout["summary"]["post_receipt_parser_verification_field_count"] == 10 + assert closeout["summary"]["receipt_closeout_preview_count"] == 1 + assert closeout["summary"]["runner_execution_receipt_preflight_count"] == 1 + assert closeout["summary"]["runner_execution_receipt_preflight_field_count"] == 10 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["runner_execution_receipt_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-runner-execution-receipt-closeout-" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_post_receipt_parser_verification" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_post_receipt_parser_closeout" + ] + is True + ) + assert future["runner_execution_receipt_closeout_ready"] is True + assert future["post_receipt_parser_verification_bound"] is True + assert future["runner_execution_authorized"] is False + assert future["dry_run_execution_authorized"] is False + assert future["execution_authorized"] is False + assert future["stdout_capture_allowed"] is False + assert future["stderr_capture_allowed"] is False + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert future["issues_database_apply_authorization"] is False + assert future["signs_database_apply_authorization"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert receipt_closeout["authorization_material_type"] == ( + "controlled_dry_run_runner_execution_receipt_closeout" + ) + assert ( + receipt_closeout[ + "ready_for_future_database_apply_controlled_dry_run_runner_execution_receipt_closeout" + ] + is True + ) + assert receipt_closeout["runner_execution_receipt_closeout_field_count"] == 12 + assert receipt_closeout["runner_execution_receipt_closeout_acceptance_gate_count"] == 10 + assert "post_receipt_parser_verification_id" in ( + receipt_closeout["runner_execution_receipt_closeout_fields"] + ) + assert "post_receipt_parser_verification_bound" in ( + receipt_closeout["runner_execution_receipt_closeout_acceptance_gates"] + ) + assert receipt_closeout["post_receipt_parser_verification_count"] == 1 + assert receipt_closeout["post_receipt_parser_verification_field_count"] == 10 + assert receipt_closeout["receipt_closeout_preview_count"] == 1 + assert receipt_closeout["runner_execution_receipt_closeout_only"] is True + assert receipt_closeout["post_receipt_parser_verification_only"] is True + assert receipt_closeout["runner_execution_authorized"] is False + assert receipt_closeout["dry_run_execution_authorized"] is False + assert receipt_closeout["execution_authorized"] is False + assert receipt_closeout["accepts_plaintext_secret"] is False + assert receipt_closeout["reads_secret_in_preview"] is False + assert receipt_closeout["signature_material_included"] is False + assert receipt_closeout["secret_material_included"] is False + assert receipt_closeout["signs_database_apply_authorization"] is False + assert receipt_closeout["executes_database_apply"] is False + assert receipt_closeout["executes_endpoint_in_preview"] is False + assert receipt_closeout["executes_sql_in_preview"] is False + assert receipt_closeout["writes_database_in_preview"] is False + assert preflight["preflight_status"] == "preflight_only_not_executed" + assert preflight["execution_required"] is False + assert preflight["execution_performed"] is False + assert preflight["stdout_capture_allowed"] is False + assert preflight["stderr_capture_allowed"] is False + assert preflight["writes_database"] is False + assert preview["receipt_status"] == "receipt_closeout_preview_not_executed" + assert preview["execution_required"] is False + assert preview["execution_performed"] is False + assert preview["stdout_included"] is False + assert preview["stderr_included"] is False + assert preview["stdout_capture_allowed"] is False + assert preview["stderr_capture_allowed"] is False + assert preview["database_apply_authorized"] is False + assert preview["writes_database"] is False + assert preview["executes_endpoint"] is False + assert preview["executes_sql"] is False + assert parser["verification_id"] == future["post_receipt_parser_verification_id"] + assert parser["expected_preflight_status"] == "preflight_only_not_executed" + assert parser["expected_receipt_status"] == "receipt_closeout_preview_not_executed" + assert parser["expected_execution_performed"] is False + assert parser["expected_stdout_included"] is False + assert parser["expected_stderr_included"] is False + assert parser["execution_required"] is False + assert parser["stdout_allowed"] is False + assert parser["stderr_allowed"] is False + assert parser["database_apply_authorized"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_post_receipt_parser_verification" + ] + is True + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["writes_database"] is False + assert "command_artifact_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "runner_execution_receipt_preflight_no_execute" in check_keys + assert "post_receipt_parser_verification_bound" in check_keys + assert "post_receipt_parser_blocks_execution" in check_keys + assert "receipt_closeout_preview_only" in check_keys + assert "result_parser_and_receipt_validation_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bindings_carried_forward" in check_keys + assert "command_artifact_closeout_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_post_receipt_parser_closeout_waits_without_ready_runner_receipt(): + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_post_receipt_parser_closeout( + _payload(), + batch_size=1, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_no_apply_enforcement_verification" + ] + parser_closeout = closeout[ + "controlled_dry_run_post_receipt_parser_closeout" + ] + enforcement = parser_closeout["no_apply_enforcement_verification"] + contract = closeout[ + "controlled_dry_run_post_receipt_parser_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_post_receipt_parser_closeout_checks" + ] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_post_receipt_parser_closeout" + ) + assert closeout["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_RUNNER_EXECUTION_RECEIPT_CLOSEOUT" + ) + assert closeout["summary"]["controlled_dry_run_post_receipt_parser_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_post_receipt_parser_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_runner_execution_receipt_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_post_receipt_parser_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_post_receipt_parser_closeout_field_count"] == 12 + assert closeout["summary"]["controlled_dry_run_post_receipt_parser_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["no_apply_enforcement_verification_count"] == 1 + assert closeout["summary"]["no_apply_enforcement_verification_field_count"] == 12 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["post_receipt_parser_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-post-receipt-parser-closeout-" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_no_apply_enforcement_verification" + ] + is False + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_no_apply_enforcement_closeout" + ] + is False + ) + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert parser_closeout["authorization_material_type"] == ( + "controlled_dry_run_post_receipt_parser_closeout" + ) + assert parser_closeout["post_receipt_parser_closeout_field_count"] == 12 + assert parser_closeout["post_receipt_parser_closeout_acceptance_gate_count"] == 10 + assert parser_closeout["post_receipt_parser_closeout_only"] is True + assert parser_closeout["no_apply_enforcement_verification_only"] is True + assert parser_closeout["endpoint_execution_allowed"] is False + assert parser_closeout["sql_execution_allowed"] is False + assert parser_closeout["database_write_allowed"] is False + assert parser_closeout["database_apply_authorized"] is False + assert parser_closeout["signs_database_apply_authorization"] is False + assert parser_closeout["executes_database_apply"] is False + assert parser_closeout["executes_endpoint_in_preview"] is False + assert parser_closeout["executes_sql_in_preview"] is False + assert parser_closeout["writes_database_in_preview"] is False + assert enforcement["enforcement_status"] == "no_apply_enforcement_preview_ready" + assert enforcement["endpoint_execution_allowed"] is False + assert enforcement["sql_execution_allowed"] is False + assert enforcement["database_write_allowed"] is False + assert enforcement["database_apply_authorized"] is False + assert enforcement["executes_database_apply"] is False + assert enforcement["executes_endpoint"] is False + assert enforcement["executes_sql"] is False + assert enforcement["writes_database"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_no_apply_enforcement_verification" + ] + is False + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["writes_database"] is False + assert "runner_execution_receipt_closeout_ready" in check_keys + assert "post_receipt_parser_verification_ready" in check_keys + assert "no_apply_enforcement_verification_bound" in check_keys + assert "no_apply_enforcement_blocks_endpoint_sql_db_write" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_post_receipt_parser_closeout_ready_after_fake_fetch_but_no_apply(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_post_receipt_parser_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_no_apply_enforcement_verification" + ] + parser_closeout = closeout[ + "controlled_dry_run_post_receipt_parser_closeout" + ] + enforcement = parser_closeout["no_apply_enforcement_verification"] + parser = parser_closeout["post_receipt_parser_verification"] + preview = parser_closeout["receipt_closeout_preview"] + contract = closeout[ + "controlled_dry_run_post_receipt_parser_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_post_receipt_parser_closeout_checks" + ] + ] + assert ( + closeout["result"] + == "DB_APPLY_CONTROLLED_DRY_RUN_POST_RECEIPT_PARSER_CLOSEOUT_READY" + ) + assert closeout["summary"]["controlled_dry_run_post_receipt_parser_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_post_receipt_parser_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_post_receipt_parser_closeout_pass_count"] == 12 + assert closeout["summary"]["controlled_dry_run_post_receipt_parser_closeout_waiting_count"] == 0 + assert closeout["summary"]["controlled_dry_run_runner_execution_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_execution_receipt_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_command_artifact_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_execution_plan_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_readiness_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_package_ready_count"] == 1 + assert closeout["summary"]["controlled_apply_final_preflight_ready_count"] == 1 + assert closeout["summary"]["no_apply_enforcement_verification_count"] == 1 + assert closeout["summary"]["no_apply_enforcement_verification_field_count"] == 12 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_no_apply_enforcement_verification" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_no_apply_enforcement_closeout" + ] + is True + ) + assert future["post_receipt_parser_closeout_ready"] is True + assert future["no_apply_enforcement_verification_bound"] is True + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert parser_closeout["ready_for_future_database_apply_controlled_dry_run_post_receipt_parser_closeout"] is True + assert parser_closeout["post_receipt_parser_closeout_only"] is True + assert parser_closeout["no_apply_enforcement_verification_only"] is True + assert parser_closeout["endpoint_execution_allowed"] is False + assert parser_closeout["sql_execution_allowed"] is False + assert parser_closeout["database_write_allowed"] is False + assert parser_closeout["database_apply_authorized"] is False + assert parser_closeout["signs_database_apply_authorization"] is False + assert parser_closeout["executes_database_apply"] is False + assert parser_closeout["executes_endpoint_in_preview"] is False + assert parser_closeout["executes_sql_in_preview"] is False + assert parser_closeout["writes_database_in_preview"] is False + assert parser["parser_verification_status"] == "post_receipt_parser_preview_ready" + assert parser["expected_execution_performed"] is False + assert parser["expected_stdout_included"] is False + assert parser["expected_stderr_included"] is False + assert parser["database_apply_authorized"] is False + assert preview["receipt_status"] == "receipt_closeout_preview_not_executed" + assert preview["execution_performed"] is False + assert preview["stdout_included"] is False + assert preview["stderr_included"] is False + assert preview["writes_database"] is False + assert enforcement["verification_id"] == future["no_apply_enforcement_verification_id"] + assert enforcement["source_post_receipt_parser_verification_id"] == parser["verification_id"] + assert enforcement["endpoint_execution_allowed"] is False + assert enforcement["sql_execution_allowed"] is False + assert enforcement["database_write_allowed"] is False + assert enforcement["database_apply_authorized"] is False + assert enforcement["executes_database_apply"] is False + assert enforcement["executes_endpoint"] is False + assert enforcement["executes_sql"] is False + assert enforcement["writes_database"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_no_apply_enforcement_verification" + ] + is True + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["writes_database"] is False + assert "runner_execution_receipt_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "post_receipt_parser_verification_ready" in check_keys + assert "post_receipt_parser_blocks_execution" in check_keys + assert "receipt_closeout_preview_not_executed" in check_keys + assert "no_apply_enforcement_verification_bound" in check_keys + assert "no_apply_enforcement_blocks_endpoint_sql_db_write" in check_keys + assert "result_parser_and_receipt_validation_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bindings_carried_forward" in check_keys + assert "runner_execution_receipt_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_no_apply_enforcement_closeout_waits_without_ready_parser_closeout(): + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_no_apply_enforcement_closeout( + _payload(), + batch_size=1, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_final_dry_run_executor_guard" + ] + enforcement_closeout = closeout[ + "controlled_dry_run_no_apply_enforcement_closeout" + ] + final_guard = enforcement_closeout["final_dry_run_executor_guard"] + contract = closeout[ + "controlled_dry_run_no_apply_enforcement_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_no_apply_enforcement_closeout_checks" + ] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_no_apply_enforcement_closeout" + ) + assert closeout["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_POST_RECEIPT_PARSER_CLOSEOUT" + ) + assert closeout["summary"]["controlled_dry_run_no_apply_enforcement_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_no_apply_enforcement_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_post_receipt_parser_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_no_apply_enforcement_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_no_apply_enforcement_closeout_field_count"] == 12 + assert closeout["summary"]["controlled_dry_run_no_apply_enforcement_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["final_dry_run_executor_guard_count"] == 1 + assert closeout["summary"]["final_dry_run_executor_guard_field_count"] == 12 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["no_apply_enforcement_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-no-apply-enforcement-closeout-" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_final_dry_run_executor_guard" + ] + is False + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_final_executor_guard_closeout" + ] + is False + ) + assert future["dry_run_executor_invocation_allowed"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert enforcement_closeout["authorization_material_type"] == ( + "controlled_dry_run_no_apply_enforcement_closeout" + ) + assert enforcement_closeout["no_apply_enforcement_closeout_field_count"] == 12 + assert enforcement_closeout["no_apply_enforcement_closeout_acceptance_gate_count"] == 10 + assert enforcement_closeout["no_apply_enforcement_closeout_only"] is True + assert enforcement_closeout["final_dry_run_executor_guard_only"] is True + assert enforcement_closeout["dry_run_executor_invocation_allowed"] is False + assert enforcement_closeout["endpoint_execution_allowed"] is False + assert enforcement_closeout["sql_execution_allowed"] is False + assert enforcement_closeout["database_write_allowed"] is False + assert enforcement_closeout["database_apply_authorized"] is False + assert enforcement_closeout["signs_database_apply_authorization"] is False + assert enforcement_closeout["executes_database_apply"] is False + assert enforcement_closeout["executes_endpoint_in_preview"] is False + assert enforcement_closeout["executes_sql_in_preview"] is False + assert enforcement_closeout["writes_database_in_preview"] is False + assert final_guard["guard_status"] == "final_dry_run_executor_guard_preview_ready" + assert final_guard["dry_run_executor_invocation_allowed"] is False + assert final_guard["endpoint_execution_allowed"] is False + assert final_guard["sql_execution_allowed"] is False + assert final_guard["database_write_allowed"] is False + assert final_guard["database_apply_authorized"] is False + assert final_guard["executes_database_apply"] is False + assert final_guard["executes_endpoint"] is False + assert final_guard["executes_sql"] is False + assert final_guard["writes_database"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_final_dry_run_executor_guard" + ] + is False + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["writes_database"] is False + assert "post_receipt_parser_closeout_ready" in check_keys + assert "no_apply_enforcement_verification_ready" in check_keys + assert "final_dry_run_executor_guard_bound" in check_keys + assert "final_executor_guard_blocks_execution" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_no_apply_enforcement_closeout_ready_after_fake_fetch_but_executor_guard_does_not_invoke(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_no_apply_enforcement_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_final_dry_run_executor_guard" + ] + enforcement_closeout = closeout[ + "controlled_dry_run_no_apply_enforcement_closeout" + ] + enforcement = enforcement_closeout["no_apply_enforcement_verification"] + final_guard = enforcement_closeout["final_dry_run_executor_guard"] + contract = closeout[ + "controlled_dry_run_no_apply_enforcement_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_no_apply_enforcement_closeout_checks" + ] + ] + assert ( + closeout["result"] + == "DB_APPLY_CONTROLLED_DRY_RUN_NO_APPLY_ENFORCEMENT_CLOSEOUT_READY" + ) + assert closeout["summary"]["controlled_dry_run_no_apply_enforcement_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_no_apply_enforcement_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_no_apply_enforcement_closeout_pass_count"] == 12 + assert closeout["summary"]["controlled_dry_run_no_apply_enforcement_closeout_waiting_count"] == 0 + assert closeout["summary"]["controlled_dry_run_post_receipt_parser_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_execution_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_command_artifact_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_execution_plan_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_readiness_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_package_ready_count"] == 1 + assert closeout["summary"]["controlled_apply_final_preflight_ready_count"] == 1 + assert closeout["summary"]["final_dry_run_executor_guard_count"] == 1 + assert closeout["summary"]["final_dry_run_executor_guard_field_count"] == 12 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_final_dry_run_executor_guard" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_final_executor_guard_closeout" + ] + is True + ) + assert future["no_apply_enforcement_closeout_ready"] is True + assert future["final_dry_run_executor_guard_bound"] is True + assert future["dry_run_executor_invocation_allowed"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert enforcement_closeout["ready_for_future_database_apply_controlled_dry_run_no_apply_enforcement_closeout"] is True + assert enforcement_closeout["no_apply_enforcement_closeout_only"] is True + assert enforcement_closeout["final_dry_run_executor_guard_only"] is True + assert enforcement_closeout["dry_run_executor_invocation_allowed"] is False + assert enforcement_closeout["endpoint_execution_allowed"] is False + assert enforcement_closeout["sql_execution_allowed"] is False + assert enforcement_closeout["database_write_allowed"] is False + assert enforcement_closeout["database_apply_authorized"] is False + assert enforcement_closeout["executes_database_apply"] is False + assert enforcement_closeout["executes_endpoint_in_preview"] is False + assert enforcement_closeout["executes_sql_in_preview"] is False + assert enforcement_closeout["writes_database_in_preview"] is False + assert enforcement["enforcement_status"] == "no_apply_enforcement_preview_ready" + assert enforcement["endpoint_execution_allowed"] is False + assert enforcement["sql_execution_allowed"] is False + assert enforcement["database_write_allowed"] is False + assert enforcement["database_apply_authorized"] is False + assert enforcement["executes_endpoint"] is False + assert enforcement["executes_sql"] is False + assert enforcement["writes_database"] is False + assert final_guard["guard_id"] == future["final_dry_run_executor_guard_id"] + assert final_guard["guard_status"] == "final_dry_run_executor_guard_preview_ready" + assert final_guard["dry_run_executor_invocation_allowed"] is False + assert final_guard["stdout_capture_allowed"] is False + assert final_guard["stderr_capture_allowed"] is False + assert final_guard["database_apply_authorized"] is False + assert final_guard["executes_database_apply"] is False + assert final_guard["executes_endpoint"] is False + assert final_guard["executes_sql"] is False + assert final_guard["writes_database"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_final_dry_run_executor_guard" + ] + is True + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["writes_database"] is False + assert "post_receipt_parser_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "no_apply_enforcement_verification_ready" in check_keys + assert "no_apply_blocks_endpoint_sql_db_write" in check_keys + assert "final_dry_run_executor_guard_bound" in check_keys + assert "final_executor_guard_blocks_execution" in check_keys + assert "parser_and_receipt_preview_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bindings_carried_forward" in check_keys + assert "post_receipt_parser_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_final_executor_guard_closeout_waits_without_ready_no_apply_closeout(): + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_final_executor_guard_closeout( + _payload(), + batch_size=1, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_pre_apply_replay_verifier" + ] + guard_closeout = closeout[ + "controlled_dry_run_final_executor_guard_closeout" + ] + replay = guard_closeout["pre_apply_replay_verifier"] + final_guard = guard_closeout["final_dry_run_executor_guard"] + contract = closeout[ + "controlled_dry_run_final_executor_guard_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_final_executor_guard_closeout_checks" + ] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_final_executor_guard_closeout" + ) + assert closeout["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_NO_APPLY_ENFORCEMENT_CLOSEOUT" + ) + assert closeout["summary"]["controlled_dry_run_final_executor_guard_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_final_executor_guard_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_no_apply_enforcement_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_final_executor_guard_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_final_executor_guard_closeout_field_count"] == 12 + assert closeout["summary"]["controlled_dry_run_final_executor_guard_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["pre_apply_replay_verifier_count"] == 1 + assert closeout["summary"]["pre_apply_replay_verifier_field_count"] == 12 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["final_executor_guard_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-final-executor-guard-closeout-" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_pre_apply_replay_verifier" + ] + is False + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_pre_apply_replay_closeout" + ] + is False + ) + assert future["dry_run_executor_invocation_allowed"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert guard_closeout["authorization_material_type"] == ( + "controlled_dry_run_final_executor_guard_closeout" + ) + assert guard_closeout["final_executor_guard_closeout_field_count"] == 12 + assert guard_closeout["final_executor_guard_closeout_acceptance_gate_count"] == 10 + assert guard_closeout["final_executor_guard_closeout_only"] is True + assert guard_closeout["pre_apply_replay_verifier_only"] is True + assert guard_closeout["dry_run_executor_invocation_allowed"] is False + assert guard_closeout["endpoint_execution_allowed"] is False + assert guard_closeout["sql_execution_allowed"] is False + assert guard_closeout["database_write_allowed"] is False + assert guard_closeout["database_apply_authorized"] is False + assert guard_closeout["executes_database_apply"] is False + assert guard_closeout["executes_endpoint_in_preview"] is False + assert guard_closeout["executes_sql_in_preview"] is False + assert guard_closeout["writes_database_in_preview"] is False + assert final_guard["guard_status"] == "final_dry_run_executor_guard_preview_ready" + assert final_guard["dry_run_executor_invocation_allowed"] is False + assert replay["verifier_status"] == "pre_apply_replay_verifier_preview_ready" + assert replay["replay_mode"] == "pre_apply_replay_preview_only" + assert replay["dry_run_executor_invocation_allowed"] is False + assert replay["endpoint_execution_allowed"] is False + assert replay["sql_execution_allowed"] is False + assert replay["database_write_allowed"] is False + assert replay["database_apply_authorized"] is False + assert replay["executes_database_apply"] is False + assert replay["executes_endpoint"] is False + assert replay["executes_sql"] is False + assert replay["writes_database"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_pre_apply_replay_verifier" + ] + is False + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["writes_database"] is False + assert "no_apply_enforcement_closeout_ready" in check_keys + assert "final_dry_run_executor_guard_ready" in check_keys + assert "pre_apply_replay_verifier_bound" in check_keys + assert "pre_apply_replay_verifier_preview_only" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_final_executor_guard_closeout_ready_after_fake_fetch_but_replay_is_preview_only(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_final_executor_guard_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_pre_apply_replay_verifier" + ] + guard_closeout = closeout[ + "controlled_dry_run_final_executor_guard_closeout" + ] + replay = guard_closeout["pre_apply_replay_verifier"] + final_guard = guard_closeout["final_dry_run_executor_guard"] + contract = closeout[ + "controlled_dry_run_final_executor_guard_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_final_executor_guard_closeout_checks" + ] + ] + assert ( + closeout["result"] + == "DB_APPLY_CONTROLLED_DRY_RUN_FINAL_EXECUTOR_GUARD_CLOSEOUT_READY" + ) + assert closeout["summary"]["controlled_dry_run_final_executor_guard_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_final_executor_guard_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_final_executor_guard_closeout_pass_count"] == 12 + assert closeout["summary"]["controlled_dry_run_final_executor_guard_closeout_waiting_count"] == 0 + assert closeout["summary"]["controlled_dry_run_no_apply_enforcement_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_post_receipt_parser_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_execution_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_command_artifact_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_execution_plan_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_readiness_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_package_ready_count"] == 1 + assert closeout["summary"]["controlled_apply_final_preflight_ready_count"] == 1 + assert closeout["summary"]["pre_apply_replay_verifier_count"] == 1 + assert closeout["summary"]["pre_apply_replay_verifier_field_count"] == 12 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_pre_apply_replay_verifier" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_pre_apply_replay_closeout" + ] + is True + ) + assert future["final_executor_guard_closeout_ready"] is True + assert future["pre_apply_replay_verifier_bound"] is True + assert future["dry_run_executor_invocation_allowed"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert guard_closeout["ready_for_future_database_apply_controlled_dry_run_final_executor_guard_closeout"] is True + assert guard_closeout["final_executor_guard_closeout_only"] is True + assert guard_closeout["pre_apply_replay_verifier_only"] is True + assert guard_closeout["dry_run_executor_invocation_allowed"] is False + assert guard_closeout["endpoint_execution_allowed"] is False + assert guard_closeout["sql_execution_allowed"] is False + assert guard_closeout["database_write_allowed"] is False + assert guard_closeout["database_apply_authorized"] is False + assert guard_closeout["executes_database_apply"] is False + assert guard_closeout["executes_endpoint_in_preview"] is False + assert guard_closeout["executes_sql_in_preview"] is False + assert guard_closeout["writes_database_in_preview"] is False + assert final_guard["guard_status"] == "final_dry_run_executor_guard_preview_ready" + assert final_guard["dry_run_executor_invocation_allowed"] is False + assert replay["verification_id"] == future["pre_apply_replay_verifier_id"] + assert replay["replay_mode"] == "pre_apply_replay_preview_only" + assert replay["dry_run_executor_invocation_allowed"] is False + assert replay["stdout_capture_allowed"] is False + assert replay["stderr_capture_allowed"] is False + assert replay["database_apply_authorized"] is False + assert replay["executes_database_apply"] is False + assert replay["executes_endpoint"] is False + assert replay["executes_sql"] is False + assert replay["writes_database"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_pre_apply_replay_verifier" + ] + is True + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["writes_database"] is False + assert "no_apply_enforcement_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "final_dry_run_executor_guard_ready" in check_keys + assert "final_executor_guard_blocks_invocation" in check_keys + assert "pre_apply_replay_verifier_bound" in check_keys + assert "pre_apply_replay_verifier_preview_only" in check_keys + assert "no_apply_enforcement_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bindings_carried_forward" in check_keys + assert "no_apply_enforcement_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_pre_apply_replay_closeout_waits_without_ready_final_executor_guard_closeout(): + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_pre_apply_replay_closeout( + _payload(), + batch_size=1, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_apply_executor_readiness_contract" + ] + replay_closeout = closeout[ + "controlled_dry_run_pre_apply_replay_closeout" + ] + readiness = replay_closeout["apply_executor_readiness_contract"] + contract = closeout[ + "controlled_dry_run_pre_apply_replay_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_pre_apply_replay_closeout_checks" + ] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_pre_apply_replay_closeout" + ) + assert closeout["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_FINAL_EXECUTOR_GUARD_CLOSEOUT" + ) + assert closeout["summary"]["controlled_dry_run_pre_apply_replay_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_pre_apply_replay_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_final_executor_guard_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_pre_apply_replay_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_pre_apply_replay_closeout_field_count"] == 12 + assert closeout["summary"]["controlled_dry_run_pre_apply_replay_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["apply_executor_readiness_contract_count"] == 1 + assert closeout["summary"]["apply_executor_readiness_contract_field_count"] == 12 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["pre_apply_replay_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-pre-apply-replay-closeout-" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_apply_executor_readiness_contract" + ] + is False + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_apply_executor_readiness_closeout" + ] + is False + ) + assert future["dry_run_executor_invocation_allowed"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert replay_closeout["authorization_material_type"] == ( + "controlled_dry_run_pre_apply_replay_closeout" + ) + assert replay_closeout["pre_apply_replay_closeout_field_count"] == 12 + assert replay_closeout["pre_apply_replay_closeout_acceptance_gate_count"] == 10 + assert replay_closeout["pre_apply_replay_closeout_only"] is True + assert replay_closeout["apply_executor_readiness_contract_only"] is True + assert replay_closeout["dry_run_executor_invocation_allowed"] is False + assert replay_closeout["endpoint_execution_allowed"] is False + assert replay_closeout["sql_execution_allowed"] is False + assert replay_closeout["database_write_allowed"] is False + assert replay_closeout["database_apply_authorized"] is False + assert replay_closeout["executes_database_apply"] is False + assert replay_closeout["executes_endpoint_in_preview"] is False + assert replay_closeout["executes_sql_in_preview"] is False + assert replay_closeout["writes_database_in_preview"] is False + assert readiness["readiness_status"] == "apply_executor_readiness_contract_preview_ready" + assert readiness["readiness_mode"] == "apply_executor_readiness_contract_preview_only" + assert readiness["dry_run_executor_invocation_allowed"] is False + assert readiness["endpoint_execution_allowed"] is False + assert readiness["sql_execution_allowed"] is False + assert readiness["database_write_allowed"] is False + assert readiness["database_apply_authorized"] is False + assert readiness["executes_database_apply"] is False + assert readiness["executes_endpoint"] is False + assert readiness["executes_sql"] is False + assert readiness["writes_database"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_apply_executor_readiness_contract" + ] + is False + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_dry_run_executor_invocation_now"] is False + assert contract["writes_database"] is False + assert "final_executor_guard_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "pre_apply_replay_verifier_ready" in check_keys + assert "pre_apply_replay_preview_only" in check_keys + assert "apply_executor_readiness_contract_bound" in check_keys + assert "apply_executor_readiness_contract_blocks_apply" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_pre_apply_replay_closeout_ready_after_fake_fetch_but_executor_readiness_contract_blocks_apply(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_pre_apply_replay_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_apply_executor_readiness_contract" + ] + replay_closeout = closeout[ + "controlled_dry_run_pre_apply_replay_closeout" + ] + readiness = replay_closeout["apply_executor_readiness_contract"] + replay = replay_closeout["pre_apply_replay_verifier"] + contract = closeout[ + "controlled_dry_run_pre_apply_replay_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_pre_apply_replay_closeout_checks" + ] + ] + assert ( + closeout["result"] + == "DB_APPLY_CONTROLLED_DRY_RUN_PRE_APPLY_REPLAY_CLOSEOUT_READY" + ) + assert closeout["summary"]["controlled_dry_run_pre_apply_replay_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_pre_apply_replay_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_pre_apply_replay_closeout_pass_count"] == 12 + assert closeout["summary"]["controlled_dry_run_pre_apply_replay_closeout_waiting_count"] == 0 + assert closeout["summary"]["controlled_dry_run_final_executor_guard_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_no_apply_enforcement_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_post_receipt_parser_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_execution_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_command_artifact_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_execution_plan_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_readiness_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_package_ready_count"] == 1 + assert closeout["summary"]["controlled_apply_final_preflight_ready_count"] == 1 + assert closeout["summary"]["apply_executor_readiness_contract_count"] == 1 + assert closeout["summary"]["apply_executor_readiness_contract_field_count"] == 12 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_apply_executor_readiness_contract" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_apply_executor_readiness_closeout" + ] + is True + ) + assert future["pre_apply_replay_closeout_ready"] is True + assert future["apply_executor_readiness_contract_bound"] is True + assert future["dry_run_executor_invocation_allowed"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert replay_closeout["ready_for_future_database_apply_controlled_dry_run_pre_apply_replay_closeout"] is True + assert replay_closeout["pre_apply_replay_closeout_only"] is True + assert replay_closeout["apply_executor_readiness_contract_only"] is True + assert replay_closeout["dry_run_executor_invocation_allowed"] is False + assert replay_closeout["endpoint_execution_allowed"] is False + assert replay_closeout["sql_execution_allowed"] is False + assert replay_closeout["database_write_allowed"] is False + assert replay_closeout["database_apply_authorized"] is False + assert replay_closeout["executes_database_apply"] is False + assert replay_closeout["executes_endpoint_in_preview"] is False + assert replay_closeout["executes_sql_in_preview"] is False + assert replay_closeout["writes_database_in_preview"] is False + assert readiness["contract_id"] == future["apply_executor_readiness_contract_id"] + assert readiness["readiness_status"] == "apply_executor_readiness_contract_preview_ready" + assert readiness["readiness_mode"] == "apply_executor_readiness_contract_preview_only" + assert readiness["source_pre_apply_replay_verifier_id"] == replay["verification_id"] + assert readiness["apply_executor_readiness_contract_field_count"] == 12 + assert readiness["dry_run_executor_invocation_allowed"] is False + assert readiness["endpoint_execution_allowed"] is False + assert readiness["sql_execution_allowed"] is False + assert readiness["database_write_allowed"] is False + assert readiness["ready_for_database_apply_now"] is False + assert readiness["database_apply_authorized"] is False + assert readiness["executes_database_apply"] is False + assert readiness["executes_endpoint"] is False + assert readiness["executes_sql"] is False + assert readiness["writes_database"] is False + assert replay["replay_mode"] == "pre_apply_replay_preview_only" + assert replay["dry_run_executor_invocation_allowed"] is False + assert replay["database_apply_authorized"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_apply_executor_readiness_contract" + ] + is True + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_dry_run_executor_invocation_now"] is False + assert contract["writes_database"] is False + assert "final_executor_guard_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "pre_apply_replay_verifier_ready" in check_keys + assert "pre_apply_replay_preview_only" in check_keys + assert "apply_executor_readiness_contract_bound" in check_keys + assert "apply_executor_readiness_contract_blocks_apply" in check_keys + assert "final_guard_and_no_apply_enforcement_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bound" in check_keys + assert "final_executor_guard_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_apply_executor_readiness_closeout_waits_without_ready_pre_apply_replay_closeout(): + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_apply_executor_readiness_closeout( + _payload(), + batch_size=1, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_invocation_readiness_receipt" + ] + readiness_closeout = closeout[ + "controlled_dry_run_apply_executor_readiness_closeout" + ] + receipt = readiness_closeout["dry_run_invocation_readiness_receipt"] + contract = closeout[ + "controlled_dry_run_apply_executor_readiness_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_apply_executor_readiness_closeout_checks" + ] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_apply_executor_readiness_closeout" + ) + assert closeout["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_PRE_APPLY_REPLAY_CLOSEOUT" + ) + assert closeout["summary"]["controlled_dry_run_apply_executor_readiness_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_apply_executor_readiness_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_pre_apply_replay_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_apply_executor_readiness_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_apply_executor_readiness_closeout_field_count"] == 12 + assert closeout["summary"]["controlled_dry_run_apply_executor_readiness_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["dry_run_invocation_readiness_receipt_count"] == 1 + assert closeout["summary"]["dry_run_invocation_readiness_receipt_field_count"] == 12 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["apply_executor_readiness_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-apply-executor-readiness-closeout-" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_invocation_readiness_receipt" + ] + is False + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_invocation_receipt_closeout" + ] + is False + ) + assert future["dry_run_executor_invocation_allowed"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert readiness_closeout["authorization_material_type"] == ( + "controlled_dry_run_apply_executor_readiness_closeout" + ) + assert readiness_closeout["apply_executor_readiness_closeout_field_count"] == 12 + assert readiness_closeout["apply_executor_readiness_closeout_acceptance_gate_count"] == 10 + assert readiness_closeout["apply_executor_readiness_closeout_only"] is True + assert readiness_closeout["dry_run_invocation_readiness_receipt_only"] is True + assert readiness_closeout["dry_run_executor_invocation_allowed"] is False + assert readiness_closeout["ready_for_dry_run_executor_invocation_now"] is False + assert readiness_closeout["endpoint_execution_allowed"] is False + assert readiness_closeout["sql_execution_allowed"] is False + assert readiness_closeout["database_write_allowed"] is False + assert readiness_closeout["database_apply_authorized"] is False + assert readiness_closeout["executes_database_apply"] is False + assert readiness_closeout["executes_endpoint_in_preview"] is False + assert readiness_closeout["executes_sql_in_preview"] is False + assert readiness_closeout["writes_database_in_preview"] is False + assert receipt["receipt_status"] == "dry_run_invocation_readiness_receipt_preview_ready" + assert receipt["receipt_mode"] == "dry_run_invocation_readiness_preview_only" + assert receipt["dry_run_executor_invocation_allowed"] is False + assert receipt["ready_for_dry_run_executor_invocation_now"] is False + assert receipt["endpoint_execution_allowed"] is False + assert receipt["sql_execution_allowed"] is False + assert receipt["database_write_allowed"] is False + assert receipt["database_apply_authorized"] is False + assert receipt["executes_database_apply"] is False + assert receipt["executes_endpoint"] is False + assert receipt["executes_sql"] is False + assert receipt["writes_database"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_invocation_readiness_receipt" + ] + is False + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_dry_run_executor_invocation_now"] is False + assert contract["ready_for_actual_dry_run_execution_now"] is False + assert contract["writes_database"] is False + assert "pre_apply_replay_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "apply_executor_readiness_contract_ready" in check_keys + assert "apply_executor_readiness_contract_blocks_invocation" in check_keys + assert "dry_run_invocation_readiness_receipt_bound" in check_keys + assert "dry_run_invocation_readiness_receipt_no_execute" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_apply_executor_readiness_closeout_ready_after_fake_fetch_but_invocation_receipt_is_preview_only(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_apply_executor_readiness_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_invocation_readiness_receipt" + ] + readiness_closeout = closeout[ + "controlled_dry_run_apply_executor_readiness_closeout" + ] + receipt = readiness_closeout["dry_run_invocation_readiness_receipt"] + readiness = readiness_closeout["apply_executor_readiness_contract"] + contract = closeout[ + "controlled_dry_run_apply_executor_readiness_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_apply_executor_readiness_closeout_checks" + ] + ] + assert ( + closeout["result"] + == "DB_APPLY_CONTROLLED_DRY_RUN_APPLY_EXECUTOR_READINESS_CLOSEOUT_READY" + ) + assert closeout["summary"]["controlled_dry_run_apply_executor_readiness_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_apply_executor_readiness_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_apply_executor_readiness_closeout_pass_count"] == 12 + assert closeout["summary"]["controlled_dry_run_apply_executor_readiness_closeout_waiting_count"] == 0 + assert closeout["summary"]["controlled_dry_run_pre_apply_replay_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_final_executor_guard_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_no_apply_enforcement_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_post_receipt_parser_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_execution_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_command_artifact_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_execution_plan_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_readiness_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_package_ready_count"] == 1 + assert closeout["summary"]["controlled_apply_final_preflight_ready_count"] == 1 + assert closeout["summary"]["dry_run_invocation_readiness_receipt_count"] == 1 + assert closeout["summary"]["dry_run_invocation_readiness_receipt_field_count"] == 12 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_invocation_readiness_receipt" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_invocation_receipt_closeout" + ] + is True + ) + assert future["apply_executor_readiness_closeout_ready"] is True + assert future["dry_run_invocation_readiness_receipt_bound"] is True + assert future["dry_run_executor_invocation_allowed"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert readiness_closeout["ready_for_future_database_apply_controlled_dry_run_apply_executor_readiness_closeout"] is True + assert readiness_closeout["apply_executor_readiness_closeout_only"] is True + assert readiness_closeout["dry_run_invocation_readiness_receipt_only"] is True + assert readiness_closeout["dry_run_executor_invocation_allowed"] is False + assert readiness_closeout["ready_for_dry_run_executor_invocation_now"] is False + assert readiness_closeout["endpoint_execution_allowed"] is False + assert readiness_closeout["sql_execution_allowed"] is False + assert readiness_closeout["database_write_allowed"] is False + assert readiness_closeout["database_apply_authorized"] is False + assert readiness_closeout["executes_database_apply"] is False + assert readiness_closeout["executes_endpoint_in_preview"] is False + assert readiness_closeout["executes_sql_in_preview"] is False + assert readiness_closeout["writes_database_in_preview"] is False + assert receipt["receipt_id"] == future["dry_run_invocation_readiness_receipt_id"] + assert receipt["receipt_status"] == "dry_run_invocation_readiness_receipt_preview_ready" + assert receipt["receipt_mode"] == "dry_run_invocation_readiness_preview_only" + assert receipt["dry_run_invocation_readiness_receipt_field_count"] == 12 + assert receipt["source_apply_executor_readiness_contract_id"] == readiness["contract_id"] + assert receipt["dry_run_executor_invocation_allowed"] is False + assert receipt["ready_for_dry_run_executor_invocation_now"] is False + assert receipt["endpoint_execution_allowed"] is False + assert receipt["sql_execution_allowed"] is False + assert receipt["database_write_allowed"] is False + assert receipt["ready_for_database_apply_now"] is False + assert receipt["database_apply_authorized"] is False + assert receipt["executes_database_apply"] is False + assert receipt["executes_endpoint"] is False + assert receipt["executes_sql"] is False + assert receipt["writes_database"] is False + assert readiness["readiness_mode"] == "apply_executor_readiness_contract_preview_only" + assert readiness["dry_run_executor_invocation_allowed"] is False + assert readiness["database_apply_authorized"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_invocation_readiness_receipt" + ] + is True + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_dry_run_executor_invocation_now"] is False + assert contract["ready_for_actual_dry_run_execution_now"] is False + assert contract["writes_database"] is False + assert "pre_apply_replay_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "apply_executor_readiness_contract_ready" in check_keys + assert "apply_executor_readiness_contract_blocks_invocation" in check_keys + assert "dry_run_invocation_readiness_receipt_bound" in check_keys + assert "dry_run_invocation_readiness_receipt_no_execute" in check_keys + assert "pre_apply_replay_and_final_guard_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bound" in check_keys + assert "pre_apply_replay_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_invocation_receipt_closeout_waits_without_ready_apply_executor_closeout(): + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_invocation_receipt_closeout( + _payload(), + batch_size=1, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_no_write_invocation_package" + ] + invocation_closeout = closeout[ + "controlled_dry_run_invocation_receipt_closeout" + ] + package = invocation_closeout["no_write_invocation_package"] + contract = closeout[ + "controlled_dry_run_invocation_receipt_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_invocation_receipt_closeout_checks" + ] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_invocation_receipt_closeout" + ) + assert closeout["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_APPLY_EXECUTOR_READINESS_CLOSEOUT" + ) + assert closeout["summary"]["controlled_dry_run_invocation_receipt_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_invocation_receipt_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_apply_executor_readiness_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_invocation_receipt_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_invocation_receipt_closeout_field_count"] == 12 + assert closeout["summary"]["controlled_dry_run_invocation_receipt_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["no_write_invocation_package_count"] == 1 + assert closeout["summary"]["no_write_invocation_package_field_count"] == 12 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["invocation_receipt_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-invocation-receipt-closeout-" + ) + assert future["no_write_invocation_package_id"].endswith( + "-no-write-invocation-package" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_no_write_invocation_package" + ] + is False + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_no_write_invocation_package_closeout" + ] + is False + ) + assert future["dry_run_executor_invocation_allowed"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert invocation_closeout["authorization_material_type"] == ( + "controlled_dry_run_invocation_receipt_closeout" + ) + assert invocation_closeout["invocation_receipt_closeout_only"] is True + assert invocation_closeout["no_write_invocation_package_only"] is True + assert invocation_closeout["dry_run_executor_invocation_allowed"] is False + assert invocation_closeout["ready_for_dry_run_executor_invocation_now"] is False + assert invocation_closeout["endpoint_execution_allowed"] is False + assert invocation_closeout["sql_execution_allowed"] is False + assert invocation_closeout["database_write_allowed"] is False + assert invocation_closeout["database_apply_authorized"] is False + assert invocation_closeout["executes_database_apply"] is False + assert invocation_closeout["executes_endpoint_in_preview"] is False + assert invocation_closeout["executes_sql_in_preview"] is False + assert invocation_closeout["writes_database_in_preview"] is False + assert package["package_status"] == "no_write_invocation_package_preview_ready" + assert package["package_mode"] == "no_write_invocation_package_preview_only" + assert package["no_write_invocation_package_field_count"] == 12 + assert package["dry_run_executor_invocation_allowed"] is False + assert package["ready_for_no_write_dry_run_invocation_package_now"] is False + assert package["ready_for_actual_dry_run_execution_now"] is False + assert package["endpoint_execution_allowed"] is False + assert package["sql_execution_allowed"] is False + assert package["database_write_allowed"] is False + assert package["database_apply_authorized"] is False + assert package["executes_database_apply"] is False + assert package["executes_endpoint"] is False + assert package["executes_sql"] is False + assert package["writes_database"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_no_write_invocation_package" + ] + is False + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_dry_run_executor_invocation_now"] is False + assert contract["ready_for_actual_dry_run_execution_now"] is False + assert contract["writes_database"] is False + assert "apply_executor_readiness_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "dry_run_invocation_readiness_receipt_ready" in check_keys + assert "dry_run_invocation_readiness_receipt_no_execute" in check_keys + assert "no_write_invocation_package_bound" in check_keys + assert "no_write_invocation_package_blocks_execution" in check_keys + assert "apply_executor_readiness_and_replay_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bound" in check_keys + assert "apply_executor_readiness_closeout_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_invocation_receipt_closeout_ready_after_fake_fetch_but_package_is_no_write(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_invocation_receipt_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_no_write_invocation_package" + ] + invocation_closeout = closeout[ + "controlled_dry_run_invocation_receipt_closeout" + ] + package = invocation_closeout["no_write_invocation_package"] + receipt = invocation_closeout["dry_run_invocation_readiness_receipt"] + readiness_closeout = invocation_closeout["apply_executor_readiness_closeout"] + contract = closeout[ + "controlled_dry_run_invocation_receipt_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_invocation_receipt_closeout_checks" + ] + ] + assert ( + closeout["result"] + == "DB_APPLY_CONTROLLED_DRY_RUN_INVOCATION_RECEIPT_CLOSEOUT_READY" + ) + assert closeout["summary"]["controlled_dry_run_invocation_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_invocation_receipt_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_invocation_receipt_closeout_pass_count"] == 12 + assert closeout["summary"]["controlled_dry_run_invocation_receipt_closeout_waiting_count"] == 0 + assert closeout["summary"]["controlled_dry_run_apply_executor_readiness_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_pre_apply_replay_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_final_executor_guard_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_no_apply_enforcement_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_post_receipt_parser_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_execution_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_command_artifact_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_execution_plan_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_readiness_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_package_ready_count"] == 1 + assert closeout["summary"]["controlled_apply_final_preflight_ready_count"] == 1 + assert closeout["summary"]["no_write_invocation_package_count"] == 1 + assert closeout["summary"]["no_write_invocation_package_field_count"] == 12 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_no_write_invocation_package" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_no_write_invocation_package_closeout" + ] + is True + ) + assert future["invocation_receipt_closeout_ready"] is True + assert future["no_write_invocation_package_bound"] is True + assert future["dry_run_executor_invocation_allowed"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["ready_for_actual_dry_run_execution_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert invocation_closeout["ready_for_future_database_apply_controlled_dry_run_invocation_receipt_closeout"] is True + assert invocation_closeout["invocation_receipt_closeout_field_count"] == 12 + assert invocation_closeout["invocation_receipt_closeout_acceptance_gate_count"] == 10 + assert invocation_closeout["no_write_invocation_package_count"] == 1 + assert invocation_closeout["no_write_invocation_package_field_count"] == 12 + assert invocation_closeout["dry_run_executor_invocation_allowed"] is False + assert invocation_closeout["ready_for_dry_run_executor_invocation_now"] is False + assert invocation_closeout["endpoint_execution_allowed"] is False + assert invocation_closeout["sql_execution_allowed"] is False + assert invocation_closeout["database_write_allowed"] is False + assert invocation_closeout["database_apply_authorized"] is False + assert invocation_closeout["executes_database_apply"] is False + assert invocation_closeout["executes_endpoint_in_preview"] is False + assert invocation_closeout["executes_sql_in_preview"] is False + assert invocation_closeout["writes_database_in_preview"] is False + assert package["package_id"] == future["no_write_invocation_package_id"] + assert package["source_invocation_receipt_closeout_id"] == future["invocation_receipt_closeout_id"] + assert package["source_dry_run_invocation_readiness_receipt_id"] == receipt["receipt_id"] + assert package["source_apply_executor_readiness_closeout_id"] == readiness_closeout["apply_executor_readiness_closeout_id"] + assert package["required_command_shape_hash"] == receipt["required_command_shape_hash"] + assert package["package_status"] == "no_write_invocation_package_preview_ready" + assert package["package_mode"] == "no_write_invocation_package_preview_only" + assert package["dry_run_executor_invocation_allowed"] is False + assert package["ready_for_no_write_dry_run_invocation_package_now"] is False + assert package["ready_for_actual_dry_run_execution_now"] is False + assert package["endpoint_execution_allowed"] is False + assert package["sql_execution_allowed"] is False + assert package["database_write_allowed"] is False + assert package["ready_for_database_apply_now"] is False + assert package["database_apply_authorized"] is False + assert package["issues_database_apply_authorization"] is False + assert package["signs_database_apply_authorization"] is False + assert package["executes_authorization_evidence"] is False + assert package["executes_database_apply"] is False + assert package["executes_endpoint"] is False + assert package["executes_sql"] is False + assert package["writes_database"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_no_write_invocation_package" + ] + is True + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_dry_run_executor_invocation_now"] is False + assert contract["ready_for_actual_dry_run_execution_now"] is False + assert contract["writes_database"] is False + assert "apply_executor_readiness_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "dry_run_invocation_readiness_receipt_ready" in check_keys + assert "dry_run_invocation_readiness_receipt_no_execute" in check_keys + assert "no_write_invocation_package_bound" in check_keys + assert "no_write_invocation_package_blocks_execution" in check_keys + assert "apply_executor_readiness_and_replay_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bound" in check_keys + assert "apply_executor_readiness_closeout_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_no_write_invocation_package_closeout_waits_without_ready_invocation_receipt_closeout(): + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_no_write_invocation_package_closeout( + _payload(), + batch_size=1, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_execution_preflight_guard" + ] + package_closeout = closeout[ + "controlled_dry_run_no_write_invocation_package_closeout" + ] + guard = package_closeout["execution_preflight_guard"] + package = package_closeout["no_write_invocation_package"] + contract = closeout[ + "controlled_dry_run_no_write_invocation_package_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_no_write_invocation_package_closeout_checks" + ] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_no_write_invocation_package_closeout" + ) + assert closeout["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_INVOCATION_RECEIPT_CLOSEOUT" + ) + assert closeout["summary"]["controlled_dry_run_no_write_invocation_package_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_no_write_invocation_package_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_invocation_receipt_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_no_write_invocation_package_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_no_write_invocation_package_closeout_field_count"] == 12 + assert closeout["summary"]["controlled_dry_run_no_write_invocation_package_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["execution_preflight_guard_count"] == 1 + assert closeout["summary"]["execution_preflight_guard_field_count"] == 12 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["no_write_invocation_package_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-no-write-invocation-package-closeout-" + ) + assert future["execution_preflight_guard_id"].endswith( + "-execution-preflight-guard" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_execution_preflight_guard" + ] + is False + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_execution_preflight_guard_closeout" + ] + is False + ) + assert future["dry_run_executor_invocation_allowed"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["ready_for_actual_dry_run_execution_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert package_closeout["authorization_material_type"] == ( + "controlled_dry_run_no_write_invocation_package_closeout" + ) + assert package_closeout["no_write_invocation_package_closeout_only"] is True + assert package_closeout["execution_preflight_guard_only"] is True + assert package_closeout["dry_run_executor_invocation_allowed"] is False + assert package_closeout["ready_for_dry_run_executor_invocation_now"] is False + assert package_closeout["endpoint_execution_allowed"] is False + assert package_closeout["sql_execution_allowed"] is False + assert package_closeout["database_write_allowed"] is False + assert package_closeout["database_apply_authorized"] is False + assert package_closeout["executes_database_apply"] is False + assert package_closeout["executes_endpoint_in_preview"] is False + assert package_closeout["executes_sql_in_preview"] is False + assert package_closeout["writes_database_in_preview"] is False + assert package["package_mode"] == "no_write_invocation_package_preview_only" + assert package["dry_run_executor_invocation_allowed"] is False + assert package["executes_database_apply"] is False + assert guard["guard_status"] == "execution_preflight_guard_preview_ready" + assert guard["guard_mode"] == "execution_preflight_guard_preview_only" + assert guard["execution_preflight_guard_field_count"] == 12 + assert guard["dry_run_executor_invocation_allowed"] is False + assert guard["ready_for_execution_preflight_guard_now"] is False + assert guard["ready_for_dry_run_executor_invocation_now"] is False + assert guard["ready_for_actual_dry_run_execution_now"] is False + assert guard["endpoint_execution_allowed"] is False + assert guard["sql_execution_allowed"] is False + assert guard["database_write_allowed"] is False + assert guard["database_apply_authorized"] is False + assert guard["executes_database_apply"] is False + assert guard["executes_endpoint"] is False + assert guard["executes_sql"] is False + assert guard["writes_database"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_execution_preflight_guard" + ] + is False + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_dry_run_executor_invocation_now"] is False + assert contract["ready_for_actual_dry_run_execution_now"] is False + assert contract["writes_database"] is False + assert "invocation_receipt_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "no_write_invocation_package_ready" in check_keys + assert "no_write_invocation_package_no_execute" in check_keys + assert "execution_preflight_guard_bound" in check_keys + assert "execution_preflight_guard_blocks_execution" in check_keys + assert "invocation_receipt_and_apply_readiness_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bound" in check_keys + assert "invocation_receipt_closeout_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_no_write_invocation_package_closeout_ready_after_fake_fetch_but_guard_is_preview_only(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_no_write_invocation_package_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_execution_preflight_guard" + ] + package_closeout = closeout[ + "controlled_dry_run_no_write_invocation_package_closeout" + ] + guard = package_closeout["execution_preflight_guard"] + package = package_closeout["no_write_invocation_package"] + invocation_closeout = package_closeout["invocation_receipt_closeout"] + contract = closeout[ + "controlled_dry_run_no_write_invocation_package_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_no_write_invocation_package_closeout_checks" + ] + ] + assert ( + closeout["result"] + == "DB_APPLY_CONTROLLED_DRY_RUN_NO_WRITE_INVOCATION_PACKAGE_CLOSEOUT_READY" + ) + assert closeout["summary"]["controlled_dry_run_no_write_invocation_package_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_no_write_invocation_package_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_no_write_invocation_package_closeout_pass_count"] == 12 + assert closeout["summary"]["controlled_dry_run_no_write_invocation_package_closeout_waiting_count"] == 0 + assert closeout["summary"]["controlled_dry_run_invocation_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_apply_executor_readiness_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_pre_apply_replay_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_final_executor_guard_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_no_apply_enforcement_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_post_receipt_parser_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_execution_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_command_artifact_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_execution_plan_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_readiness_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_package_ready_count"] == 1 + assert closeout["summary"]["controlled_apply_final_preflight_ready_count"] == 1 + assert closeout["summary"]["execution_preflight_guard_count"] == 1 + assert closeout["summary"]["execution_preflight_guard_field_count"] == 12 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_execution_preflight_guard" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_execution_preflight_guard_closeout" + ] + is True + ) + assert future["no_write_invocation_package_closeout_ready"] is True + assert future["execution_preflight_guard_bound"] is True + assert future["dry_run_executor_invocation_allowed"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["ready_for_actual_dry_run_execution_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert ( + package_closeout[ + "ready_for_future_database_apply_controlled_dry_run_no_write_invocation_package_closeout" + ] + is True + ) + assert package_closeout["no_write_invocation_package_closeout_field_count"] == 12 + assert package_closeout["no_write_invocation_package_closeout_acceptance_gate_count"] == 10 + assert package_closeout["execution_preflight_guard_count"] == 1 + assert package_closeout["execution_preflight_guard_field_count"] == 12 + assert package_closeout["dry_run_executor_invocation_allowed"] is False + assert package_closeout["ready_for_dry_run_executor_invocation_now"] is False + assert package_closeout["endpoint_execution_allowed"] is False + assert package_closeout["sql_execution_allowed"] is False + assert package_closeout["database_write_allowed"] is False + assert package_closeout["database_apply_authorized"] is False + assert package_closeout["executes_database_apply"] is False + assert package_closeout["executes_endpoint_in_preview"] is False + assert package_closeout["executes_sql_in_preview"] is False + assert package_closeout["writes_database_in_preview"] is False + assert guard["guard_id"] == future["execution_preflight_guard_id"] + assert guard["source_no_write_invocation_package_closeout_id"] == future["no_write_invocation_package_closeout_id"] + assert guard["source_no_write_invocation_package_id"] == package["package_id"] + assert guard["source_invocation_receipt_closeout_id"] == invocation_closeout["invocation_receipt_closeout_id"] + assert guard["required_command_shape_hash"] == package["required_command_shape_hash"] + assert guard["guard_status"] == "execution_preflight_guard_preview_ready" + assert guard["guard_mode"] == "execution_preflight_guard_preview_only" + assert guard["dry_run_executor_invocation_allowed"] is False + assert guard["ready_for_execution_preflight_guard_now"] is False + assert guard["ready_for_dry_run_executor_invocation_now"] is False + assert guard["ready_for_actual_dry_run_execution_now"] is False + assert guard["endpoint_execution_allowed"] is False + assert guard["sql_execution_allowed"] is False + assert guard["database_write_allowed"] is False + assert guard["ready_for_database_apply_now"] is False + assert guard["database_apply_authorized"] is False + assert guard["issues_database_apply_authorization"] is False + assert guard["signs_database_apply_authorization"] is False + assert guard["executes_authorization_evidence"] is False + assert guard["executes_database_apply"] is False + assert guard["executes_endpoint"] is False + assert guard["executes_sql"] is False + assert guard["writes_database"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_execution_preflight_guard" + ] + is True + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_dry_run_executor_invocation_now"] is False + assert contract["ready_for_actual_dry_run_execution_now"] is False + assert contract["writes_database"] is False + assert "invocation_receipt_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "no_write_invocation_package_ready" in check_keys + assert "no_write_invocation_package_no_execute" in check_keys + assert "execution_preflight_guard_bound" in check_keys + assert "execution_preflight_guard_blocks_execution" in check_keys + assert "invocation_receipt_and_apply_readiness_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bound" in check_keys + assert "invocation_receipt_closeout_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_execution_preflight_guard_closeout_waits_without_ready_package_closeout(): + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_execution_preflight_guard_closeout( + _payload(), + batch_size=1, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_runner_invocation_boundary" + ] + guard_closeout = closeout[ + "controlled_dry_run_execution_preflight_guard_closeout" + ] + boundary = guard_closeout["runner_invocation_boundary"] + guard = guard_closeout["execution_preflight_guard"] + contract = closeout[ + "controlled_dry_run_execution_preflight_guard_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_execution_preflight_guard_closeout_checks" + ] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_execution_preflight_guard_closeout" + ) + assert closeout["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_NO_WRITE_INVOCATION_PACKAGE_CLOSEOUT" + ) + assert closeout["summary"]["controlled_dry_run_execution_preflight_guard_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_execution_preflight_guard_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_no_write_invocation_package_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_execution_preflight_guard_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_execution_preflight_guard_closeout_field_count"] == 12 + assert closeout["summary"]["controlled_dry_run_execution_preflight_guard_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["runner_invocation_boundary_count"] == 1 + assert closeout["summary"]["runner_invocation_boundary_field_count"] == 12 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["execution_preflight_guard_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-execution-preflight-guard-closeout-" + ) + assert future["runner_invocation_boundary_id"].endswith( + "-runner-invocation-boundary" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_runner_invocation_boundary" + ] + is False + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_runner_invocation_boundary_closeout" + ] + is False + ) + assert future["dry_run_executor_invocation_allowed"] is False + assert future["runner_invocation_allowed"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["ready_for_actual_dry_run_execution_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert guard_closeout["authorization_material_type"] == ( + "controlled_dry_run_execution_preflight_guard_closeout" + ) + assert guard_closeout["execution_preflight_guard_closeout_only"] is True + assert guard_closeout["runner_invocation_boundary_only"] is True + assert guard_closeout["dry_run_executor_invocation_allowed"] is False + assert guard_closeout["runner_invocation_allowed"] is False + assert guard_closeout["ready_for_dry_run_executor_invocation_now"] is False + assert guard_closeout["endpoint_execution_allowed"] is False + assert guard_closeout["sql_execution_allowed"] is False + assert guard_closeout["database_write_allowed"] is False + assert guard_closeout["database_apply_authorized"] is False + assert guard_closeout["executes_database_apply"] is False + assert guard_closeout["executes_endpoint_in_preview"] is False + assert guard_closeout["executes_sql_in_preview"] is False + assert guard_closeout["writes_database_in_preview"] is False + assert guard["guard_mode"] == "execution_preflight_guard_preview_only" + assert guard["dry_run_executor_invocation_allowed"] is False + assert guard["executes_database_apply"] is False + assert boundary["boundary_status"] == "runner_invocation_boundary_preview_ready" + assert boundary["boundary_mode"] == "runner_invocation_boundary_preview_only" + assert boundary["runner_invocation_boundary_field_count"] == 12 + assert boundary["dry_run_executor_invocation_allowed"] is False + assert boundary["runner_invocation_allowed"] is False + assert boundary["ready_for_runner_invocation_boundary_now"] is False + assert boundary["ready_for_dry_run_executor_invocation_now"] is False + assert boundary["ready_for_actual_dry_run_execution_now"] is False + assert boundary["endpoint_execution_allowed"] is False + assert boundary["sql_execution_allowed"] is False + assert boundary["database_write_allowed"] is False + assert boundary["database_apply_authorized"] is False + assert boundary["executes_database_apply"] is False + assert boundary["executes_endpoint"] is False + assert boundary["executes_sql"] is False + assert boundary["writes_database"] is False + assert boundary["captures_stdout"] is False + assert boundary["captures_stderr"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_runner_invocation_boundary" + ] + is False + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_dry_run_executor_invocation_now"] is False + assert contract["ready_for_actual_dry_run_execution_now"] is False + assert contract["writes_database"] is False + assert "no_write_invocation_package_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "execution_preflight_guard_ready" in check_keys + assert "execution_preflight_guard_no_execute" in check_keys + assert "runner_invocation_boundary_bound" in check_keys + assert "runner_invocation_boundary_blocks_execution" in check_keys + assert "no_write_package_and_invocation_receipt_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bound" in check_keys + assert "no_write_invocation_package_closeout_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_execution_preflight_guard_closeout_ready_after_fake_fetch_but_boundary_blocks_runner(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_execution_preflight_guard_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_runner_invocation_boundary" + ] + guard_closeout = closeout[ + "controlled_dry_run_execution_preflight_guard_closeout" + ] + boundary = guard_closeout["runner_invocation_boundary"] + guard = guard_closeout["execution_preflight_guard"] + package_closeout = guard_closeout["no_write_invocation_package_closeout"] + contract = closeout[ + "controlled_dry_run_execution_preflight_guard_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_execution_preflight_guard_closeout_checks" + ] + ] + assert ( + closeout["result"] + == "DB_APPLY_CONTROLLED_DRY_RUN_EXECUTION_PREFLIGHT_GUARD_CLOSEOUT_READY" + ) + assert closeout["summary"]["controlled_dry_run_execution_preflight_guard_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_execution_preflight_guard_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_execution_preflight_guard_closeout_pass_count"] == 12 + assert closeout["summary"]["controlled_dry_run_execution_preflight_guard_closeout_waiting_count"] == 0 + assert closeout["summary"]["controlled_dry_run_no_write_invocation_package_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_invocation_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_apply_executor_readiness_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_pre_apply_replay_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_final_executor_guard_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_no_apply_enforcement_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_post_receipt_parser_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_execution_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_command_artifact_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_execution_plan_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_readiness_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_package_ready_count"] == 1 + assert closeout["summary"]["controlled_apply_final_preflight_ready_count"] == 1 + assert closeout["summary"]["runner_invocation_boundary_count"] == 1 + assert closeout["summary"]["runner_invocation_boundary_field_count"] == 12 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_runner_invocation_boundary" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_runner_invocation_boundary_closeout" + ] + is True + ) + assert future["execution_preflight_guard_closeout_ready"] is True + assert future["runner_invocation_boundary_bound"] is True + assert future["dry_run_executor_invocation_allowed"] is False + assert future["runner_invocation_allowed"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["ready_for_actual_dry_run_execution_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert ( + guard_closeout[ + "ready_for_future_database_apply_controlled_dry_run_execution_preflight_guard_closeout" + ] + is True + ) + assert guard_closeout["execution_preflight_guard_closeout_field_count"] == 12 + assert guard_closeout["execution_preflight_guard_closeout_acceptance_gate_count"] == 10 + assert guard_closeout["runner_invocation_boundary_count"] == 1 + assert guard_closeout["runner_invocation_boundary_field_count"] == 12 + assert guard_closeout["dry_run_executor_invocation_allowed"] is False + assert guard_closeout["runner_invocation_allowed"] is False + assert guard_closeout["ready_for_dry_run_executor_invocation_now"] is False + assert guard_closeout["endpoint_execution_allowed"] is False + assert guard_closeout["sql_execution_allowed"] is False + assert guard_closeout["database_write_allowed"] is False + assert guard_closeout["database_apply_authorized"] is False + assert guard_closeout["executes_database_apply"] is False + assert guard_closeout["executes_endpoint_in_preview"] is False + assert guard_closeout["executes_sql_in_preview"] is False + assert guard_closeout["writes_database_in_preview"] is False + assert boundary["boundary_id"] == future["runner_invocation_boundary_id"] + assert boundary["source_execution_preflight_guard_closeout_id"] == future["execution_preflight_guard_closeout_id"] + assert boundary["source_execution_preflight_guard_id"] == guard["guard_id"] + assert boundary["source_no_write_invocation_package_closeout_id"] == package_closeout["no_write_invocation_package_closeout_id"] + assert boundary["required_command_shape_hash"] == guard["required_command_shape_hash"] + assert boundary["boundary_status"] == "runner_invocation_boundary_preview_ready" + assert boundary["boundary_mode"] == "runner_invocation_boundary_preview_only" + assert boundary["dry_run_executor_invocation_allowed"] is False + assert boundary["runner_invocation_allowed"] is False + assert boundary["ready_for_runner_invocation_boundary_now"] is False + assert boundary["ready_for_dry_run_executor_invocation_now"] is False + assert boundary["ready_for_actual_dry_run_execution_now"] is False + assert boundary["endpoint_execution_allowed"] is False + assert boundary["sql_execution_allowed"] is False + assert boundary["database_write_allowed"] is False + assert boundary["ready_for_database_apply_now"] is False + assert boundary["database_apply_authorized"] is False + assert boundary["issues_database_apply_authorization"] is False + assert boundary["signs_database_apply_authorization"] is False + assert boundary["executes_authorization_evidence"] is False + assert boundary["executes_database_apply"] is False + assert boundary["executes_endpoint"] is False + assert boundary["executes_sql"] is False + assert boundary["writes_database"] is False + assert boundary["captures_stdout"] is False + assert boundary["captures_stderr"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_runner_invocation_boundary" + ] + is True + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_dry_run_executor_invocation_now"] is False + assert contract["ready_for_actual_dry_run_execution_now"] is False + assert contract["writes_database"] is False + assert "no_write_invocation_package_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "execution_preflight_guard_ready" in check_keys + assert "execution_preflight_guard_no_execute" in check_keys + assert "runner_invocation_boundary_bound" in check_keys + assert "runner_invocation_boundary_blocks_execution" in check_keys + assert "no_write_package_and_invocation_receipt_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bound" in check_keys + assert "no_write_invocation_package_closeout_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_runner_invocation_boundary_closeout_waits_without_ready_guard_closeout(): + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_runner_invocation_boundary_closeout( + _payload(), + batch_size=1, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_no_execution_receipt_handoff" + ] + boundary_closeout = closeout[ + "controlled_dry_run_runner_invocation_boundary_closeout" + ] + handoff = boundary_closeout["no_execution_receipt_handoff"] + boundary = boundary_closeout["runner_invocation_boundary"] + contract = closeout[ + "controlled_dry_run_runner_invocation_boundary_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_runner_invocation_boundary_closeout_checks" + ] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_runner_invocation_boundary_closeout" + ) + assert closeout["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_EXECUTION_PREFLIGHT_GUARD_CLOSEOUT" + ) + assert closeout["summary"]["controlled_dry_run_runner_invocation_boundary_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_runner_invocation_boundary_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_execution_preflight_guard_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_runner_invocation_boundary_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_invocation_boundary_closeout_field_count"] == 12 + assert closeout["summary"]["controlled_dry_run_runner_invocation_boundary_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["no_execution_receipt_handoff_count"] == 1 + assert closeout["summary"]["no_execution_receipt_handoff_field_count"] == 12 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["runner_invocation_boundary_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-runner-invocation-boundary-closeout-" + ) + assert future["no_execution_receipt_handoff_id"].endswith( + "-no-execution-receipt-handoff" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_no_execution_receipt_handoff" + ] + is False + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_no_execution_receipt_handoff_closeout" + ] + is False + ) + assert future["dry_run_executor_invocation_allowed"] is False + assert future["runner_invocation_allowed"] is False + assert future["execution_receipt_present"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["ready_for_actual_dry_run_execution_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert future["stdout_included"] is False + assert future["stderr_included"] is False + assert boundary_closeout["authorization_material_type"] == ( + "controlled_dry_run_runner_invocation_boundary_closeout" + ) + assert boundary_closeout["runner_invocation_boundary_closeout_only"] is True + assert boundary_closeout["no_execution_receipt_handoff_only"] is True + assert boundary_closeout["dry_run_executor_invocation_allowed"] is False + assert boundary_closeout["runner_invocation_allowed"] is False + assert boundary_closeout["ready_for_dry_run_executor_invocation_now"] is False + assert boundary_closeout["endpoint_execution_allowed"] is False + assert boundary_closeout["sql_execution_allowed"] is False + assert boundary_closeout["database_write_allowed"] is False + assert boundary_closeout["database_apply_authorized"] is False + assert boundary_closeout["executes_database_apply"] is False + assert boundary_closeout["executes_endpoint_in_preview"] is False + assert boundary_closeout["executes_sql_in_preview"] is False + assert boundary_closeout["writes_database_in_preview"] is False + assert boundary_closeout["captures_stdout"] is False + assert boundary_closeout["captures_stderr"] is False + assert boundary["boundary_mode"] == "runner_invocation_boundary_preview_only" + assert boundary["dry_run_executor_invocation_allowed"] is False + assert boundary["runner_invocation_allowed"] is False + assert handoff["handoff_status"] == "no_execution_receipt_handoff_preview_ready" + assert handoff["handoff_mode"] == "no_execution_receipt_handoff_preview_only" + assert handoff["no_execution_receipt_handoff_field_count"] == 12 + assert handoff["execution_receipt_present"] is False + assert handoff["execution_receipt_required"] is False + assert handoff["dry_run_executor_invocation_allowed"] is False + assert handoff["runner_invocation_allowed"] is False + assert handoff["ready_for_no_execution_receipt_handoff_now"] is False + assert handoff["ready_for_dry_run_executor_invocation_now"] is False + assert handoff["ready_for_actual_dry_run_execution_now"] is False + assert handoff["endpoint_execution_allowed"] is False + assert handoff["sql_execution_allowed"] is False + assert handoff["database_write_allowed"] is False + assert handoff["database_apply_authorized"] is False + assert handoff["executes_database_apply"] is False + assert handoff["executes_endpoint"] is False + assert handoff["executes_sql"] is False + assert handoff["writes_database"] is False + assert handoff["stdout_included"] is False + assert handoff["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_no_execution_receipt_handoff" + ] + is False + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_dry_run_executor_invocation_now"] is False + assert contract["ready_for_actual_dry_run_execution_now"] is False + assert contract["writes_database"] is False + assert "execution_preflight_guard_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "runner_invocation_boundary_ready" in check_keys + assert "runner_invocation_boundary_no_execute" in check_keys + assert "no_execution_receipt_handoff_bound" in check_keys + assert "no_execution_receipt_handoff_blocks_execution" in check_keys + assert "execution_preflight_guard_and_no_write_package_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bound" in check_keys + assert "execution_preflight_guard_closeout_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_runner_invocation_boundary_closeout_ready_after_fake_fetch_but_handoff_is_no_execution(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_runner_invocation_boundary_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_no_execution_receipt_handoff" + ] + boundary_closeout = closeout[ + "controlled_dry_run_runner_invocation_boundary_closeout" + ] + handoff = boundary_closeout["no_execution_receipt_handoff"] + boundary = boundary_closeout["runner_invocation_boundary"] + guard_closeout = boundary_closeout["execution_preflight_guard_closeout"] + contract = closeout[ + "controlled_dry_run_runner_invocation_boundary_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_runner_invocation_boundary_closeout_checks" + ] + ] + assert ( + closeout["result"] + == "DB_APPLY_CONTROLLED_DRY_RUN_RUNNER_INVOCATION_BOUNDARY_CLOSEOUT_READY" + ) + assert closeout["summary"]["controlled_dry_run_runner_invocation_boundary_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_invocation_boundary_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_runner_invocation_boundary_closeout_pass_count"] == 12 + assert closeout["summary"]["controlled_dry_run_runner_invocation_boundary_closeout_waiting_count"] == 0 + assert closeout["summary"]["controlled_dry_run_execution_preflight_guard_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_no_write_invocation_package_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_invocation_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_apply_executor_readiness_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_pre_apply_replay_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_final_executor_guard_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_no_apply_enforcement_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_post_receipt_parser_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_execution_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_command_artifact_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_execution_plan_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_readiness_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_package_ready_count"] == 1 + assert closeout["summary"]["controlled_apply_final_preflight_ready_count"] == 1 + assert closeout["summary"]["no_execution_receipt_handoff_count"] == 1 + assert closeout["summary"]["no_execution_receipt_handoff_field_count"] == 12 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_no_execution_receipt_handoff" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_no_execution_receipt_handoff_closeout" + ] + is True + ) + assert future["runner_invocation_boundary_closeout_ready"] is True + assert future["no_execution_receipt_handoff_bound"] is True + assert future["dry_run_executor_invocation_allowed"] is False + assert future["runner_invocation_allowed"] is False + assert future["dry_run_execution_performed"] is False + assert future["execution_receipt_present"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["ready_for_actual_dry_run_execution_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert future["stdout_included"] is False + assert future["stderr_included"] is False + assert ( + boundary_closeout[ + "ready_for_future_database_apply_controlled_dry_run_runner_invocation_boundary_closeout" + ] + is True + ) + assert boundary_closeout["runner_invocation_boundary_closeout_field_count"] == 12 + assert boundary_closeout["runner_invocation_boundary_closeout_acceptance_gate_count"] == 10 + assert boundary_closeout["no_execution_receipt_handoff_count"] == 1 + assert boundary_closeout["no_execution_receipt_handoff_field_count"] == 12 + assert boundary_closeout["dry_run_executor_invocation_allowed"] is False + assert boundary_closeout["runner_invocation_allowed"] is False + assert boundary_closeout["ready_for_dry_run_executor_invocation_now"] is False + assert boundary_closeout["endpoint_execution_allowed"] is False + assert boundary_closeout["sql_execution_allowed"] is False + assert boundary_closeout["database_write_allowed"] is False + assert boundary_closeout["database_apply_authorized"] is False + assert boundary_closeout["executes_database_apply"] is False + assert boundary_closeout["executes_endpoint_in_preview"] is False + assert boundary_closeout["executes_sql_in_preview"] is False + assert boundary_closeout["writes_database_in_preview"] is False + assert boundary_closeout["captures_stdout"] is False + assert boundary_closeout["captures_stderr"] is False + assert handoff["handoff_id"] == future["no_execution_receipt_handoff_id"] + assert handoff["source_runner_invocation_boundary_closeout_id"] == future["runner_invocation_boundary_closeout_id"] + assert handoff["source_runner_invocation_boundary_id"] == boundary["boundary_id"] + assert handoff["source_execution_preflight_guard_closeout_id"] == guard_closeout["execution_preflight_guard_closeout_id"] + assert handoff["required_command_shape_hash"] == boundary["required_command_shape_hash"] + assert handoff["handoff_status"] == "no_execution_receipt_handoff_preview_ready" + assert handoff["handoff_mode"] == "no_execution_receipt_handoff_preview_only" + assert handoff["execution_receipt_present"] is False + assert handoff["execution_receipt_required"] is False + assert handoff["dry_run_executor_invocation_allowed"] is False + assert handoff["runner_invocation_allowed"] is False + assert handoff["ready_for_no_execution_receipt_handoff_now"] is False + assert handoff["ready_for_dry_run_executor_invocation_now"] is False + assert handoff["ready_for_actual_dry_run_execution_now"] is False + assert handoff["endpoint_execution_allowed"] is False + assert handoff["sql_execution_allowed"] is False + assert handoff["database_write_allowed"] is False + assert handoff["ready_for_database_apply_now"] is False + assert handoff["database_apply_authorized"] is False + assert handoff["issues_database_apply_authorization"] is False + assert handoff["signs_database_apply_authorization"] is False + assert handoff["executes_authorization_evidence"] is False + assert handoff["executes_database_apply"] is False + assert handoff["executes_endpoint"] is False + assert handoff["executes_sql"] is False + assert handoff["writes_database"] is False + assert handoff["captures_stdout"] is False + assert handoff["captures_stderr"] is False + assert handoff["stdout_included"] is False + assert handoff["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_no_execution_receipt_handoff" + ] + is True + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_dry_run_executor_invocation_now"] is False + assert contract["ready_for_actual_dry_run_execution_now"] is False + assert contract["writes_database"] is False + assert "execution_preflight_guard_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "runner_invocation_boundary_ready" in check_keys + assert "runner_invocation_boundary_no_execute" in check_keys + assert "no_execution_receipt_handoff_bound" in check_keys + assert "no_execution_receipt_handoff_blocks_execution" in check_keys + assert "execution_preflight_guard_and_no_write_package_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bound" in check_keys + assert "execution_preflight_guard_closeout_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_no_execution_receipt_handoff_closeout_waits_without_ready_boundary_closeout(): + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_no_execution_receipt_handoff_closeout( + _payload(), + batch_size=1, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_final_no_runner_execution_proof" + ] + handoff_closeout = closeout[ + "controlled_dry_run_no_execution_receipt_handoff_closeout" + ] + proof = handoff_closeout["final_no_runner_execution_proof"] + contract = closeout[ + "controlled_dry_run_no_execution_receipt_handoff_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_no_execution_receipt_handoff_closeout_checks" + ] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_no_execution_receipt_handoff_closeout" + ) + assert closeout["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_RUNNER_INVOCATION_BOUNDARY_CLOSEOUT" + ) + assert closeout["summary"]["controlled_dry_run_no_execution_receipt_handoff_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_no_execution_receipt_handoff_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_runner_invocation_boundary_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_no_execution_receipt_handoff_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_no_execution_receipt_handoff_closeout_field_count"] == 12 + assert closeout["summary"]["controlled_dry_run_no_execution_receipt_handoff_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["final_no_runner_execution_proof_count"] == 1 + assert closeout["summary"]["final_no_runner_execution_proof_field_count"] == 12 + assert closeout["summary"]["dry_run_executor_invoked_count"] == 0 + assert closeout["summary"]["runner_invocation_performed_count"] == 0 + assert closeout["summary"]["endpoint_executed_count"] == 0 + assert closeout["summary"]["sql_executed_count"] == 0 + assert closeout["summary"]["database_written_count"] == 0 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["no_execution_receipt_handoff_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-no-execution-receipt-handoff-closeout-" + ) + assert future["final_no_runner_execution_proof_id"].endswith( + "-final-no-runner-execution-proof" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_final_no_runner_execution_proof" + ] + is False + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_final_no_runner_execution_proof_closeout" + ] + is False + ) + assert future["no_execution_receipt_handoff_closeout_ready"] is False + assert future["final_no_runner_execution_proof_bound"] is False + assert future["dry_run_executor_invoked"] is False + assert future["runner_invocation_performed"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["execution_receipt_present"] is False + assert future["dry_run_executor_invocation_allowed"] is False + assert future["runner_invocation_allowed"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["ready_for_actual_dry_run_execution_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert future["stdout_included"] is False + assert future["stderr_included"] is False + assert handoff_closeout["authorization_material_type"] == ( + "controlled_dry_run_no_execution_receipt_handoff_closeout" + ) + assert handoff_closeout["no_execution_receipt_handoff_closeout_only"] is True + assert handoff_closeout["final_no_runner_execution_proof_only"] is True + assert handoff_closeout["dry_run_executor_invoked"] is False + assert handoff_closeout["runner_invocation_performed"] is False + assert handoff_closeout["endpoint_executed"] is False + assert handoff_closeout["sql_executed"] is False + assert handoff_closeout["database_written"] is False + assert handoff_closeout["dry_run_executor_invocation_allowed"] is False + assert handoff_closeout["runner_invocation_allowed"] is False + assert handoff_closeout["ready_for_dry_run_executor_invocation_now"] is False + assert handoff_closeout["endpoint_execution_allowed"] is False + assert handoff_closeout["sql_execution_allowed"] is False + assert handoff_closeout["database_write_allowed"] is False + assert handoff_closeout["database_apply_authorized"] is False + assert handoff_closeout["executes_database_apply"] is False + assert handoff_closeout["executes_endpoint_in_preview"] is False + assert handoff_closeout["executes_sql_in_preview"] is False + assert handoff_closeout["writes_database_in_preview"] is False + assert handoff_closeout["stdout_included"] is False + assert handoff_closeout["stderr_included"] is False + assert proof["proof_status"] == "final_no_runner_execution_proof_preview_ready" + assert proof["proof_mode"] == "final_no_runner_execution_proof_preview_only" + assert proof["final_no_runner_execution_proof_field_count"] == 12 + assert proof["execution_receipt_present"] is False + assert proof["execution_receipt_required"] is False + assert proof["dry_run_executor_invoked"] is False + assert proof["runner_invocation_performed"] is False + assert proof["endpoint_executed"] is False + assert proof["sql_executed"] is False + assert proof["database_written"] is False + assert proof["dry_run_executor_invocation_allowed"] is False + assert proof["runner_invocation_allowed"] is False + assert proof["ready_for_dry_run_executor_invocation_now"] is False + assert proof["ready_for_actual_dry_run_execution_now"] is False + assert proof["endpoint_execution_allowed"] is False + assert proof["sql_execution_allowed"] is False + assert proof["database_write_allowed"] is False + assert proof["database_apply_authorized"] is False + assert proof["executes_database_apply"] is False + assert proof["executes_endpoint"] is False + assert proof["executes_sql"] is False + assert proof["writes_database"] is False + assert proof["stdout_included"] is False + assert proof["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_final_no_runner_execution_proof" + ] + is False + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_dry_run_executor_invocation_now"] is False + assert contract["ready_for_actual_dry_run_execution_now"] is False + assert contract["writes_database"] is False + assert "runner_invocation_boundary_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "no_execution_receipt_handoff_ready" in check_keys + assert "no_execution_receipt_handoff_no_execute" in check_keys + assert "final_no_runner_execution_proof_bound" in check_keys + assert "final_no_runner_execution_proof_blocks_execution" in check_keys + assert "previous_closeouts_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bound" in check_keys + assert "runner_invocation_boundary_closeout_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_no_execution_receipt_handoff_closeout_ready_after_fake_fetch_but_proof_is_no_runner_execution(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_no_execution_receipt_handoff_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_final_no_runner_execution_proof" + ] + handoff_closeout = closeout[ + "controlled_dry_run_no_execution_receipt_handoff_closeout" + ] + proof = handoff_closeout["final_no_runner_execution_proof"] + handoff = handoff_closeout["no_execution_receipt_handoff"] + boundary_closeout = handoff_closeout["runner_invocation_boundary_closeout"] + contract = closeout[ + "controlled_dry_run_no_execution_receipt_handoff_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_no_execution_receipt_handoff_closeout_checks" + ] + ] + assert closeout["result"] == ( + "DB_APPLY_CONTROLLED_DRY_RUN_NO_EXECUTION_RECEIPT_HANDOFF_CLOSEOUT_READY" + ) + assert closeout["summary"]["controlled_dry_run_no_execution_receipt_handoff_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_no_execution_receipt_handoff_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_no_execution_receipt_handoff_closeout_pass_count"] == 12 + assert closeout["summary"]["controlled_dry_run_no_execution_receipt_handoff_closeout_waiting_count"] == 0 + assert closeout["summary"]["controlled_dry_run_runner_invocation_boundary_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_execution_preflight_guard_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_no_write_invocation_package_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_invocation_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_apply_executor_readiness_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_pre_apply_replay_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_final_executor_guard_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_no_apply_enforcement_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_post_receipt_parser_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_execution_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_command_artifact_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_execution_plan_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_readiness_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_package_ready_count"] == 1 + assert closeout["summary"]["controlled_apply_final_preflight_ready_count"] == 1 + assert closeout["summary"]["final_no_runner_execution_proof_count"] == 1 + assert closeout["summary"]["final_no_runner_execution_proof_field_count"] == 12 + assert closeout["summary"]["dry_run_executor_invoked_count"] == 0 + assert closeout["summary"]["runner_invocation_performed_count"] == 0 + assert closeout["summary"]["endpoint_executed_count"] == 0 + assert closeout["summary"]["sql_executed_count"] == 0 + assert closeout["summary"]["database_written_count"] == 0 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_final_no_runner_execution_proof" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_final_no_runner_execution_proof_closeout" + ] + is True + ) + assert future["no_execution_receipt_handoff_closeout_ready"] is True + assert future["final_no_runner_execution_proof_bound"] is True + assert future["dry_run_executor_invoked"] is False + assert future["runner_invocation_performed"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["execution_receipt_present"] is False + assert future["dry_run_executor_invocation_allowed"] is False + assert future["runner_invocation_allowed"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["ready_for_actual_dry_run_execution_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert future["stdout_included"] is False + assert future["stderr_included"] is False + assert ( + handoff_closeout[ + "ready_for_future_database_apply_controlled_dry_run_no_execution_receipt_handoff_closeout" + ] + is True + ) + assert handoff_closeout["no_execution_receipt_handoff_closeout_field_count"] == 12 + assert handoff_closeout["no_execution_receipt_handoff_closeout_acceptance_gate_count"] == 10 + assert handoff_closeout["final_no_runner_execution_proof_count"] == 1 + assert handoff_closeout["final_no_runner_execution_proof_field_count"] == 12 + assert handoff_closeout["dry_run_executor_invoked"] is False + assert handoff_closeout["runner_invocation_performed"] is False + assert handoff_closeout["endpoint_executed"] is False + assert handoff_closeout["sql_executed"] is False + assert handoff_closeout["database_written"] is False + assert handoff_closeout["dry_run_executor_invocation_allowed"] is False + assert handoff_closeout["runner_invocation_allowed"] is False + assert handoff_closeout["ready_for_dry_run_executor_invocation_now"] is False + assert handoff_closeout["endpoint_execution_allowed"] is False + assert handoff_closeout["sql_execution_allowed"] is False + assert handoff_closeout["database_write_allowed"] is False + assert handoff_closeout["database_apply_authorized"] is False + assert handoff_closeout["executes_database_apply"] is False + assert handoff_closeout["executes_endpoint_in_preview"] is False + assert handoff_closeout["executes_sql_in_preview"] is False + assert handoff_closeout["writes_database_in_preview"] is False + assert handoff_closeout["stdout_included"] is False + assert handoff_closeout["stderr_included"] is False + assert proof["proof_id"] == future["final_no_runner_execution_proof_id"] + assert proof["source_no_execution_receipt_handoff_closeout_id"] == future["no_execution_receipt_handoff_closeout_id"] + assert proof["source_no_execution_receipt_handoff_id"] == handoff["handoff_id"] + assert proof["source_runner_invocation_boundary_closeout_id"] == boundary_closeout["runner_invocation_boundary_closeout_id"] + assert proof["required_command_shape_hash"] == handoff["required_command_shape_hash"] + assert proof["proof_status"] == "final_no_runner_execution_proof_preview_ready" + assert proof["proof_mode"] == "final_no_runner_execution_proof_preview_only" + assert proof["execution_receipt_present"] is False + assert proof["execution_receipt_required"] is False + assert proof["dry_run_executor_invoked"] is False + assert proof["runner_invocation_performed"] is False + assert proof["endpoint_executed"] is False + assert proof["sql_executed"] is False + assert proof["database_written"] is False + assert proof["dry_run_executor_invocation_allowed"] is False + assert proof["runner_invocation_allowed"] is False + assert proof["ready_for_final_no_runner_execution_proof_now"] is False + assert proof["ready_for_dry_run_executor_invocation_now"] is False + assert proof["ready_for_actual_dry_run_execution_now"] is False + assert proof["endpoint_execution_allowed"] is False + assert proof["sql_execution_allowed"] is False + assert proof["database_write_allowed"] is False + assert proof["database_apply_authorized"] is False + assert proof["executes_database_apply"] is False + assert proof["executes_endpoint"] is False + assert proof["executes_sql"] is False + assert proof["writes_database"] is False + assert proof["captures_stdout"] is False + assert proof["captures_stderr"] is False + assert proof["stdout_included"] is False + assert proof["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_final_no_runner_execution_proof" + ] + is True + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_dry_run_executor_invocation_now"] is False + assert contract["ready_for_actual_dry_run_execution_now"] is False + assert contract["writes_database"] is False + assert "runner_invocation_boundary_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "no_execution_receipt_handoff_ready" in check_keys + assert "no_execution_receipt_handoff_no_execute" in check_keys + assert "final_no_runner_execution_proof_bound" in check_keys + assert "final_no_runner_execution_proof_blocks_execution" in check_keys + assert "previous_closeouts_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bound" in check_keys + assert "runner_invocation_boundary_closeout_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_final_no_runner_execution_proof_closeout_waits_without_ready_handoff_closeout(): + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_final_no_runner_execution_proof_closeout( + _payload(), + batch_size=1, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_controlled_executor_quarantine_proof" + ] + proof_closeout = closeout[ + "controlled_dry_run_final_no_runner_execution_proof_closeout" + ] + quarantine = proof_closeout["controlled_executor_quarantine_proof"] + contract = closeout[ + "controlled_dry_run_final_no_runner_execution_proof_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_final_no_runner_execution_proof_closeout_checks" + ] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_final_no_runner_execution_proof_closeout" + ) + assert closeout["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_NO_EXECUTION_RECEIPT_HANDOFF_CLOSEOUT" + ) + assert closeout["summary"]["controlled_dry_run_final_no_runner_execution_proof_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_final_no_runner_execution_proof_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_no_execution_receipt_handoff_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_final_no_runner_execution_proof_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_final_no_runner_execution_proof_closeout_field_count"] == 12 + assert closeout["summary"]["controlled_dry_run_final_no_runner_execution_proof_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["controlled_executor_quarantine_proof_count"] == 1 + assert closeout["summary"]["controlled_executor_quarantine_proof_field_count"] == 12 + assert closeout["summary"]["dry_run_executor_invoked_count"] == 0 + assert closeout["summary"]["runner_invocation_performed_count"] == 0 + assert closeout["summary"]["endpoint_executed_count"] == 0 + assert closeout["summary"]["sql_executed_count"] == 0 + assert closeout["summary"]["database_written_count"] == 0 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["final_no_runner_execution_proof_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-final-no-runner-execution-proof-closeout-" + ) + assert future["controlled_executor_quarantine_proof_id"].endswith( + "-controlled-executor-quarantine-proof" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_controlled_executor_quarantine_proof" + ] + is False + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_controlled_executor_quarantine_proof_closeout" + ] + is False + ) + assert future["final_no_runner_execution_proof_closeout_ready"] is False + assert future["controlled_executor_quarantine_proof_bound"] is False + assert future["controlled_executor_quarantine_bound"] is True + assert future["executor_quarantine_enforced"] is True + assert future["dry_run_executor_invoked"] is False + assert future["runner_invocation_performed"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["execution_receipt_present"] is False + assert future["dry_run_executor_invocation_allowed"] is False + assert future["runner_invocation_allowed"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["ready_for_actual_dry_run_execution_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert future["stdout_included"] is False + assert future["stderr_included"] is False + assert proof_closeout["authorization_material_type"] == ( + "controlled_dry_run_final_no_runner_execution_proof_closeout" + ) + assert proof_closeout["final_no_runner_execution_proof_closeout_only"] is True + assert proof_closeout["controlled_executor_quarantine_proof_only"] is True + assert proof_closeout["controlled_executor_quarantine_bound"] is True + assert proof_closeout["executor_quarantine_enforced"] is True + assert proof_closeout["dry_run_executor_invoked"] is False + assert proof_closeout["runner_invocation_performed"] is False + assert proof_closeout["endpoint_executed"] is False + assert proof_closeout["sql_executed"] is False + assert proof_closeout["database_written"] is False + assert proof_closeout["dry_run_executor_invocation_allowed"] is False + assert proof_closeout["runner_invocation_allowed"] is False + assert proof_closeout["ready_for_dry_run_executor_invocation_now"] is False + assert proof_closeout["endpoint_execution_allowed"] is False + assert proof_closeout["sql_execution_allowed"] is False + assert proof_closeout["database_write_allowed"] is False + assert proof_closeout["database_apply_authorized"] is False + assert proof_closeout["executes_database_apply"] is False + assert proof_closeout["executes_endpoint_in_preview"] is False + assert proof_closeout["executes_sql_in_preview"] is False + assert proof_closeout["writes_database_in_preview"] is False + assert proof_closeout["stdout_included"] is False + assert proof_closeout["stderr_included"] is False + assert quarantine["quarantine_status"] == "controlled_executor_quarantine_proof_preview_ready" + assert quarantine["quarantine_mode"] == "controlled_executor_quarantine_proof_preview_only" + assert quarantine["controlled_executor_quarantine_proof_field_count"] == 12 + assert quarantine["controlled_executor_quarantine_bound"] is True + assert quarantine["executor_quarantine_enforced"] is True + assert quarantine["dry_run_executor_invoked"] is False + assert quarantine["runner_invocation_performed"] is False + assert quarantine["endpoint_executed"] is False + assert quarantine["sql_executed"] is False + assert quarantine["database_written"] is False + assert quarantine["execution_receipt_present"] is False + assert quarantine["execution_receipt_required"] is False + assert quarantine["dry_run_executor_invocation_allowed"] is False + assert quarantine["runner_invocation_allowed"] is False + assert quarantine["ready_for_controlled_executor_quarantine_now"] is False + assert quarantine["ready_for_dry_run_executor_invocation_now"] is False + assert quarantine["ready_for_actual_dry_run_execution_now"] is False + assert quarantine["endpoint_execution_allowed"] is False + assert quarantine["sql_execution_allowed"] is False + assert quarantine["database_write_allowed"] is False + assert quarantine["database_apply_authorized"] is False + assert quarantine["executes_database_apply"] is False + assert quarantine["executes_endpoint"] is False + assert quarantine["executes_sql"] is False + assert quarantine["writes_database"] is False + assert quarantine["stdout_included"] is False + assert quarantine["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_controlled_executor_quarantine_proof" + ] + is False + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_dry_run_executor_invocation_now"] is False + assert contract["ready_for_actual_dry_run_execution_now"] is False + assert contract["writes_database"] is False + assert "no_execution_receipt_handoff_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "final_no_runner_execution_proof_ready" in check_keys + assert "final_no_runner_execution_proof_no_execute" in check_keys + assert "controlled_executor_quarantine_proof_bound" in check_keys + assert "controlled_executor_quarantine_proof_blocks_execution" in check_keys + assert "previous_closeouts_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bound" in check_keys + assert "no_execution_receipt_handoff_closeout_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_final_no_runner_execution_proof_closeout_ready_after_fake_fetch_but_quarantine_is_no_executor(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_final_no_runner_execution_proof_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_controlled_executor_quarantine_proof" + ] + proof_closeout = closeout[ + "controlled_dry_run_final_no_runner_execution_proof_closeout" + ] + quarantine = proof_closeout["controlled_executor_quarantine_proof"] + final_proof = proof_closeout["final_no_runner_execution_proof"] + handoff_closeout = proof_closeout["no_execution_receipt_handoff_closeout"] + contract = closeout[ + "controlled_dry_run_final_no_runner_execution_proof_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_final_no_runner_execution_proof_closeout_checks" + ] + ] + assert closeout["result"] == ( + "DB_APPLY_CONTROLLED_DRY_RUN_FINAL_NO_RUNNER_EXECUTION_PROOF_CLOSEOUT_READY" + ) + assert closeout["summary"]["controlled_dry_run_final_no_runner_execution_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_final_no_runner_execution_proof_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_final_no_runner_execution_proof_closeout_pass_count"] == 12 + assert closeout["summary"]["controlled_dry_run_final_no_runner_execution_proof_closeout_waiting_count"] == 0 + assert closeout["summary"]["controlled_dry_run_no_execution_receipt_handoff_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_invocation_boundary_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_execution_preflight_guard_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_no_write_invocation_package_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_invocation_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_apply_executor_readiness_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_pre_apply_replay_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_final_executor_guard_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_no_apply_enforcement_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_post_receipt_parser_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_execution_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_command_artifact_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_execution_plan_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_readiness_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_package_ready_count"] == 1 + assert closeout["summary"]["controlled_apply_final_preflight_ready_count"] == 1 + assert closeout["summary"]["controlled_executor_quarantine_proof_count"] == 1 + assert closeout["summary"]["controlled_executor_quarantine_proof_field_count"] == 12 + assert closeout["summary"]["dry_run_executor_invoked_count"] == 0 + assert closeout["summary"]["runner_invocation_performed_count"] == 0 + assert closeout["summary"]["endpoint_executed_count"] == 0 + assert closeout["summary"]["sql_executed_count"] == 0 + assert closeout["summary"]["database_written_count"] == 0 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_controlled_executor_quarantine_proof" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_controlled_executor_quarantine_proof_closeout" + ] + is True + ) + assert future["final_no_runner_execution_proof_closeout_ready"] is True + assert future["controlled_executor_quarantine_proof_bound"] is True + assert future["controlled_executor_quarantine_bound"] is True + assert future["executor_quarantine_enforced"] is True + assert future["dry_run_executor_invoked"] is False + assert future["runner_invocation_performed"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["execution_receipt_present"] is False + assert future["dry_run_executor_invocation_allowed"] is False + assert future["runner_invocation_allowed"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["ready_for_actual_dry_run_execution_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert future["stdout_included"] is False + assert future["stderr_included"] is False + assert ( + proof_closeout[ + "ready_for_future_database_apply_controlled_dry_run_final_no_runner_execution_proof_closeout" + ] + is True + ) + assert proof_closeout["final_no_runner_execution_proof_closeout_field_count"] == 12 + assert proof_closeout["final_no_runner_execution_proof_closeout_acceptance_gate_count"] == 10 + assert proof_closeout["controlled_executor_quarantine_proof_count"] == 1 + assert proof_closeout["controlled_executor_quarantine_proof_field_count"] == 12 + assert proof_closeout["controlled_executor_quarantine_bound"] is True + assert proof_closeout["executor_quarantine_enforced"] is True + assert proof_closeout["dry_run_executor_invoked"] is False + assert proof_closeout["runner_invocation_performed"] is False + assert proof_closeout["endpoint_executed"] is False + assert proof_closeout["sql_executed"] is False + assert proof_closeout["database_written"] is False + assert proof_closeout["dry_run_executor_invocation_allowed"] is False + assert proof_closeout["runner_invocation_allowed"] is False + assert proof_closeout["ready_for_dry_run_executor_invocation_now"] is False + assert proof_closeout["endpoint_execution_allowed"] is False + assert proof_closeout["sql_execution_allowed"] is False + assert proof_closeout["database_write_allowed"] is False + assert proof_closeout["database_apply_authorized"] is False + assert proof_closeout["executes_database_apply"] is False + assert proof_closeout["executes_endpoint_in_preview"] is False + assert proof_closeout["executes_sql_in_preview"] is False + assert proof_closeout["writes_database_in_preview"] is False + assert proof_closeout["stdout_included"] is False + assert proof_closeout["stderr_included"] is False + assert quarantine["quarantine_proof_id"] == future["controlled_executor_quarantine_proof_id"] + assert quarantine["source_final_no_runner_execution_proof_closeout_id"] == future["final_no_runner_execution_proof_closeout_id"] + assert quarantine["source_final_no_runner_execution_proof_id"] == final_proof["proof_id"] + assert quarantine["source_no_execution_receipt_handoff_closeout_id"] == handoff_closeout["no_execution_receipt_handoff_closeout_id"] + assert quarantine["required_command_shape_hash"] == final_proof["required_command_shape_hash"] + assert quarantine["quarantine_status"] == "controlled_executor_quarantine_proof_preview_ready" + assert quarantine["quarantine_mode"] == "controlled_executor_quarantine_proof_preview_only" + assert quarantine["controlled_executor_quarantine_bound"] is True + assert quarantine["executor_quarantine_enforced"] is True + assert quarantine["dry_run_executor_invoked"] is False + assert quarantine["runner_invocation_performed"] is False + assert quarantine["endpoint_executed"] is False + assert quarantine["sql_executed"] is False + assert quarantine["database_written"] is False + assert quarantine["dry_run_executor_invocation_allowed"] is False + assert quarantine["runner_invocation_allowed"] is False + assert quarantine["ready_for_controlled_executor_quarantine_now"] is False + assert quarantine["ready_for_dry_run_executor_invocation_now"] is False + assert quarantine["ready_for_actual_dry_run_execution_now"] is False + assert quarantine["endpoint_execution_allowed"] is False + assert quarantine["sql_execution_allowed"] is False + assert quarantine["database_write_allowed"] is False + assert quarantine["database_apply_authorized"] is False + assert quarantine["executes_database_apply"] is False + assert quarantine["executes_endpoint"] is False + assert quarantine["executes_sql"] is False + assert quarantine["writes_database"] is False + assert quarantine["captures_stdout"] is False + assert quarantine["captures_stderr"] is False + assert quarantine["stdout_included"] is False + assert quarantine["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_controlled_executor_quarantine_proof" + ] + is True + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_dry_run_executor_invocation_now"] is False + assert contract["ready_for_actual_dry_run_execution_now"] is False + assert contract["writes_database"] is False + assert "no_execution_receipt_handoff_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "final_no_runner_execution_proof_ready" in check_keys + assert "final_no_runner_execution_proof_no_execute" in check_keys + assert "controlled_executor_quarantine_proof_bound" in check_keys + assert "controlled_executor_quarantine_proof_blocks_execution" in check_keys + assert "previous_closeouts_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bound" in check_keys + assert "no_execution_receipt_handoff_closeout_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_controlled_executor_quarantine_proof_closeout_waits_without_ready_final_proof_closeout(): + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_controlled_executor_quarantine_proof_closeout( + _payload(), + batch_size=1, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_execution_envelope_freeze_proof" + ] + quarantine_closeout = closeout[ + "controlled_dry_run_controlled_executor_quarantine_proof_closeout" + ] + freeze = quarantine_closeout["dry_run_execution_envelope_freeze_proof"] + contract = closeout[ + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_checks" + ] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_controlled_executor_quarantine_proof_closeout" + ) + assert closeout["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_FINAL_NO_RUNNER_EXECUTION_PROOF_CLOSEOUT" + ) + assert closeout["summary"]["controlled_dry_run_controlled_executor_quarantine_proof_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_controlled_executor_quarantine_proof_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_final_no_runner_execution_proof_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_controlled_executor_quarantine_proof_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_controlled_executor_quarantine_proof_closeout_field_count"] == 12 + assert closeout["summary"]["controlled_dry_run_controlled_executor_quarantine_proof_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["dry_run_execution_envelope_freeze_proof_count"] == 1 + assert closeout["summary"]["dry_run_execution_envelope_freeze_proof_field_count"] == 12 + assert closeout["summary"]["dry_run_executor_invoked_count"] == 0 + assert closeout["summary"]["runner_invocation_performed_count"] == 0 + assert closeout["summary"]["endpoint_executed_count"] == 0 + assert closeout["summary"]["sql_executed_count"] == 0 + assert closeout["summary"]["database_written_count"] == 0 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["controlled_executor_quarantine_proof_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-controlled-executor-quarantine-proof-closeout-" + ) + assert future["dry_run_execution_envelope_freeze_proof_id"].endswith( + "-dry-run-execution-envelope-freeze-proof" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_execution_envelope_freeze_proof" + ] + is False + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_execution_envelope_freeze_proof_closeout" + ] + is False + ) + assert future["controlled_executor_quarantine_proof_closeout_ready"] is False + assert future["dry_run_execution_envelope_freeze_proof_bound"] is False + assert future["controlled_executor_quarantine_bound"] is True + assert future["executor_quarantine_enforced"] is True + assert future["execution_envelope_frozen"] is True + assert future["execution_envelope_mutation_allowed"] is False + assert future["dry_run_executor_invoked"] is False + assert future["runner_invocation_performed"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["execution_receipt_present"] is False + assert future["dry_run_executor_invocation_allowed"] is False + assert future["runner_invocation_allowed"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["ready_for_actual_dry_run_execution_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert future["stdout_included"] is False + assert future["stderr_included"] is False + assert quarantine_closeout["authorization_material_type"] == ( + "controlled_dry_run_controlled_executor_quarantine_proof_closeout" + ) + assert quarantine_closeout["controlled_executor_quarantine_proof_closeout_only"] is True + assert quarantine_closeout["dry_run_execution_envelope_freeze_proof_only"] is True + assert quarantine_closeout["execution_envelope_frozen"] is True + assert quarantine_closeout["execution_envelope_mutation_allowed"] is False + assert quarantine_closeout["dry_run_executor_invoked"] is False + assert quarantine_closeout["runner_invocation_performed"] is False + assert quarantine_closeout["endpoint_executed"] is False + assert quarantine_closeout["sql_executed"] is False + assert quarantine_closeout["database_written"] is False + assert quarantine_closeout["dry_run_executor_invocation_allowed"] is False + assert quarantine_closeout["runner_invocation_allowed"] is False + assert quarantine_closeout["ready_for_dry_run_executor_invocation_now"] is False + assert quarantine_closeout["endpoint_execution_allowed"] is False + assert quarantine_closeout["sql_execution_allowed"] is False + assert quarantine_closeout["database_write_allowed"] is False + assert quarantine_closeout["database_apply_authorized"] is False + assert quarantine_closeout["executes_database_apply"] is False + assert quarantine_closeout["executes_endpoint_in_preview"] is False + assert quarantine_closeout["executes_sql_in_preview"] is False + assert quarantine_closeout["writes_database_in_preview"] is False + assert quarantine_closeout["stdout_included"] is False + assert quarantine_closeout["stderr_included"] is False + assert freeze["freeze_status"] == "dry_run_execution_envelope_freeze_proof_preview_ready" + assert freeze["freeze_mode"] == "dry_run_execution_envelope_freeze_proof_preview_only" + assert freeze["dry_run_execution_envelope_freeze_proof_field_count"] == 12 + assert freeze["execution_envelope_frozen"] is True + assert freeze["execution_envelope_mutation_allowed"] is False + assert freeze["dry_run_executor_invoked"] is False + assert freeze["runner_invocation_performed"] is False + assert freeze["endpoint_executed"] is False + assert freeze["sql_executed"] is False + assert freeze["database_written"] is False + assert freeze["execution_receipt_present"] is False + assert freeze["execution_receipt_required"] is False + assert freeze["dry_run_executor_invocation_allowed"] is False + assert freeze["runner_invocation_allowed"] is False + assert freeze["ready_for_dry_run_executor_invocation_now"] is False + assert freeze["ready_for_actual_dry_run_execution_now"] is False + assert freeze["endpoint_execution_allowed"] is False + assert freeze["sql_execution_allowed"] is False + assert freeze["database_write_allowed"] is False + assert freeze["database_apply_authorized"] is False + assert freeze["executes_database_apply"] is False + assert freeze["executes_endpoint"] is False + assert freeze["executes_sql"] is False + assert freeze["writes_database"] is False + assert freeze["stdout_included"] is False + assert freeze["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_execution_envelope_freeze_proof" + ] + is False + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_dry_run_executor_invocation_now"] is False + assert contract["ready_for_actual_dry_run_execution_now"] is False + assert contract["writes_database"] is False + assert "final_no_runner_execution_proof_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "controlled_executor_quarantine_proof_ready" in check_keys + assert "controlled_executor_quarantine_proof_no_execute" in check_keys + assert "dry_run_execution_envelope_freeze_proof_bound" in check_keys + assert "dry_run_execution_envelope_freeze_proof_blocks_execution" in check_keys + assert "previous_closeouts_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bound" in check_keys + assert "final_no_runner_execution_proof_closeout_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_controlled_executor_quarantine_proof_closeout_ready_after_fake_fetch_but_envelope_is_frozen(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_controlled_executor_quarantine_proof_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_execution_envelope_freeze_proof" + ] + quarantine_closeout = closeout[ + "controlled_dry_run_controlled_executor_quarantine_proof_closeout" + ] + freeze = quarantine_closeout["dry_run_execution_envelope_freeze_proof"] + quarantine = quarantine_closeout["controlled_executor_quarantine_proof"] + source_closeout = quarantine_closeout["final_no_runner_execution_proof_closeout"] + final_proof = quarantine_closeout["final_no_runner_execution_proof"] + contract = closeout[ + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_checks" + ] + ] + assert closeout["result"] == ( + "DB_APPLY_CONTROLLED_DRY_RUN_CONTROLLED_EXECUTOR_QUARANTINE_PROOF_CLOSEOUT_READY" + ) + assert closeout["summary"]["controlled_dry_run_controlled_executor_quarantine_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_controlled_executor_quarantine_proof_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_controlled_executor_quarantine_proof_closeout_pass_count"] == 12 + assert closeout["summary"]["controlled_dry_run_controlled_executor_quarantine_proof_closeout_waiting_count"] == 0 + assert closeout["summary"]["controlled_dry_run_final_no_runner_execution_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_no_execution_receipt_handoff_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_invocation_boundary_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_execution_preflight_guard_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_no_write_invocation_package_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_invocation_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_apply_executor_readiness_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_pre_apply_replay_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_final_executor_guard_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_no_apply_enforcement_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_post_receipt_parser_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_execution_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_command_artifact_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_execution_plan_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_runner_readiness_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_receipt_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_package_ready_count"] == 1 + assert closeout["summary"]["controlled_apply_final_preflight_ready_count"] == 1 + assert closeout["summary"]["dry_run_execution_envelope_freeze_proof_count"] == 1 + assert closeout["summary"]["dry_run_execution_envelope_freeze_proof_field_count"] == 12 + assert closeout["summary"]["dry_run_executor_invoked_count"] == 0 + assert closeout["summary"]["runner_invocation_performed_count"] == 0 + assert closeout["summary"]["endpoint_executed_count"] == 0 + assert closeout["summary"]["sql_executed_count"] == 0 + assert closeout["summary"]["database_written_count"] == 0 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_execution_envelope_freeze_proof" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_execution_envelope_freeze_proof_closeout" + ] + is True + ) + assert future["controlled_executor_quarantine_proof_closeout_ready"] is True + assert future["dry_run_execution_envelope_freeze_proof_bound"] is True + assert future["controlled_executor_quarantine_bound"] is True + assert future["executor_quarantine_enforced"] is True + assert future["execution_envelope_frozen"] is True + assert future["execution_envelope_mutation_allowed"] is False + assert future["dry_run_executor_invoked"] is False + assert future["runner_invocation_performed"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["execution_receipt_present"] is False + assert future["dry_run_executor_invocation_allowed"] is False + assert future["runner_invocation_allowed"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["ready_for_actual_dry_run_execution_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert future["stdout_included"] is False + assert future["stderr_included"] is False + assert ( + quarantine_closeout[ + "ready_for_future_database_apply_controlled_dry_run_controlled_executor_quarantine_proof_closeout" + ] + is True + ) + assert quarantine_closeout["controlled_executor_quarantine_proof_closeout_field_count"] == 12 + assert quarantine_closeout["controlled_executor_quarantine_proof_closeout_acceptance_gate_count"] == 10 + assert quarantine_closeout["dry_run_execution_envelope_freeze_proof_count"] == 1 + assert quarantine_closeout["dry_run_execution_envelope_freeze_proof_field_count"] == 12 + assert quarantine_closeout["controlled_executor_quarantine_proof_closeout_only"] is True + assert quarantine_closeout["dry_run_execution_envelope_freeze_proof_only"] is True + assert quarantine_closeout["execution_envelope_frozen"] is True + assert quarantine_closeout["execution_envelope_mutation_allowed"] is False + assert quarantine_closeout["dry_run_executor_invoked"] is False + assert quarantine_closeout["runner_invocation_performed"] is False + assert quarantine_closeout["endpoint_executed"] is False + assert quarantine_closeout["sql_executed"] is False + assert quarantine_closeout["database_written"] is False + assert quarantine_closeout["dry_run_executor_invocation_allowed"] is False + assert quarantine_closeout["runner_invocation_allowed"] is False + assert quarantine_closeout["ready_for_dry_run_executor_invocation_now"] is False + assert quarantine_closeout["endpoint_execution_allowed"] is False + assert quarantine_closeout["sql_execution_allowed"] is False + assert quarantine_closeout["database_write_allowed"] is False + assert quarantine_closeout["database_apply_authorized"] is False + assert quarantine_closeout["executes_database_apply"] is False + assert quarantine_closeout["executes_endpoint_in_preview"] is False + assert quarantine_closeout["executes_sql_in_preview"] is False + assert quarantine_closeout["writes_database_in_preview"] is False + assert quarantine_closeout["stdout_included"] is False + assert quarantine_closeout["stderr_included"] is False + assert freeze["freeze_proof_id"] == future["dry_run_execution_envelope_freeze_proof_id"] + assert freeze["source_controlled_executor_quarantine_proof_closeout_id"] == future["controlled_executor_quarantine_proof_closeout_id"] + assert freeze["source_controlled_executor_quarantine_proof_id"] == quarantine["quarantine_proof_id"] + assert freeze["source_final_no_runner_execution_proof_closeout_id"] == source_closeout["final_no_runner_execution_proof_closeout_id"] + assert freeze["source_final_no_runner_execution_proof_id"] == final_proof["proof_id"] + assert freeze["required_command_shape_hash"] == quarantine["required_command_shape_hash"] + assert freeze["freeze_status"] == "dry_run_execution_envelope_freeze_proof_preview_ready" + assert freeze["freeze_mode"] == "dry_run_execution_envelope_freeze_proof_preview_only" + assert freeze["execution_envelope_frozen"] is True + assert freeze["execution_envelope_mutation_allowed"] is False + assert freeze["dry_run_executor_invoked"] is False + assert freeze["runner_invocation_performed"] is False + assert freeze["endpoint_executed"] is False + assert freeze["sql_executed"] is False + assert freeze["database_written"] is False + assert freeze["execution_receipt_present"] is False + assert freeze["dry_run_executor_invocation_allowed"] is False + assert freeze["runner_invocation_allowed"] is False + assert freeze["ready_for_dry_run_executor_invocation_now"] is False + assert freeze["ready_for_actual_dry_run_execution_now"] is False + assert freeze["endpoint_execution_allowed"] is False + assert freeze["sql_execution_allowed"] is False + assert freeze["database_write_allowed"] is False + assert freeze["database_apply_authorized"] is False + assert freeze["executes_database_apply"] is False + assert freeze["executes_endpoint"] is False + assert freeze["executes_sql"] is False + assert freeze["writes_database"] is False + assert freeze["captures_stdout"] is False + assert freeze["captures_stderr"] is False + assert freeze["stdout_included"] is False + assert freeze["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_execution_envelope_freeze_proof" + ] + is True + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_dry_run_executor_invocation_now"] is False + assert contract["ready_for_actual_dry_run_execution_now"] is False + assert contract["writes_database"] is False + assert "final_no_runner_execution_proof_closeout_ready" in check_keys + assert "controlled_executor_quarantine_proof_ready" in check_keys + assert "dry_run_execution_envelope_freeze_proof_bound" in check_keys + assert "dry_run_execution_envelope_freeze_proof_blocks_execution" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_execution_envelope_freeze_proof_closeout_waits_without_ready_quarantine_closeout(): + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_execution_envelope_freeze_proof_closeout( + _payload(), + batch_size=1, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_frozen_envelope_verifier_handoff" + ] + freeze_closeout = closeout[ + "controlled_dry_run_execution_envelope_freeze_proof_closeout" + ] + handoff = freeze_closeout["frozen_envelope_verifier_handoff"] + contract = closeout[ + "controlled_dry_run_execution_envelope_freeze_proof_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_execution_envelope_freeze_proof_closeout_checks" + ] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_execution_envelope_freeze_proof_closeout" + ) + assert closeout["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_CONTROLLED_EXECUTOR_QUARANTINE_PROOF_CLOSEOUT" + ) + assert closeout["summary"]["controlled_dry_run_execution_envelope_freeze_proof_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_execution_envelope_freeze_proof_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_controlled_executor_quarantine_proof_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_execution_envelope_freeze_proof_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_execution_envelope_freeze_proof_closeout_field_count"] == 12 + assert closeout["summary"]["controlled_dry_run_execution_envelope_freeze_proof_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["frozen_envelope_verifier_handoff_count"] == 1 + assert closeout["summary"]["frozen_envelope_verifier_handoff_field_count"] == 12 + assert closeout["summary"]["verifier_invoked_count"] == 0 + assert closeout["summary"]["verifier_receipt_present_count"] == 0 + assert closeout["summary"]["dry_run_executor_invoked_count"] == 0 + assert closeout["summary"]["runner_invocation_performed_count"] == 0 + assert closeout["summary"]["endpoint_executed_count"] == 0 + assert closeout["summary"]["sql_executed_count"] == 0 + assert closeout["summary"]["database_written_count"] == 0 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["execution_envelope_freeze_proof_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-execution-envelope-freeze-proof-closeout-" + ) + assert future["frozen_envelope_verifier_handoff_id"].endswith( + "-frozen-envelope-verifier-handoff" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_frozen_envelope_verifier_handoff" + ] + is False + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_frozen_envelope_verifier_handoff_closeout" + ] + is False + ) + assert future["execution_envelope_freeze_proof_closeout_ready"] is False + assert future["frozen_envelope_verifier_handoff_bound"] is False + assert future["execution_envelope_frozen"] is True + assert future["execution_envelope_mutation_allowed"] is False + assert future["verifier_invocation_allowed"] is False + assert future["verifier_invoked"] is False + assert future["verifier_receipt_present"] is False + assert future["dry_run_executor_invoked"] is False + assert future["runner_invocation_performed"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["dry_run_executor_invocation_allowed"] is False + assert future["runner_invocation_allowed"] is False + assert future["ready_for_database_apply_now"] is False + assert future["ready_for_verifier_invocation_now"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert future["stdout_included"] is False + assert future["stderr_included"] is False + assert freeze_closeout["authorization_material_type"] == ( + "controlled_dry_run_execution_envelope_freeze_proof_closeout" + ) + assert freeze_closeout["execution_envelope_freeze_proof_closeout_only"] is True + assert freeze_closeout["frozen_envelope_verifier_handoff_only"] is True + assert freeze_closeout["execution_envelope_frozen"] is True + assert freeze_closeout["execution_envelope_mutation_allowed"] is False + assert freeze_closeout["verifier_invocation_allowed"] is False + assert freeze_closeout["verifier_invoked"] is False + assert freeze_closeout["verifier_receipt_present"] is False + assert freeze_closeout["dry_run_executor_invoked"] is False + assert freeze_closeout["runner_invocation_performed"] is False + assert freeze_closeout["endpoint_executed"] is False + assert freeze_closeout["sql_executed"] is False + assert freeze_closeout["database_written"] is False + assert freeze_closeout["ready_for_verifier_invocation_now"] is False + assert freeze_closeout["ready_for_dry_run_executor_invocation_now"] is False + assert freeze_closeout["endpoint_execution_allowed"] is False + assert freeze_closeout["sql_execution_allowed"] is False + assert freeze_closeout["database_write_allowed"] is False + assert freeze_closeout["database_apply_authorized"] is False + assert freeze_closeout["executes_database_apply"] is False + assert freeze_closeout["executes_endpoint_in_preview"] is False + assert freeze_closeout["executes_sql_in_preview"] is False + assert freeze_closeout["writes_database_in_preview"] is False + assert handoff["handoff_status"] == "frozen_envelope_verifier_handoff_preview_ready" + assert handoff["verifier_handoff_mode"] == "frozen_envelope_verifier_handoff_preview_only" + assert handoff["frozen_envelope_verifier_handoff_field_count"] == 12 + assert handoff["execution_envelope_frozen"] is True + assert handoff["execution_envelope_mutation_allowed"] is False + assert handoff["verifier_handoff_bound"] is True + assert handoff["verifier_invocation_allowed"] is False + assert handoff["verifier_invoked"] is False + assert handoff["verifier_receipt_present"] is False + assert handoff["dry_run_executor_invoked"] is False + assert handoff["runner_invocation_performed"] is False + assert handoff["endpoint_executed"] is False + assert handoff["sql_executed"] is False + assert handoff["database_written"] is False + assert handoff["ready_for_verifier_invocation_now"] is False + assert handoff["endpoint_execution_allowed"] is False + assert handoff["sql_execution_allowed"] is False + assert handoff["database_write_allowed"] is False + assert handoff["database_apply_authorized"] is False + assert handoff["executes_database_apply"] is False + assert handoff["executes_endpoint"] is False + assert handoff["executes_sql"] is False + assert handoff["writes_database"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_frozen_envelope_verifier_handoff" + ] + is False + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_verifier_invocation_now"] is False + assert contract["ready_for_dry_run_executor_invocation_now"] is False + assert "controlled_executor_quarantine_proof_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "dry_run_execution_envelope_freeze_proof_ready" in check_keys + assert "dry_run_execution_envelope_freeze_proof_no_execute" in check_keys + assert "frozen_envelope_verifier_handoff_bound" in check_keys + assert "frozen_envelope_verifier_handoff_blocks_execution" in check_keys + assert "previous_closeouts_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bound" in check_keys + assert "controlled_executor_quarantine_proof_closeout_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_execution_envelope_freeze_proof_closeout_ready_after_fake_fetch_but_verifier_is_handoff_only(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_execution_envelope_freeze_proof_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_frozen_envelope_verifier_handoff" + ] + freeze_closeout = closeout[ + "controlled_dry_run_execution_envelope_freeze_proof_closeout" + ] + handoff = freeze_closeout["frozen_envelope_verifier_handoff"] + freeze = freeze_closeout["dry_run_execution_envelope_freeze_proof"] + source_closeout = freeze_closeout["controlled_executor_quarantine_proof_closeout"] + contract = closeout[ + "controlled_dry_run_execution_envelope_freeze_proof_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_execution_envelope_freeze_proof_closeout_checks" + ] + ] + assert closeout["result"] == ( + "DB_APPLY_CONTROLLED_DRY_RUN_EXECUTION_ENVELOPE_FREEZE_PROOF_CLOSEOUT_READY" + ) + assert closeout["summary"]["controlled_dry_run_execution_envelope_freeze_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_execution_envelope_freeze_proof_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_execution_envelope_freeze_proof_closeout_pass_count"] == 12 + assert closeout["summary"]["controlled_dry_run_execution_envelope_freeze_proof_closeout_waiting_count"] == 0 + assert closeout["summary"]["controlled_dry_run_controlled_executor_quarantine_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_final_no_runner_execution_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_no_execution_receipt_handoff_closeout_ready_count"] == 1 + assert closeout["summary"]["frozen_envelope_verifier_handoff_count"] == 1 + assert closeout["summary"]["frozen_envelope_verifier_handoff_field_count"] == 12 + assert closeout["summary"]["verifier_invoked_count"] == 0 + assert closeout["summary"]["verifier_receipt_present_count"] == 0 + assert closeout["summary"]["dry_run_executor_invoked_count"] == 0 + assert closeout["summary"]["runner_invocation_performed_count"] == 0 + assert closeout["summary"]["endpoint_executed_count"] == 0 + assert closeout["summary"]["sql_executed_count"] == 0 + assert closeout["summary"]["database_written_count"] == 0 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_frozen_envelope_verifier_handoff" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_frozen_envelope_verifier_handoff_closeout" + ] + is True + ) + assert future["execution_envelope_freeze_proof_closeout_ready"] is True + assert future["frozen_envelope_verifier_handoff_bound"] is True + assert future["execution_envelope_frozen"] is True + assert future["execution_envelope_mutation_allowed"] is False + assert future["verifier_invocation_allowed"] is False + assert future["verifier_invoked"] is False + assert future["verifier_receipt_present"] is False + assert future["dry_run_executor_invoked"] is False + assert future["runner_invocation_performed"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["ready_for_database_apply_now"] is False + assert future["ready_for_verifier_invocation_now"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert freeze_closeout["ready_for_future_database_apply_controlled_dry_run_execution_envelope_freeze_proof_closeout"] is True + assert freeze_closeout["execution_envelope_freeze_proof_closeout_field_count"] == 12 + assert freeze_closeout["execution_envelope_freeze_proof_closeout_acceptance_gate_count"] == 10 + assert freeze_closeout["frozen_envelope_verifier_handoff_count"] == 1 + assert freeze_closeout["frozen_envelope_verifier_handoff_field_count"] == 12 + assert freeze_closeout["execution_envelope_freeze_proof_closeout_only"] is True + assert freeze_closeout["frozen_envelope_verifier_handoff_only"] is True + assert freeze_closeout["execution_envelope_frozen"] is True + assert freeze_closeout["execution_envelope_mutation_allowed"] is False + assert freeze_closeout["verifier_invocation_allowed"] is False + assert freeze_closeout["verifier_invoked"] is False + assert freeze_closeout["verifier_receipt_present"] is False + assert freeze_closeout["dry_run_executor_invoked"] is False + assert freeze_closeout["runner_invocation_performed"] is False + assert freeze_closeout["endpoint_executed"] is False + assert freeze_closeout["sql_executed"] is False + assert freeze_closeout["database_written"] is False + assert freeze_closeout["ready_for_verifier_invocation_now"] is False + assert freeze_closeout["endpoint_execution_allowed"] is False + assert freeze_closeout["sql_execution_allowed"] is False + assert freeze_closeout["database_write_allowed"] is False + assert freeze_closeout["database_apply_authorized"] is False + assert freeze_closeout["executes_database_apply"] is False + assert freeze_closeout["executes_endpoint_in_preview"] is False + assert freeze_closeout["executes_sql_in_preview"] is False + assert freeze_closeout["writes_database_in_preview"] is False + assert handoff["handoff_id"] == future["frozen_envelope_verifier_handoff_id"] + assert handoff["source_execution_envelope_freeze_proof_closeout_id"] == future["execution_envelope_freeze_proof_closeout_id"] + assert handoff["source_dry_run_execution_envelope_freeze_proof_id"] == freeze["freeze_proof_id"] + assert handoff["source_controlled_executor_quarantine_proof_closeout_id"] == source_closeout["controlled_executor_quarantine_proof_closeout_id"] + assert handoff["required_command_shape_hash"] == freeze["required_command_shape_hash"] + assert handoff["handoff_status"] == "frozen_envelope_verifier_handoff_preview_ready" + assert handoff["verifier_handoff_mode"] == "frozen_envelope_verifier_handoff_preview_only" + assert handoff["execution_envelope_frozen"] is True + assert handoff["execution_envelope_mutation_allowed"] is False + assert handoff["verifier_handoff_bound"] is True + assert handoff["verifier_invocation_allowed"] is False + assert handoff["verifier_invoked"] is False + assert handoff["verifier_receipt_present"] is False + assert handoff["dry_run_executor_invoked"] is False + assert handoff["runner_invocation_performed"] is False + assert handoff["endpoint_executed"] is False + assert handoff["sql_executed"] is False + assert handoff["database_written"] is False + assert handoff["ready_for_verifier_invocation_now"] is False + assert handoff["endpoint_execution_allowed"] is False + assert handoff["sql_execution_allowed"] is False + assert handoff["database_write_allowed"] is False + assert handoff["database_apply_authorized"] is False + assert handoff["executes_database_apply"] is False + assert handoff["executes_endpoint"] is False + assert handoff["executes_sql"] is False + assert handoff["writes_database"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_frozen_envelope_verifier_handoff" + ] + is True + ) + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_verifier_invocation_now"] is False + assert contract["ready_for_dry_run_executor_invocation_now"] is False + assert "controlled_executor_quarantine_proof_closeout_ready" in check_keys + assert "dry_run_execution_envelope_freeze_proof_ready" in check_keys + assert "frozen_envelope_verifier_handoff_bound" in check_keys + assert "frozen_envelope_verifier_handoff_blocks_execution" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_frozen_envelope_verifier_handoff_closeout_waits_without_ready_freeze_closeout(): + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_frozen_envelope_verifier_handoff_closeout( + _payload(), + batch_size=1, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_verifier_invocation_lock_proof" + ] + handoff_closeout = closeout[ + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout" + ] + lock = handoff_closeout["verifier_invocation_lock_proof"] + contract = closeout[ + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_checks" + ] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_frozen_envelope_verifier_handoff_closeout" + ) + assert closeout["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_EXECUTION_ENVELOPE_FREEZE_PROOF_CLOSEOUT" + ) + assert closeout["summary"]["controlled_dry_run_frozen_envelope_verifier_handoff_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_frozen_envelope_verifier_handoff_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_execution_envelope_freeze_proof_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_frozen_envelope_verifier_handoff_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_frozen_envelope_verifier_handoff_closeout_field_count"] == 12 + assert closeout["summary"]["controlled_dry_run_frozen_envelope_verifier_handoff_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["verifier_invocation_lock_proof_count"] == 1 + assert closeout["summary"]["verifier_invocation_lock_proof_field_count"] == 12 + assert closeout["summary"]["verifier_invocation_locked_count"] == 1 + assert closeout["summary"]["verifier_invoked_count"] == 0 + assert closeout["summary"]["verifier_receipt_present_count"] == 0 + assert closeout["summary"]["dry_run_executor_invoked_count"] == 0 + assert closeout["summary"]["runner_invocation_performed_count"] == 0 + assert closeout["summary"]["endpoint_executed_count"] == 0 + assert closeout["summary"]["sql_executed_count"] == 0 + assert closeout["summary"]["database_written_count"] == 0 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["frozen_envelope_verifier_handoff_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-frozen-envelope-verifier-handoff-closeout-" + ) + assert future["verifier_invocation_lock_proof_id"].endswith( + "-verifier-invocation-lock-proof" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_verifier_invocation_lock_proof" + ] + is False + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_verifier_invocation_lock_proof_closeout" + ] + is False + ) + assert future["frozen_envelope_verifier_handoff_closeout_ready"] is False + assert future["execution_envelope_freeze_proof_closeout_ready"] is False + assert future["verifier_invocation_lock_proof_bound"] is False + assert future["verifier_invocation_locked"] is True + assert future["verifier_invocation_allowed"] is False + assert future["verifier_invoked"] is False + assert future["verifier_receipt_present"] is False + assert future["dry_run_executor_invoked"] is False + assert future["runner_invocation_performed"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["ready_for_database_apply_now"] is False + assert future["ready_for_verifier_invocation_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert future["stdout_included"] is False + assert future["stderr_included"] is False + assert handoff_closeout["authorization_material_type"] == ( + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout" + ) + assert handoff_closeout["frozen_envelope_verifier_handoff_closeout_only"] is True + assert handoff_closeout["verifier_invocation_lock_proof_only"] is True + assert handoff_closeout["verifier_invocation_locked"] is True + assert handoff_closeout["verifier_invocation_allowed"] is False + assert handoff_closeout["verifier_invoked"] is False + assert handoff_closeout["verifier_receipt_present"] is False + assert handoff_closeout["dry_run_executor_invoked"] is False + assert handoff_closeout["runner_invocation_performed"] is False + assert handoff_closeout["endpoint_executed"] is False + assert handoff_closeout["sql_executed"] is False + assert handoff_closeout["database_written"] is False + assert handoff_closeout["ready_for_verifier_invocation_now"] is False + assert handoff_closeout["endpoint_execution_allowed"] is False + assert handoff_closeout["sql_execution_allowed"] is False + assert handoff_closeout["database_write_allowed"] is False + assert handoff_closeout["database_apply_authorized"] is False + assert handoff_closeout["executes_database_apply"] is False + assert handoff_closeout["executes_endpoint_in_preview"] is False + assert handoff_closeout["executes_sql_in_preview"] is False + assert handoff_closeout["writes_database_in_preview"] is False + assert lock["lock_status"] == "verifier_invocation_lock_proof_preview_ready" + assert lock["lock_mode"] == "verifier_invocation_lock_proof_preview_only" + assert lock["verifier_invocation_lock_proof_field_count"] == 12 + assert lock["verifier_invocation_locked"] is True + assert lock["verifier_invocation_allowed"] is False + assert lock["verifier_invoked"] is False + assert lock["verifier_receipt_present"] is False + assert lock["dry_run_executor_invoked"] is False + assert lock["runner_invocation_performed"] is False + assert lock["endpoint_executed"] is False + assert lock["sql_executed"] is False + assert lock["database_written"] is False + assert lock["ready_for_verifier_invocation_now"] is False + assert lock["endpoint_execution_allowed"] is False + assert lock["sql_execution_allowed"] is False + assert lock["database_write_allowed"] is False + assert lock["database_apply_authorized"] is False + assert lock["executes_database_apply"] is False + assert lock["executes_endpoint"] is False + assert lock["executes_sql"] is False + assert lock["writes_database"] is False + assert lock["stdout_included"] is False + assert lock["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_verifier_invocation_lock_proof" + ] + is False + ) + assert contract["verifier_invocation_locked"] is True + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_verifier_invocation_now"] is False + assert "execution_envelope_freeze_proof_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "frozen_envelope_verifier_handoff_ready" in check_keys + assert "frozen_envelope_verifier_handoff_no_execute" in check_keys + assert "verifier_invocation_lock_proof_bound" in check_keys + assert "verifier_invocation_lock_proof_blocks_execution" in check_keys + assert "previous_closeouts_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bound" in check_keys + assert "execution_envelope_freeze_proof_closeout_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_frozen_envelope_verifier_handoff_closeout_ready_after_fake_fetch_but_verifier_invocation_is_locked(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_frozen_envelope_verifier_handoff_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_verifier_invocation_lock_proof" + ] + handoff_closeout = closeout[ + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout" + ] + lock = handoff_closeout["verifier_invocation_lock_proof"] + handoff = handoff_closeout["frozen_envelope_verifier_handoff"] + source_closeout = handoff_closeout["execution_envelope_freeze_proof_closeout"] + freeze = handoff_closeout["dry_run_execution_envelope_freeze_proof"] + contract = closeout[ + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_checks" + ] + ] + assert closeout["result"] == ( + "DB_APPLY_CONTROLLED_DRY_RUN_FROZEN_ENVELOPE_VERIFIER_HANDOFF_CLOSEOUT_READY" + ) + assert closeout["summary"]["controlled_dry_run_frozen_envelope_verifier_handoff_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_frozen_envelope_verifier_handoff_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_frozen_envelope_verifier_handoff_closeout_pass_count"] == 12 + assert closeout["summary"]["controlled_dry_run_frozen_envelope_verifier_handoff_closeout_waiting_count"] == 0 + assert closeout["summary"]["controlled_dry_run_execution_envelope_freeze_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_controlled_executor_quarantine_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_final_no_runner_execution_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["verifier_invocation_lock_proof_count"] == 1 + assert closeout["summary"]["verifier_invocation_lock_proof_field_count"] == 12 + assert closeout["summary"]["verifier_invocation_locked_count"] == 1 + assert closeout["summary"]["verifier_invoked_count"] == 0 + assert closeout["summary"]["verifier_receipt_present_count"] == 0 + assert closeout["summary"]["dry_run_executor_invoked_count"] == 0 + assert closeout["summary"]["runner_invocation_performed_count"] == 0 + assert closeout["summary"]["endpoint_executed_count"] == 0 + assert closeout["summary"]["sql_executed_count"] == 0 + assert closeout["summary"]["database_written_count"] == 0 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_verifier_invocation_lock_proof" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_verifier_invocation_lock_proof_closeout" + ] + is True + ) + assert future["frozen_envelope_verifier_handoff_closeout_ready"] is True + assert future["execution_envelope_freeze_proof_closeout_ready"] is True + assert future["frozen_envelope_verifier_handoff_ready"] is True + assert future["verifier_invocation_lock_proof_bound"] is True + assert future["verifier_invocation_locked"] is True + assert future["verifier_invocation_allowed"] is False + assert future["verifier_invoked"] is False + assert future["verifier_receipt_present"] is False + assert future["dry_run_executor_invoked"] is False + assert future["runner_invocation_performed"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["ready_for_database_apply_now"] is False + assert future["ready_for_verifier_invocation_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert handoff_closeout["ready_for_future_database_apply_controlled_dry_run_verifier_invocation_lock_proof"] is True + assert handoff_closeout["frozen_envelope_verifier_handoff_closeout_field_count"] == 12 + assert handoff_closeout["frozen_envelope_verifier_handoff_closeout_acceptance_gate_count"] == 10 + assert handoff_closeout["verifier_invocation_lock_proof_count"] == 1 + assert handoff_closeout["verifier_invocation_lock_proof_field_count"] == 12 + assert handoff_closeout["frozen_envelope_verifier_handoff_closeout_only"] is True + assert handoff_closeout["verifier_invocation_lock_proof_only"] is True + assert handoff_closeout["verifier_invocation_locked"] is True + assert handoff_closeout["verifier_invocation_allowed"] is False + assert handoff_closeout["verifier_invoked"] is False + assert handoff_closeout["verifier_receipt_present"] is False + assert handoff_closeout["dry_run_executor_invoked"] is False + assert handoff_closeout["runner_invocation_performed"] is False + assert handoff_closeout["endpoint_executed"] is False + assert handoff_closeout["sql_executed"] is False + assert handoff_closeout["database_written"] is False + assert handoff_closeout["ready_for_verifier_invocation_now"] is False + assert handoff_closeout["endpoint_execution_allowed"] is False + assert handoff_closeout["sql_execution_allowed"] is False + assert handoff_closeout["database_write_allowed"] is False + assert handoff_closeout["database_apply_authorized"] is False + assert handoff_closeout["executes_database_apply"] is False + assert handoff_closeout["executes_endpoint_in_preview"] is False + assert handoff_closeout["executes_sql_in_preview"] is False + assert handoff_closeout["writes_database_in_preview"] is False + assert lock["lock_proof_id"] == future["verifier_invocation_lock_proof_id"] + assert ( + lock["source_frozen_envelope_verifier_handoff_closeout_id"] + == future["frozen_envelope_verifier_handoff_closeout_id"] + ) + assert lock["source_frozen_envelope_verifier_handoff_id"] == handoff["handoff_id"] + assert ( + lock["source_execution_envelope_freeze_proof_closeout_id"] + == source_closeout["execution_envelope_freeze_proof_closeout_id"] + ) + assert lock["source_dry_run_execution_envelope_freeze_proof_id"] == freeze["freeze_proof_id"] + assert lock["required_command_shape_hash"] == freeze["required_command_shape_hash"] + assert lock["lock_status"] == "verifier_invocation_lock_proof_preview_ready" + assert lock["lock_mode"] == "verifier_invocation_lock_proof_preview_only" + assert lock["verifier_invocation_locked"] is True + assert lock["verifier_invocation_allowed"] is False + assert lock["verifier_invoked"] is False + assert lock["verifier_receipt_present"] is False + assert lock["verifier_receipt_required"] is False + assert lock["dry_run_executor_invoked"] is False + assert lock["runner_invocation_performed"] is False + assert lock["endpoint_executed"] is False + assert lock["sql_executed"] is False + assert lock["database_written"] is False + assert lock["ready_for_verifier_invocation_now"] is False + assert lock["endpoint_execution_allowed"] is False + assert lock["sql_execution_allowed"] is False + assert lock["database_write_allowed"] is False + assert lock["database_apply_authorized"] is False + assert lock["executes_database_apply"] is False + assert lock["executes_endpoint"] is False + assert lock["executes_sql"] is False + assert lock["writes_database"] is False + assert lock["stdout_included"] is False + assert lock["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_verifier_invocation_lock_proof" + ] + is True + ) + assert contract["verifier_invocation_locked"] is True + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_verifier_invocation_now"] is False + assert "execution_envelope_freeze_proof_closeout_ready" in check_keys + assert "frozen_envelope_verifier_handoff_ready" in check_keys + assert "frozen_envelope_verifier_handoff_no_execute" in check_keys + assert "verifier_invocation_lock_proof_bound" in check_keys + assert "verifier_invocation_lock_proof_blocks_execution" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_verifier_invocation_lock_proof_closeout_waits_without_ready_lock_closeout(): + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_verifier_invocation_lock_proof_closeout( + _payload(), + batch_size=1, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof" + ] + lock_closeout = closeout[ + "controlled_dry_run_verifier_invocation_lock_proof_closeout" + ] + receipt = lock_closeout["verifier_no_execution_receipt_proof"] + contract = closeout[ + "controlled_dry_run_verifier_invocation_lock_proof_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_verifier_invocation_lock_proof_closeout_checks" + ] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_verifier_invocation_lock_proof_closeout" + ) + assert closeout["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_FROZEN_ENVELOPE_VERIFIER_HANDOFF_CLOSEOUT" + ) + assert closeout["summary"]["controlled_dry_run_verifier_invocation_lock_proof_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_verifier_invocation_lock_proof_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_frozen_envelope_verifier_handoff_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_verifier_invocation_lock_proof_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_verifier_invocation_lock_proof_closeout_field_count"] == 12 + assert closeout["summary"]["controlled_dry_run_verifier_invocation_lock_proof_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["verifier_no_execution_receipt_proof_count"] == 1 + assert closeout["summary"]["verifier_no_execution_receipt_proof_field_count"] == 12 + assert closeout["summary"]["verifier_invocation_locked_count"] == 1 + assert closeout["summary"]["verifier_invoked_count"] == 0 + assert closeout["summary"]["verifier_receipt_present_count"] == 0 + assert closeout["summary"]["dry_run_executor_invoked_count"] == 0 + assert closeout["summary"]["runner_invocation_performed_count"] == 0 + assert closeout["summary"]["endpoint_executed_count"] == 0 + assert closeout["summary"]["sql_executed_count"] == 0 + assert closeout["summary"]["database_written_count"] == 0 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["verifier_invocation_lock_proof_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-verifier-invocation-lock-proof-closeout-" + ) + assert future["verifier_no_execution_receipt_proof_id"].endswith( + "-verifier-no-execution-receipt-proof" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof" + ] + is False + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof_closeout" + ] + is False + ) + assert future["verifier_invocation_lock_proof_closeout_ready"] is False + assert future["frozen_envelope_verifier_handoff_closeout_ready"] is False + assert future["verifier_no_execution_receipt_proof_bound"] is False + assert future["verifier_invocation_locked"] is True + assert future["verifier_invocation_allowed"] is False + assert future["verifier_invoked"] is False + assert future["verifier_receipt_present"] is False + assert future["dry_run_executor_invoked"] is False + assert future["runner_invocation_performed"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["ready_for_database_apply_now"] is False + assert future["ready_for_verifier_invocation_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert lock_closeout["authorization_material_type"] == ( + "controlled_dry_run_verifier_invocation_lock_proof_closeout" + ) + assert lock_closeout["verifier_invocation_lock_proof_closeout_only"] is True + assert lock_closeout["verifier_no_execution_receipt_proof_only"] is True + assert lock_closeout["verifier_invocation_locked"] is True + assert lock_closeout["verifier_invocation_allowed"] is False + assert lock_closeout["verifier_invoked"] is False + assert lock_closeout["verifier_receipt_present"] is False + assert lock_closeout["dry_run_executor_invoked"] is False + assert lock_closeout["runner_invocation_performed"] is False + assert lock_closeout["endpoint_executed"] is False + assert lock_closeout["sql_executed"] is False + assert lock_closeout["database_written"] is False + assert receipt["receipt_status"] == "verifier_no_execution_receipt_proof_preview_ready" + assert receipt["receipt_mode"] == "verifier_no_execution_receipt_proof_preview_only" + assert receipt["verifier_no_execution_receipt_proof_field_count"] == 12 + assert receipt["verifier_invocation_locked"] is True + assert receipt["verifier_invocation_allowed"] is False + assert receipt["verifier_invoked"] is False + assert receipt["verifier_receipt_present"] is False + assert receipt["dry_run_executor_invoked"] is False + assert receipt["runner_invocation_performed"] is False + assert receipt["endpoint_executed"] is False + assert receipt["sql_executed"] is False + assert receipt["database_written"] is False + assert receipt["ready_for_verifier_invocation_now"] is False + assert receipt["endpoint_execution_allowed"] is False + assert receipt["sql_execution_allowed"] is False + assert receipt["database_write_allowed"] is False + assert receipt["database_apply_authorized"] is False + assert receipt["executes_database_apply"] is False + assert receipt["executes_endpoint"] is False + assert receipt["executes_sql"] is False + assert receipt["writes_database"] is False + assert receipt["stdout_included"] is False + assert receipt["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof" + ] + is False + ) + assert contract["verifier_invocation_locked"] is True + assert contract["verifier_invoked"] is False + assert contract["verifier_receipt_present"] is False + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_verifier_invocation_now"] is False + assert "frozen_envelope_verifier_handoff_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "verifier_invocation_lock_proof_ready" in check_keys + assert "verifier_invocation_lock_proof_no_execute" in check_keys + assert "verifier_no_execution_receipt_proof_bound" in check_keys + assert "verifier_no_execution_receipt_proof_blocks_execution" in check_keys + assert "previous_closeouts_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bound" in check_keys + assert "frozen_envelope_verifier_handoff_closeout_contract_blocks_database_apply" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_verifier_invocation_lock_proof_closeout_ready_after_fake_fetch_but_verifier_receipt_is_absent(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_verifier_invocation_lock_proof_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof" + ] + lock_closeout = closeout[ + "controlled_dry_run_verifier_invocation_lock_proof_closeout" + ] + receipt = lock_closeout["verifier_no_execution_receipt_proof"] + lock = lock_closeout["verifier_invocation_lock_proof"] + source_closeout = lock_closeout["frozen_envelope_verifier_handoff_closeout"] + contract = closeout[ + "controlled_dry_run_verifier_invocation_lock_proof_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_verifier_invocation_lock_proof_closeout_checks" + ] + ] + assert closeout["result"] == ( + "DB_APPLY_CONTROLLED_DRY_RUN_VERIFIER_INVOCATION_LOCK_PROOF_CLOSEOUT_READY" + ) + assert closeout["summary"]["controlled_dry_run_verifier_invocation_lock_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_verifier_invocation_lock_proof_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_verifier_invocation_lock_proof_closeout_pass_count"] == 12 + assert closeout["summary"]["controlled_dry_run_verifier_invocation_lock_proof_closeout_waiting_count"] == 0 + assert closeout["summary"]["controlled_dry_run_frozen_envelope_verifier_handoff_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_execution_envelope_freeze_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["verifier_no_execution_receipt_proof_count"] == 1 + assert closeout["summary"]["verifier_no_execution_receipt_proof_field_count"] == 12 + assert closeout["summary"]["verifier_invocation_locked_count"] == 1 + assert closeout["summary"]["verifier_invoked_count"] == 0 + assert closeout["summary"]["verifier_receipt_present_count"] == 0 + assert closeout["summary"]["dry_run_executor_invoked_count"] == 0 + assert closeout["summary"]["runner_invocation_performed_count"] == 0 + assert closeout["summary"]["endpoint_executed_count"] == 0 + assert closeout["summary"]["sql_executed_count"] == 0 + assert closeout["summary"]["database_written_count"] == 0 + assert closeout["summary"]["reads_secret_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof_closeout" + ] + is True + ) + assert future["verifier_invocation_lock_proof_closeout_ready"] is True + assert future["frozen_envelope_verifier_handoff_closeout_ready"] is True + assert future["verifier_invocation_lock_proof_ready"] is True + assert future["verifier_no_execution_receipt_proof_bound"] is True + assert future["verifier_invocation_locked"] is True + assert future["verifier_invocation_allowed"] is False + assert future["verifier_invoked"] is False + assert future["verifier_receipt_present"] is False + assert future["dry_run_executor_invoked"] is False + assert future["runner_invocation_performed"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["ready_for_database_apply_now"] is False + assert future["ready_for_verifier_invocation_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert lock_closeout["ready_for_future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof"] is True + assert lock_closeout["verifier_invocation_lock_proof_closeout_field_count"] == 12 + assert lock_closeout["verifier_invocation_lock_proof_closeout_acceptance_gate_count"] == 10 + assert lock_closeout["verifier_no_execution_receipt_proof_count"] == 1 + assert lock_closeout["verifier_no_execution_receipt_proof_field_count"] == 12 + assert lock_closeout["verifier_invocation_lock_proof_closeout_only"] is True + assert lock_closeout["verifier_no_execution_receipt_proof_only"] is True + assert lock_closeout["verifier_invocation_locked"] is True + assert lock_closeout["verifier_invocation_allowed"] is False + assert lock_closeout["verifier_invoked"] is False + assert lock_closeout["verifier_receipt_present"] is False + assert lock_closeout["dry_run_executor_invoked"] is False + assert lock_closeout["runner_invocation_performed"] is False + assert lock_closeout["endpoint_executed"] is False + assert lock_closeout["sql_executed"] is False + assert lock_closeout["database_written"] is False + assert receipt["receipt_proof_id"] == future["verifier_no_execution_receipt_proof_id"] + assert ( + receipt["source_verifier_invocation_lock_proof_closeout_id"] + == future["verifier_invocation_lock_proof_closeout_id"] + ) + assert receipt["source_verifier_invocation_lock_proof_id"] == lock["lock_proof_id"] + assert ( + receipt["source_frozen_envelope_verifier_handoff_closeout_id"] + == source_closeout["frozen_envelope_verifier_handoff_closeout_id"] + ) + assert receipt["required_command_shape_hash"] == lock["required_command_shape_hash"] + assert receipt["receipt_status"] == "verifier_no_execution_receipt_proof_preview_ready" + assert receipt["receipt_mode"] == "verifier_no_execution_receipt_proof_preview_only" + assert receipt["verifier_invocation_locked"] is True + assert receipt["verifier_invocation_allowed"] is False + assert receipt["verifier_invoked"] is False + assert receipt["verifier_receipt_present"] is False + assert receipt["verifier_receipt_required"] is False + assert receipt["dry_run_executor_invoked"] is False + assert receipt["runner_invocation_performed"] is False + assert receipt["endpoint_executed"] is False + assert receipt["sql_executed"] is False + assert receipt["database_written"] is False + assert receipt["ready_for_verifier_invocation_now"] is False + assert receipt["endpoint_execution_allowed"] is False + assert receipt["sql_execution_allowed"] is False + assert receipt["database_write_allowed"] is False + assert receipt["database_apply_authorized"] is False + assert receipt["executes_database_apply"] is False + assert receipt["executes_endpoint"] is False + assert receipt["executes_sql"] is False + assert receipt["writes_database"] is False + assert receipt["stdout_included"] is False + assert receipt["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof" + ] + is True + ) + assert contract["verifier_invocation_locked"] is True + assert contract["verifier_invoked"] is False + assert contract["verifier_receipt_present"] is False + assert contract["executes_database_apply"] is False + assert contract["executes_endpoint"] is False + assert contract["executes_sql"] is False + assert contract["database_apply_authorized"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_verifier_invocation_now"] is False + assert "frozen_envelope_verifier_handoff_closeout_ready" in check_keys + assert "verifier_invocation_lock_proof_ready" in check_keys + assert "verifier_invocation_lock_proof_no_execute" in check_keys + assert "verifier_no_execution_receipt_proof_bound" in check_keys + assert "verifier_no_execution_receipt_proof_blocks_execution" in check_keys + assert "preview_has_no_side_effects_no_execution_no_signing" in check_keys + assert closeout["safety"]["reads_secret_in_preview"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_verifier_no_execution_receipt_proof_closeout_waits_without_ready_receipt_proof(): + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_verifier_no_execution_receipt_proof_closeout( + _payload(), + batch_size=1, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof" + ] + receipt_closeout = closeout[ + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout" + ] + guard = receipt_closeout["verifier_receipt_persistence_guard_proof"] + contract = closeout[ + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout_checks" + ] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_verifier_no_execution_receipt_proof_closeout" + ) + assert closeout["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_VERIFIER_INVOCATION_LOCK_PROOF_CLOSEOUT" + ) + assert closeout["summary"]["controlled_dry_run_verifier_no_execution_receipt_proof_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_verifier_no_execution_receipt_proof_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_verifier_invocation_lock_proof_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_verifier_no_execution_receipt_proof_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_verifier_no_execution_receipt_proof_closeout_field_count"] == 12 + assert closeout["summary"]["controlled_dry_run_verifier_no_execution_receipt_proof_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["verifier_receipt_persistence_guard_proof_count"] == 1 + assert closeout["summary"]["verifier_receipt_persistence_guard_proof_field_count"] == 12 + assert closeout["summary"]["verifier_receipt_persistence_locked_count"] == 1 + assert closeout["summary"]["verifier_receipt_persistence_allowed_count"] == 0 + assert closeout["summary"]["verifier_receipt_persisted_count"] == 0 + assert closeout["summary"]["persists_verifier_receipt_count"] == 0 + assert closeout["summary"]["verifier_invoked_count"] == 0 + assert closeout["summary"]["verifier_receipt_present_count"] == 0 + assert closeout["summary"]["dry_run_executor_invoked_count"] == 0 + assert closeout["summary"]["runner_invocation_performed_count"] == 0 + assert closeout["summary"]["endpoint_executed_count"] == 0 + assert closeout["summary"]["sql_executed_count"] == 0 + assert closeout["summary"]["database_written_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["verifier_no_execution_receipt_proof_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-verifier-no-execution-receipt-proof-closeout-" + ) + assert future["verifier_receipt_persistence_guard_proof_id"].endswith( + "-verifier-receipt-persistence-guard-proof" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof" + ] + is False + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout" + ] + is False + ) + assert future["verifier_no_execution_receipt_proof_closeout_ready"] is False + assert future["verifier_invocation_lock_proof_closeout_ready"] is False + assert future["verifier_receipt_persistence_guard_proof_bound"] is False + assert future["verifier_receipt_persistence_locked"] is True + assert future["verifier_receipt_persistence_allowed"] is False + assert future["verifier_receipt_persisted"] is False + assert future["persists_verifier_receipt"] is False + assert future["verifier_invoked"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["ready_for_database_apply_now"] is False + assert future["ready_for_verifier_receipt_persistence_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert receipt_closeout["authorization_material_type"] == ( + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout" + ) + assert receipt_closeout["verifier_no_execution_receipt_proof_closeout_only"] is True + assert receipt_closeout["verifier_receipt_persistence_guard_proof_only"] is True + assert receipt_closeout["verifier_receipt_persistence_locked"] is True + assert receipt_closeout["verifier_receipt_persistence_allowed"] is False + assert receipt_closeout["verifier_receipt_persisted"] is False + assert receipt_closeout["persists_verifier_receipt"] is False + assert receipt_closeout["verifier_invoked"] is False + assert receipt_closeout["endpoint_executed"] is False + assert receipt_closeout["sql_executed"] is False + assert receipt_closeout["database_written"] is False + assert guard["guard_status"] == "verifier_receipt_persistence_guard_proof_preview_ready" + assert guard["guard_mode"] == "verifier_receipt_persistence_guard_proof_preview_only" + assert guard["verifier_receipt_persistence_guard_proof_field_count"] == 12 + assert guard["verifier_receipt_persistence_locked"] is True + assert guard["verifier_receipt_persistence_allowed"] is False + assert guard["verifier_receipt_persisted"] is False + assert guard["persists_verifier_receipt"] is False + assert guard["verifier_invoked"] is False + assert guard["endpoint_executed"] is False + assert guard["sql_executed"] is False + assert guard["database_written"] is False + assert guard["ready_for_verifier_receipt_persistence_now"] is False + assert guard["endpoint_execution_allowed"] is False + assert guard["sql_execution_allowed"] is False + assert guard["database_write_allowed"] is False + assert guard["database_apply_authorized"] is False + assert guard["stdout_included"] is False + assert guard["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof" + ] + is False + ) + assert contract["verifier_receipt_persistence_locked"] is True + assert contract["verifier_receipt_persisted"] is False + assert contract["persists_verifier_receipt"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_verifier_receipt_persistence_now"] is False + assert contract["executes_database_apply"] is False + assert contract["database_apply_authorized"] is False + assert "verifier_invocation_lock_proof_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "verifier_no_execution_receipt_proof_ready" in check_keys + assert "verifier_no_execution_receipt_proof_no_execute" in check_keys + assert "verifier_receipt_persistence_guard_proof_bound" in check_keys + assert "verifier_receipt_persistence_guard_proof_blocks_persistence" in check_keys + assert "previous_closeouts_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bound" in check_keys + assert "verifier_invocation_lock_proof_closeout_contract_blocks_persistence_and_database_apply" in check_keys + assert "preview_has_no_side_effects_no_persistence_no_execution_no_signing" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["persists_verifier_receipt"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_verifier_no_execution_receipt_proof_closeout_ready_after_fake_fetch_but_persistence_is_locked(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_verifier_no_execution_receipt_proof_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof" + ] + receipt_closeout = closeout[ + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout" + ] + guard = receipt_closeout["verifier_receipt_persistence_guard_proof"] + receipt = receipt_closeout["verifier_no_execution_receipt_proof"] + source_closeout = receipt_closeout["verifier_invocation_lock_proof_closeout"] + contract = closeout[ + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout_checks" + ] + ] + assert closeout["result"] == ( + "DB_APPLY_CONTROLLED_DRY_RUN_VERIFIER_NO_EXECUTION_RECEIPT_PROOF_CLOSEOUT_READY" + ) + assert closeout["summary"]["controlled_dry_run_verifier_no_execution_receipt_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_verifier_no_execution_receipt_proof_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_verifier_no_execution_receipt_proof_closeout_pass_count"] == 12 + assert closeout["summary"]["controlled_dry_run_verifier_no_execution_receipt_proof_closeout_waiting_count"] == 0 + assert closeout["summary"]["controlled_dry_run_verifier_invocation_lock_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["verifier_receipt_persistence_guard_proof_count"] == 1 + assert closeout["summary"]["verifier_receipt_persistence_guard_proof_field_count"] == 12 + assert closeout["summary"]["verifier_receipt_persistence_locked_count"] == 1 + assert closeout["summary"]["verifier_receipt_persistence_allowed_count"] == 0 + assert closeout["summary"]["verifier_receipt_persisted_count"] == 0 + assert closeout["summary"]["persists_verifier_receipt_count"] == 0 + assert closeout["summary"]["verifier_invoked_count"] == 0 + assert closeout["summary"]["verifier_receipt_present_count"] == 0 + assert closeout["summary"]["dry_run_executor_invoked_count"] == 0 + assert closeout["summary"]["runner_invocation_performed_count"] == 0 + assert closeout["summary"]["endpoint_executed_count"] == 0 + assert closeout["summary"]["sql_executed_count"] == 0 + assert closeout["summary"]["database_written_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout" + ] + is True + ) + assert future["verifier_no_execution_receipt_proof_closeout_ready"] is True + assert future["verifier_invocation_lock_proof_closeout_ready"] is True + assert future["verifier_no_execution_receipt_proof_ready"] is True + assert future["verifier_receipt_persistence_guard_proof_bound"] is True + assert future["verifier_receipt_persistence_locked"] is True + assert future["verifier_receipt_persistence_allowed"] is False + assert future["verifier_receipt_persisted"] is False + assert future["persists_verifier_receipt"] is False + assert future["verifier_invoked"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["ready_for_database_apply_now"] is False + assert future["ready_for_verifier_receipt_persistence_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert receipt_closeout["ready_for_future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof"] is True + assert receipt_closeout["verifier_no_execution_receipt_proof_closeout_field_count"] == 12 + assert receipt_closeout["verifier_no_execution_receipt_proof_closeout_acceptance_gate_count"] == 10 + assert receipt_closeout["verifier_receipt_persistence_guard_proof_count"] == 1 + assert receipt_closeout["verifier_receipt_persistence_guard_proof_field_count"] == 12 + assert receipt_closeout["verifier_no_execution_receipt_proof_closeout_only"] is True + assert receipt_closeout["verifier_receipt_persistence_guard_proof_only"] is True + assert receipt_closeout["verifier_receipt_persistence_locked"] is True + assert receipt_closeout["verifier_receipt_persistence_allowed"] is False + assert receipt_closeout["verifier_receipt_persisted"] is False + assert receipt_closeout["persists_verifier_receipt"] is False + assert receipt_closeout["verifier_invoked"] is False + assert receipt_closeout["endpoint_executed"] is False + assert receipt_closeout["sql_executed"] is False + assert receipt_closeout["database_written"] is False + assert guard["guard_proof_id"] == future["verifier_receipt_persistence_guard_proof_id"] + assert ( + guard["source_verifier_no_execution_receipt_proof_closeout_id"] + == future["verifier_no_execution_receipt_proof_closeout_id"] + ) + assert ( + guard["source_verifier_invocation_lock_proof_closeout_id"] + == source_closeout["verifier_invocation_lock_proof_closeout_id"] + ) + assert guard["source_verifier_no_execution_receipt_proof_id"] == receipt["receipt_proof_id"] + assert guard["required_command_shape_hash"] == receipt["required_command_shape_hash"] + assert guard["guard_status"] == "verifier_receipt_persistence_guard_proof_preview_ready" + assert guard["guard_mode"] == "verifier_receipt_persistence_guard_proof_preview_only" + assert guard["verifier_receipt_persistence_locked"] is True + assert guard["verifier_receipt_persistence_allowed"] is False + assert guard["verifier_receipt_persisted"] is False + assert guard["persists_verifier_receipt"] is False + assert guard["persistence_receipt_present"] is False + assert guard["verifier_invoked"] is False + assert guard["endpoint_executed"] is False + assert guard["sql_executed"] is False + assert guard["database_written"] is False + assert guard["ready_for_verifier_receipt_persistence_now"] is False + assert guard["endpoint_execution_allowed"] is False + assert guard["sql_execution_allowed"] is False + assert guard["database_write_allowed"] is False + assert guard["database_apply_authorized"] is False + assert guard["executes_database_apply"] is False + assert guard["executes_endpoint"] is False + assert guard["executes_sql"] is False + assert guard["writes_database"] is False + assert guard["stdout_included"] is False + assert guard["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof" + ] + is True + ) + assert contract["verifier_receipt_persistence_locked"] is True + assert contract["verifier_receipt_persistence_allowed"] is False + assert contract["verifier_receipt_persisted"] is False + assert contract["persists_verifier_receipt"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_verifier_receipt_persistence_now"] is False + assert contract["executes_database_apply"] is False + assert contract["database_apply_authorized"] is False + assert "verifier_no_execution_receipt_proof_ready" in check_keys + assert "verifier_no_execution_receipt_proof_no_execute" in check_keys + assert "verifier_receipt_persistence_guard_proof_bound" in check_keys + assert "verifier_receipt_persistence_guard_proof_blocks_persistence" in check_keys + assert "preview_has_no_side_effects_no_persistence_no_execution_no_signing" in check_keys + assert closeout["safety"]["persists_verifier_receipt"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_waits_without_ready_storage_boundary(): + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout( + _payload(), + batch_size=1, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof" + ] + storage_closeout = closeout[ + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout" + ] + storage = storage_closeout["receipt_persistence_storage_boundary_proof"] + contract = closeout[ + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_checks" + ] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout" + ) + assert closeout["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_VERIFIER_NO_EXECUTION_RECEIPT_PROOF_CLOSEOUT" + ) + assert closeout["summary"]["controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_verifier_no_execution_receipt_proof_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_field_count"] == 12 + assert closeout["summary"]["controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["receipt_persistence_storage_boundary_proof_count"] == 1 + assert closeout["summary"]["receipt_persistence_storage_boundary_proof_field_count"] == 12 + assert closeout["summary"]["receipt_persistence_storage_boundary_locked_count"] == 1 + assert closeout["summary"]["receipt_persistence_storage_write_allowed_count"] == 0 + assert closeout["summary"]["receipt_persistence_storage_written_count"] == 0 + assert closeout["summary"]["verifier_receipt_persistence_allowed_count"] == 0 + assert closeout["summary"]["verifier_receipt_persisted_count"] == 0 + assert closeout["summary"]["persists_verifier_receipt_count"] == 0 + assert closeout["summary"]["verifier_invoked_count"] == 0 + assert closeout["summary"]["endpoint_executed_count"] == 0 + assert closeout["summary"]["sql_executed_count"] == 0 + assert closeout["summary"]["database_written_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["verifier_receipt_persistence_guard_proof_closeout_id"].startswith( + "pchome-db-apply-controlled-dry-run-verifier-receipt-persistence-guard-proof-closeout-" + ) + assert future["receipt_persistence_storage_boundary_proof_id"].endswith( + "-receipt-persistence-storage-boundary-proof" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof" + ] + is False + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout" + ] + is False + ) + assert future["receipt_persistence_storage_boundary_locked"] is True + assert future["receipt_persistence_storage_write_allowed"] is False + assert future["receipt_persistence_storage_written"] is False + assert future["verifier_receipt_persistence_allowed"] is False + assert future["verifier_receipt_persisted"] is False + assert future["persists_verifier_receipt"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["ready_for_database_apply_now"] is False + assert future["ready_for_receipt_persistence_storage_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert storage_closeout["verifier_receipt_persistence_guard_proof_closeout_only"] is True + assert storage_closeout["receipt_persistence_storage_boundary_proof_only"] is True + assert storage_closeout["receipt_persistence_storage_boundary_locked"] is True + assert storage_closeout["receipt_persistence_storage_write_allowed"] is False + assert storage_closeout["receipt_persistence_storage_written"] is False + assert storage_closeout["persists_verifier_receipt"] is False + assert storage_closeout["endpoint_executed"] is False + assert storage_closeout["sql_executed"] is False + assert storage_closeout["database_written"] is False + assert storage["storage_boundary_status"] == "receipt_persistence_storage_boundary_proof_preview_ready" + assert storage["storage_boundary_mode"] == "receipt_persistence_storage_boundary_proof_preview_only" + assert storage["receipt_persistence_storage_boundary_proof_field_count"] == 12 + assert storage["receipt_persistence_storage_boundary_locked"] is True + assert storage["receipt_persistence_storage_write_allowed"] is False + assert storage["receipt_persistence_storage_written"] is False + assert storage["verifier_receipt_persistence_allowed"] is False + assert storage["verifier_receipt_persisted"] is False + assert storage["persists_verifier_receipt"] is False + assert storage["endpoint_execution_allowed"] is False + assert storage["sql_execution_allowed"] is False + assert storage["database_write_allowed"] is False + assert storage["database_apply_authorized"] is False + assert storage["stdout_included"] is False + assert storage["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof" + ] + is False + ) + assert contract["receipt_persistence_storage_boundary_locked"] is True + assert contract["receipt_persistence_storage_write_allowed"] is False + assert contract["receipt_persistence_storage_written"] is False + assert contract["persists_verifier_receipt"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_receipt_persistence_storage_now"] is False + assert contract["executes_database_apply"] is False + assert contract["database_apply_authorized"] is False + assert "verifier_no_execution_receipt_proof_closeout_ready" in check_keys + assert "source_chain_ids_match" in check_keys + assert "verifier_receipt_persistence_guard_proof_ready" in check_keys + assert "verifier_receipt_persistence_guard_proof_no_persistence" in check_keys + assert "receipt_persistence_storage_boundary_proof_bound" in check_keys + assert "receipt_persistence_storage_boundary_proof_blocks_storage" in check_keys + assert "previous_closeouts_carried_forward" in check_keys + assert "target_migration_hash_locked" in check_keys + assert "rollback_and_post_apply_verifier_bound" in check_keys + assert "verifier_no_execution_receipt_proof_closeout_contract_blocks_storage_persistence_and_database_apply" in check_keys + assert "preview_has_no_side_effects_no_storage_no_persistence_no_execution_no_signing" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["persists_verifier_receipt"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_ready_after_fake_fetch_but_storage_is_locked(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof" + ] + storage_closeout = closeout[ + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout" + ] + storage = storage_closeout["receipt_persistence_storage_boundary_proof"] + guard = storage_closeout["verifier_receipt_persistence_guard_proof"] + contract = closeout[ + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_checks" + ] + ] + assert closeout["result"] == ( + "DB_APPLY_CONTROLLED_DRY_RUN_VERIFIER_RECEIPT_PERSISTENCE_GUARD_PROOF_CLOSEOUT_READY" + ) + assert closeout["summary"]["controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_pass_count"] == 12 + assert closeout["summary"]["controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_waiting_count"] == 0 + assert closeout["summary"]["controlled_dry_run_verifier_no_execution_receipt_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["receipt_persistence_storage_boundary_proof_count"] == 1 + assert closeout["summary"]["receipt_persistence_storage_boundary_proof_field_count"] == 12 + assert closeout["summary"]["receipt_persistence_storage_boundary_locked_count"] == 1 + assert closeout["summary"]["receipt_persistence_storage_write_allowed_count"] == 0 + assert closeout["summary"]["receipt_persistence_storage_written_count"] == 0 + assert closeout["summary"]["verifier_receipt_persistence_allowed_count"] == 0 + assert closeout["summary"]["verifier_receipt_persisted_count"] == 0 + assert closeout["summary"]["persists_verifier_receipt_count"] == 0 + assert closeout["summary"]["endpoint_executed_count"] == 0 + assert closeout["summary"]["sql_executed_count"] == 0 + assert closeout["summary"]["database_written_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert closeout["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout" + ] + is True + ) + assert future["verifier_receipt_persistence_guard_proof_closeout_ready"] is True + assert future["verifier_no_execution_receipt_proof_closeout_ready"] is True + assert future["verifier_receipt_persistence_guard_proof_ready"] is True + assert future["receipt_persistence_storage_boundary_proof_bound"] is True + assert future["receipt_persistence_storage_boundary_locked"] is True + assert future["receipt_persistence_storage_write_allowed"] is False + assert future["receipt_persistence_storage_written"] is False + assert future["verifier_receipt_persistence_allowed"] is False + assert future["verifier_receipt_persisted"] is False + assert future["persists_verifier_receipt"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["ready_for_database_apply_now"] is False + assert future["ready_for_receipt_persistence_storage_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert storage_closeout["ready_for_future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof"] is True + assert storage_closeout["verifier_receipt_persistence_guard_proof_closeout_field_count"] == 12 + assert storage_closeout["verifier_receipt_persistence_guard_proof_closeout_acceptance_gate_count"] == 10 + assert storage_closeout["receipt_persistence_storage_boundary_proof_count"] == 1 + assert storage_closeout["receipt_persistence_storage_boundary_proof_field_count"] == 12 + assert storage_closeout["verifier_receipt_persistence_guard_proof_closeout_only"] is True + assert storage_closeout["receipt_persistence_storage_boundary_proof_only"] is True + assert storage_closeout["receipt_persistence_storage_boundary_locked"] is True + assert storage_closeout["receipt_persistence_storage_write_allowed"] is False + assert storage_closeout["receipt_persistence_storage_written"] is False + assert storage_closeout["verifier_receipt_persistence_allowed"] is False + assert storage_closeout["verifier_receipt_persisted"] is False + assert storage_closeout["persists_verifier_receipt"] is False + assert storage_closeout["endpoint_executed"] is False + assert storage_closeout["sql_executed"] is False + assert storage_closeout["database_written"] is False + assert storage["storage_boundary_proof_id"] == future["receipt_persistence_storage_boundary_proof_id"] + assert ( + storage["source_verifier_receipt_persistence_guard_proof_closeout_id"] + == future["verifier_receipt_persistence_guard_proof_closeout_id"] + ) + assert ( + storage["source_verifier_receipt_persistence_guard_proof_id"] + == guard["guard_proof_id"] + ) + assert storage["required_command_shape_hash"] == guard["required_command_shape_hash"] + assert storage["storage_boundary_status"] == "receipt_persistence_storage_boundary_proof_preview_ready" + assert storage["storage_boundary_mode"] == "receipt_persistence_storage_boundary_proof_preview_only" + assert storage["receipt_persistence_storage_boundary_locked"] is True + assert storage["receipt_persistence_storage_write_allowed"] is False + assert storage["receipt_persistence_storage_written"] is False + assert storage["verifier_receipt_persistence_allowed"] is False + assert storage["verifier_receipt_persisted"] is False + assert storage["persists_verifier_receipt"] is False + assert storage["endpoint_executed"] is False + assert storage["sql_executed"] is False + assert storage["database_written"] is False + assert storage["ready_for_receipt_persistence_storage_now"] is False + assert storage["endpoint_execution_allowed"] is False + assert storage["sql_execution_allowed"] is False + assert storage["database_write_allowed"] is False + assert storage["database_apply_authorized"] is False + assert storage["executes_database_apply"] is False + assert storage["executes_endpoint"] is False + assert storage["executes_sql"] is False + assert storage["writes_database"] is False + assert storage["stdout_included"] is False + assert storage["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof" + ] + is True + ) + assert contract["receipt_persistence_storage_boundary_locked"] is True + assert contract["receipt_persistence_storage_write_allowed"] is False + assert contract["receipt_persistence_storage_written"] is False + assert contract["persists_verifier_receipt"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_receipt_persistence_storage_now"] is False + assert contract["executes_database_apply"] is False + assert contract["database_apply_authorized"] is False + assert "verifier_receipt_persistence_guard_proof_ready" in check_keys + assert "verifier_receipt_persistence_guard_proof_no_persistence" in check_keys + assert "receipt_persistence_storage_boundary_proof_bound" in check_keys + assert "receipt_persistence_storage_boundary_proof_blocks_storage" in check_keys + assert "preview_has_no_side_effects_no_storage_no_persistence_no_execution_no_signing" in check_keys + assert closeout["safety"]["persists_verifier_receipt"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_waits_without_ready_ledger(): + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout( + _payload(), + batch_size=1, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof" + ] + storage_closeout = closeout[ + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout" + ] + ledger = storage_closeout["storage_boundary_no_write_ledger_proof"] + contract = closeout[ + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_checks" + ] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout" + ) + assert closeout["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_VERIFIER_RECEIPT_PERSISTENCE_GUARD_PROOF_CLOSEOUT" + ) + assert closeout["summary"]["controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_field_count"] == 12 + assert closeout["summary"]["controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_ready_count"] == 0 + assert closeout["summary"]["storage_boundary_no_write_ledger_proof_count"] == 1 + assert closeout["summary"]["storage_boundary_no_write_ledger_proof_field_count"] == 12 + assert closeout["summary"]["storage_boundary_write_locked_count"] == 1 + assert closeout["summary"]["storage_boundary_write_allowed_count"] == 0 + assert closeout["summary"]["storage_boundary_written_count"] == 0 + assert closeout["summary"]["ledger_write_allowed_count"] == 0 + assert closeout["summary"]["ledger_written_count"] == 0 + assert closeout["summary"]["receipt_persistence_storage_write_allowed_count"] == 0 + assert closeout["summary"]["receipt_persistence_storage_written_count"] == 0 + assert closeout["summary"]["persists_verifier_receipt_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert future["storage_boundary_no_write_ledger_proof_id"].endswith( + "-storage-boundary-no-write-ledger-proof" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof" + ] + is False + ) + assert future["storage_boundary_write_locked"] is True + assert future["storage_boundary_write_allowed"] is False + assert future["storage_boundary_written"] is False + assert future["ledger_write_allowed"] is False + assert future["ledger_written"] is False + assert future["receipt_persistence_storage_write_allowed"] is False + assert future["receipt_persistence_storage_written"] is False + assert future["persists_verifier_receipt"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["database_apply_authorized"] is False + assert storage_closeout["receipt_persistence_storage_boundary_proof_closeout_only"] is True + assert storage_closeout["storage_boundary_no_write_ledger_proof_only"] is True + assert storage_closeout["storage_boundary_no_write_ledger_proof_count"] == 1 + assert storage_closeout["storage_boundary_no_write_ledger_proof_field_count"] == 12 + assert storage_closeout["storage_boundary_write_locked"] is True + assert storage_closeout["storage_boundary_write_allowed"] is False + assert storage_closeout["storage_boundary_written"] is False + assert storage_closeout["ledger_write_allowed"] is False + assert storage_closeout["ledger_written"] is False + assert storage_closeout["receipt_persistence_storage_write_allowed"] is False + assert storage_closeout["receipt_persistence_storage_written"] is False + assert storage_closeout["persists_verifier_receipt"] is False + assert storage_closeout["endpoint_executed"] is False + assert storage_closeout["sql_executed"] is False + assert storage_closeout["database_written"] is False + assert ledger["ledger_status"] == "storage_boundary_no_write_ledger_proof_preview_ready" + assert ledger["ledger_mode"] == "storage_boundary_no_write_ledger_proof_preview_only" + assert ledger["storage_boundary_write_locked"] is True + assert ledger["storage_boundary_write_allowed"] is False + assert ledger["storage_boundary_written"] is False + assert ledger["ledger_write_allowed"] is False + assert ledger["ledger_written"] is False + assert ledger["receipt_persistence_storage_write_allowed"] is False + assert ledger["receipt_persistence_storage_written"] is False + assert ledger["persists_verifier_receipt"] is False + assert ledger["endpoint_executed"] is False + assert ledger["sql_executed"] is False + assert ledger["database_written"] is False + assert ledger["database_apply_authorized"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof" + ] + is False + ) + assert contract["storage_boundary_write_allowed"] is False + assert contract["ledger_write_allowed"] is False + assert contract["receipt_persistence_storage_write_allowed"] is False + assert contract["persists_verifier_receipt"] is False + assert contract["executes_database_apply"] is False + assert contract["database_apply_authorized"] is False + assert "verifier_receipt_persistence_guard_proof_closeout_ready" in check_keys + assert "receipt_persistence_storage_boundary_proof_ready" in check_keys + assert "receipt_persistence_storage_boundary_proof_no_write" in check_keys + assert "storage_boundary_no_write_ledger_proof_bound" in check_keys + assert "storage_boundary_no_write_ledger_proof_blocks_write" in check_keys + assert "preview_has_no_side_effects_no_ledger_no_storage_no_persistence_no_execution_no_signing" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["persists_verifier_receipt"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_ready_after_fake_fetch_but_ledger_write_is_locked(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof" + ] + storage_closeout = closeout[ + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout" + ] + previous_storage_closeout = storage_closeout[ + "verifier_receipt_persistence_guard_proof_closeout" + ] + storage = storage_closeout["receipt_persistence_storage_boundary_proof"] + ledger = storage_closeout["storage_boundary_no_write_ledger_proof"] + contract = closeout[ + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_checks" + ] + ] + assert closeout["result"] == ( + "DB_APPLY_CONTROLLED_DRY_RUN_RECEIPT_PERSISTENCE_STORAGE_BOUNDARY_PROOF_CLOSEOUT_READY" + ) + assert closeout["summary"]["controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_pass_count"] == 12 + assert closeout["summary"]["controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_waiting_count"] == 0 + assert closeout["summary"]["controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["storage_boundary_no_write_ledger_proof_count"] == 1 + assert closeout["summary"]["storage_boundary_no_write_ledger_proof_field_count"] == 12 + assert closeout["summary"]["storage_boundary_write_locked_count"] == 1 + assert closeout["summary"]["storage_boundary_write_allowed_count"] == 0 + assert closeout["summary"]["storage_boundary_written_count"] == 0 + assert closeout["summary"]["ledger_write_allowed_count"] == 0 + assert closeout["summary"]["ledger_written_count"] == 0 + assert closeout["summary"]["receipt_persistence_storage_write_allowed_count"] == 0 + assert closeout["summary"]["receipt_persistence_storage_written_count"] == 0 + assert closeout["summary"]["persists_verifier_receipt_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout" + ] + is True + ) + assert future["receipt_persistence_storage_boundary_proof_closeout_ready"] is True + assert future["verifier_receipt_persistence_guard_proof_closeout_ready"] is True + assert future["receipt_persistence_storage_boundary_proof_ready"] is True + assert future["storage_boundary_no_write_ledger_proof_bound"] is True + assert future["storage_boundary_write_locked"] is True + assert future["storage_boundary_write_allowed"] is False + assert future["storage_boundary_written"] is False + assert future["ledger_write_allowed"] is False + assert future["ledger_written"] is False + assert future["receipt_persistence_storage_write_allowed"] is False + assert future["receipt_persistence_storage_written"] is False + assert future["persists_verifier_receipt"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["database_apply_authorized"] is False + assert storage_closeout["receipt_persistence_storage_boundary_proof_closeout_field_count"] == 12 + assert storage_closeout["receipt_persistence_storage_boundary_proof_closeout_acceptance_gate_count"] == 10 + assert storage_closeout["receipt_persistence_storage_boundary_proof_closeout_only"] is True + assert storage_closeout["storage_boundary_no_write_ledger_proof_only"] is True + assert storage_closeout["source_verifier_receipt_persistence_guard_proof_closeout_id"] == previous_storage_closeout["verifier_receipt_persistence_guard_proof_closeout_id"] + assert storage_closeout["source_receipt_persistence_storage_boundary_proof_id"] == storage["storage_boundary_proof_id"] + assert ledger["source_receipt_persistence_storage_boundary_proof_closeout_id"] == storage_closeout["receipt_persistence_storage_boundary_proof_closeout_id"] + assert ledger["source_verifier_receipt_persistence_guard_proof_closeout_id"] == previous_storage_closeout["verifier_receipt_persistence_guard_proof_closeout_id"] + assert ledger["source_receipt_persistence_storage_boundary_proof_id"] == storage["storage_boundary_proof_id"] + assert ledger["required_command_shape_hash"] == storage["required_command_shape_hash"] + assert ledger["ledger_status"] == "storage_boundary_no_write_ledger_proof_preview_ready" + assert ledger["ledger_mode"] == "storage_boundary_no_write_ledger_proof_preview_only" + assert ledger["storage_boundary_write_locked"] is True + assert ledger["storage_boundary_write_allowed"] is False + assert ledger["storage_boundary_written"] is False + assert ledger["ledger_write_allowed"] is False + assert ledger["ledger_written"] is False + assert ledger["receipt_persistence_storage_write_allowed"] is False + assert ledger["receipt_persistence_storage_written"] is False + assert ledger["verifier_receipt_persistence_allowed"] is False + assert ledger["verifier_receipt_persisted"] is False + assert ledger["persists_verifier_receipt"] is False + assert ledger["verifier_invoked"] is False + assert ledger["dry_run_executor_invoked"] is False + assert ledger["runner_invocation_performed"] is False + assert ledger["endpoint_executed"] is False + assert ledger["sql_executed"] is False + assert ledger["database_written"] is False + assert ledger["ready_for_database_apply_now"] is False + assert ledger["ready_for_storage_boundary_ledger_write_now"] is False + assert ledger["ready_for_receipt_persistence_storage_now"] is False + assert ledger["endpoint_execution_allowed"] is False + assert ledger["sql_execution_allowed"] is False + assert ledger["database_write_allowed"] is False + assert ledger["database_apply_authorized"] is False + assert ledger["stdout_included"] is False + assert ledger["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof" + ] + is True + ) + assert contract["storage_boundary_write_allowed"] is False + assert contract["ledger_write_allowed"] is False + assert contract["receipt_persistence_storage_write_allowed"] is False + assert contract["persists_verifier_receipt"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_storage_boundary_ledger_write_now"] is False + assert contract["ready_for_receipt_persistence_storage_now"] is False + assert contract["executes_database_apply"] is False + assert contract["database_apply_authorized"] is False + assert "storage_boundary_no_write_ledger_proof_bound" in check_keys + assert "storage_boundary_no_write_ledger_proof_blocks_write" in check_keys + assert "preview_has_no_side_effects_no_ledger_no_storage_no_persistence_no_execution_no_signing" in check_keys + assert closeout["safety"]["persists_verifier_receipt"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_waits_without_ready_retention(): + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout( + _payload(), + batch_size=1, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_no_write_ledger_retention_proof" + ] + ledger_closeout = closeout[ + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout" + ] + retention = ledger_closeout["no_write_ledger_retention_proof"] + contract = closeout[ + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_checks" + ] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout" + ) + assert closeout["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_RECEIPT_PERSISTENCE_STORAGE_BOUNDARY_PROOF_CLOSEOUT" + ) + assert closeout["summary"]["controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_field_count"] == 12 + assert closeout["summary"]["controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_ready_count"] == 0 + assert closeout["summary"]["no_write_ledger_retention_proof_count"] == 1 + assert closeout["summary"]["no_write_ledger_retention_proof_field_count"] == 12 + assert closeout["summary"]["ledger_retention_write_locked_count"] == 1 + assert closeout["summary"]["ledger_retention_write_allowed_count"] == 0 + assert closeout["summary"]["ledger_retention_written_count"] == 0 + assert closeout["summary"]["ledger_write_allowed_count"] == 0 + assert closeout["summary"]["ledger_written_count"] == 0 + assert closeout["summary"]["receipt_persistence_storage_write_allowed_count"] == 0 + assert closeout["summary"]["receipt_persistence_storage_written_count"] == 0 + assert closeout["summary"]["persists_verifier_receipt_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert future["no_write_ledger_retention_proof_id"].endswith( + "-no-write-ledger-retention-proof" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_no_write_ledger_retention_proof" + ] + is False + ) + assert future["ledger_retention_write_locked"] is True + assert future["ledger_retention_write_allowed"] is False + assert future["ledger_retention_written"] is False + assert future["ledger_write_allowed"] is False + assert future["ledger_written"] is False + assert future["receipt_persistence_storage_write_allowed"] is False + assert future["receipt_persistence_storage_written"] is False + assert future["persists_verifier_receipt"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["database_apply_authorized"] is False + assert ledger_closeout["storage_boundary_no_write_ledger_proof_closeout_only"] is True + assert ledger_closeout["no_write_ledger_retention_proof_only"] is True + assert ledger_closeout["no_write_ledger_retention_proof_count"] == 1 + assert ledger_closeout["no_write_ledger_retention_proof_field_count"] == 12 + assert ledger_closeout["ledger_retention_write_locked"] is True + assert ledger_closeout["ledger_retention_write_allowed"] is False + assert ledger_closeout["ledger_retention_written"] is False + assert ledger_closeout["ledger_write_allowed"] is False + assert ledger_closeout["ledger_written"] is False + assert ledger_closeout["persists_verifier_receipt"] is False + assert ledger_closeout["endpoint_executed"] is False + assert ledger_closeout["sql_executed"] is False + assert ledger_closeout["database_written"] is False + assert retention["retention_status"] == "no_write_ledger_retention_proof_preview_ready" + assert retention["retention_mode"] == "no_write_ledger_retention_proof_preview_only" + assert retention["ledger_retention_write_locked"] is True + assert retention["ledger_retention_write_allowed"] is False + assert retention["ledger_retention_written"] is False + assert retention["ledger_write_allowed"] is False + assert retention["ledger_written"] is False + assert retention["receipt_persistence_storage_write_allowed"] is False + assert retention["receipt_persistence_storage_written"] is False + assert retention["persists_verifier_receipt"] is False + assert retention["endpoint_executed"] is False + assert retention["sql_executed"] is False + assert retention["database_written"] is False + assert retention["database_apply_authorized"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_no_write_ledger_retention_proof" + ] + is False + ) + assert contract["ledger_retention_write_allowed"] is False + assert contract["ledger_retention_written"] is False + assert contract["ledger_write_allowed"] is False + assert contract["persists_verifier_receipt"] is False + assert contract["executes_database_apply"] is False + assert contract["database_apply_authorized"] is False + assert "receipt_persistence_storage_boundary_proof_closeout_ready" in check_keys + assert "storage_boundary_no_write_ledger_proof_ready" in check_keys + assert "storage_boundary_no_write_ledger_proof_no_write" in check_keys + assert "no_write_ledger_retention_proof_bound" in check_keys + assert "no_write_ledger_retention_proof_blocks_persistence" in check_keys + assert "preview_has_no_side_effects_no_retention_no_ledger_no_storage_no_persistence_no_execution_no_signing" in check_keys + assert "manual_review_not_required_for_safe_preview" in check_keys + assert closeout["safety"]["persists_verifier_receipt"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_ready_after_fake_fetch_but_retention_write_is_locked(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_no_write_ledger_retention_proof" + ] + ledger_closeout = closeout[ + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout" + ] + source_closeout = ledger_closeout[ + "receipt_persistence_storage_boundary_proof_closeout" + ] + ledger = ledger_closeout["storage_boundary_no_write_ledger_proof"] + retention = ledger_closeout["no_write_ledger_retention_proof"] + contract = closeout[ + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_checks" + ] + ] + assert closeout["result"] == ( + "DB_APPLY_CONTROLLED_DRY_RUN_STORAGE_BOUNDARY_NO_WRITE_LEDGER_PROOF_CLOSEOUT_READY" + ) + assert closeout["summary"]["controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_pass_count"] == 12 + assert closeout["summary"]["controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_waiting_count"] == 0 + assert closeout["summary"]["controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["no_write_ledger_retention_proof_count"] == 1 + assert closeout["summary"]["no_write_ledger_retention_proof_field_count"] == 12 + assert closeout["summary"]["ledger_retention_write_locked_count"] == 1 + assert closeout["summary"]["ledger_retention_write_allowed_count"] == 0 + assert closeout["summary"]["ledger_retention_written_count"] == 0 + assert closeout["summary"]["ledger_write_allowed_count"] == 0 + assert closeout["summary"]["ledger_written_count"] == 0 + assert closeout["summary"]["receipt_persistence_storage_write_allowed_count"] == 0 + assert closeout["summary"]["receipt_persistence_storage_written_count"] == 0 + assert closeout["summary"]["persists_verifier_receipt_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_no_write_ledger_retention_proof" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_no_write_ledger_retention_proof_closeout" + ] + is True + ) + assert future["storage_boundary_no_write_ledger_proof_closeout_ready"] is True + assert future["receipt_persistence_storage_boundary_proof_closeout_ready"] is True + assert future["storage_boundary_no_write_ledger_proof_ready"] is True + assert future["no_write_ledger_retention_proof_bound"] is True + assert future["ledger_retention_write_locked"] is True + assert future["ledger_retention_write_allowed"] is False + assert future["ledger_retention_written"] is False + assert future["ledger_write_allowed"] is False + assert future["ledger_written"] is False + assert future["persists_verifier_receipt"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["database_apply_authorized"] is False + assert ledger_closeout["storage_boundary_no_write_ledger_proof_closeout_field_count"] == 12 + assert ledger_closeout["storage_boundary_no_write_ledger_proof_closeout_acceptance_gate_count"] == 10 + assert ledger_closeout["storage_boundary_no_write_ledger_proof_closeout_only"] is True + assert ledger_closeout["no_write_ledger_retention_proof_only"] is True + assert ledger_closeout["source_receipt_persistence_storage_boundary_proof_closeout_id"] == source_closeout["receipt_persistence_storage_boundary_proof_closeout_id"] + assert ledger_closeout["source_storage_boundary_no_write_ledger_proof_id"] == ledger["ledger_proof_id"] + assert retention["source_storage_boundary_no_write_ledger_proof_closeout_id"] == ledger_closeout["storage_boundary_no_write_ledger_proof_closeout_id"] + assert retention["source_storage_boundary_no_write_ledger_proof_id"] == ledger["ledger_proof_id"] + assert retention["source_receipt_persistence_storage_boundary_proof_closeout_id"] == source_closeout["receipt_persistence_storage_boundary_proof_closeout_id"] + assert retention["required_command_shape_hash"] == ledger["required_command_shape_hash"] + assert retention["retention_status"] == "no_write_ledger_retention_proof_preview_ready" + assert retention["retention_mode"] == "no_write_ledger_retention_proof_preview_only" + assert retention["ledger_retention_write_locked"] is True + assert retention["ledger_retention_write_allowed"] is False + assert retention["ledger_retention_written"] is False + assert retention["retention_receipt_present"] is False + assert retention["retention_receipt_required"] is False + assert retention["ledger_write_allowed"] is False + assert retention["ledger_written"] is False + assert retention["receipt_persistence_storage_write_allowed"] is False + assert retention["receipt_persistence_storage_written"] is False + assert retention["verifier_receipt_persistence_allowed"] is False + assert retention["verifier_receipt_persisted"] is False + assert retention["persists_verifier_receipt"] is False + assert retention["verifier_invoked"] is False + assert retention["dry_run_executor_invoked"] is False + assert retention["runner_invocation_performed"] is False + assert retention["endpoint_executed"] is False + assert retention["sql_executed"] is False + assert retention["database_written"] is False + assert retention["ready_for_database_apply_now"] is False + assert retention["ready_for_no_write_ledger_retention_now"] is False + assert retention["ready_for_storage_boundary_ledger_write_now"] is False + assert retention["endpoint_execution_allowed"] is False + assert retention["sql_execution_allowed"] is False + assert retention["database_write_allowed"] is False + assert retention["database_apply_authorized"] is False + assert retention["stdout_included"] is False + assert retention["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_no_write_ledger_retention_proof" + ] + is True + ) + assert contract["ledger_retention_write_allowed"] is False + assert contract["ledger_retention_written"] is False + assert contract["ledger_write_allowed"] is False + assert contract["ledger_written"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_no_write_ledger_retention_now"] is False + assert contract["executes_database_apply"] is False + assert contract["database_apply_authorized"] is False + assert "no_write_ledger_retention_proof_bound" in check_keys + assert "no_write_ledger_retention_proof_blocks_persistence" in check_keys + assert "preview_has_no_side_effects_no_retention_no_ledger_no_storage_no_persistence_no_execution_no_signing" in check_keys + assert closeout["safety"]["persists_verifier_receipt"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + +def test_auto_policy_db_apply_controlled_dry_run_no_write_ledger_retention_proof_closeout_waits_without_ready_archive(): + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_no_write_ledger_retention_proof_closeout( + _payload(), + batch_size=1, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof" + ] + archive_closeout = closeout[ + "controlled_dry_run_no_write_ledger_retention_proof_closeout" + ] + archive = archive_closeout["retention_boundary_no_write_archive_proof"] + contract = closeout[ + "controlled_dry_run_no_write_ledger_retention_proof_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_no_write_ledger_retention_proof_closeout_checks" + ] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_no_write_ledger_retention_proof_closeout" + ) + assert closeout["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_STORAGE_BOUNDARY_NO_WRITE_LEDGER_PROOF_CLOSEOUT" + ) + assert closeout["summary"]["controlled_dry_run_no_write_ledger_retention_proof_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_no_write_ledger_retention_proof_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_no_write_ledger_retention_proof_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_no_write_ledger_retention_proof_closeout_field_count"] == 12 + assert closeout["summary"]["controlled_dry_run_no_write_ledger_retention_proof_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_ready_count"] == 0 + assert closeout["summary"]["retention_boundary_no_write_archive_proof_count"] == 1 + assert closeout["summary"]["retention_boundary_no_write_archive_proof_field_count"] == 12 + assert closeout["summary"]["retention_archive_write_locked_count"] == 1 + assert closeout["summary"]["retention_archive_write_allowed_count"] == 0 + assert closeout["summary"]["retention_archive_written_count"] == 0 + assert closeout["summary"]["ledger_retention_write_allowed_count"] == 0 + assert closeout["summary"]["ledger_retention_written_count"] == 0 + assert closeout["summary"]["ledger_write_allowed_count"] == 0 + assert closeout["summary"]["ledger_written_count"] == 0 + assert closeout["summary"]["persists_verifier_receipt_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert future["retention_boundary_no_write_archive_proof_id"].endswith( + "-retention-boundary-no-write-archive-proof" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof" + ] + is False + ) + assert future["retention_archive_write_locked"] is True + assert future["retention_archive_write_allowed"] is False + assert future["retention_archive_written"] is False + assert future["ledger_retention_write_allowed"] is False + assert future["ledger_retention_written"] is False + assert future["ledger_write_allowed"] is False + assert future["ledger_written"] is False + assert future["persists_verifier_receipt"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["database_apply_authorized"] is False + assert archive_closeout["no_write_ledger_retention_proof_closeout_only"] is True + assert archive_closeout["retention_boundary_no_write_archive_proof_only"] is True + assert archive_closeout["retention_boundary_no_write_archive_proof_count"] == 1 + assert archive_closeout["retention_boundary_no_write_archive_proof_field_count"] == 12 + assert archive_closeout["retention_archive_write_locked"] is True + assert archive_closeout["retention_archive_write_allowed"] is False + assert archive_closeout["retention_archive_written"] is False + assert archive_closeout["ledger_retention_write_allowed"] is False + assert archive_closeout["ledger_retention_written"] is False + assert archive_closeout["persists_verifier_receipt"] is False + assert archive_closeout["endpoint_executed"] is False + assert archive_closeout["sql_executed"] is False + assert archive_closeout["database_written"] is False + assert archive["archive_status"] == "retention_boundary_no_write_archive_proof_preview_ready" + assert archive["archive_mode"] == "retention_boundary_no_write_archive_proof_preview_only" + assert archive["retention_archive_write_locked"] is True + assert archive["retention_archive_write_allowed"] is False + assert archive["retention_archive_written"] is False + assert archive["ledger_retention_write_allowed"] is False + assert archive["ledger_retention_written"] is False + assert archive["ledger_write_allowed"] is False + assert archive["ledger_written"] is False + assert archive["persists_verifier_receipt"] is False + assert archive["endpoint_executed"] is False + assert archive["sql_executed"] is False + assert archive["database_written"] is False + assert archive["database_apply_authorized"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof" + ] + is False + ) + assert contract["retention_archive_write_allowed"] is False + assert contract["ledger_retention_write_allowed"] is False + assert contract["persists_verifier_receipt"] is False + assert contract["executes_database_apply"] is False + assert contract["database_apply_authorized"] is False + assert "storage_boundary_no_write_ledger_proof_closeout_ready" in check_keys + assert "no_write_ledger_retention_proof_ready" in check_keys + assert "no_write_ledger_retention_proof_no_write" in check_keys + assert "retention_boundary_no_write_archive_proof_bound" in check_keys + assert "retention_boundary_no_write_archive_proof_blocks_archive" in check_keys + assert "preview_has_no_side_effects_no_archive_no_retention_no_ledger_no_storage_no_persistence_no_execution_no_signing" in check_keys + assert closeout["safety"]["persists_verifier_receipt"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_no_write_ledger_retention_proof_closeout_ready_after_fake_fetch_but_archive_write_is_locked(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_no_write_ledger_retention_proof_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof" + ] + archive_closeout = closeout[ + "controlled_dry_run_no_write_ledger_retention_proof_closeout" + ] + source_closeout = archive_closeout[ + "storage_boundary_no_write_ledger_proof_closeout" + ] + retention = archive_closeout["no_write_ledger_retention_proof"] + archive = archive_closeout["retention_boundary_no_write_archive_proof"] + contract = closeout[ + "controlled_dry_run_no_write_ledger_retention_proof_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_no_write_ledger_retention_proof_closeout_checks" + ] + ] + assert closeout["result"] == ( + "DB_APPLY_CONTROLLED_DRY_RUN_NO_WRITE_LEDGER_RETENTION_PROOF_CLOSEOUT_READY" + ) + assert closeout["summary"]["controlled_dry_run_no_write_ledger_retention_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_no_write_ledger_retention_proof_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_no_write_ledger_retention_proof_closeout_pass_count"] == 12 + assert closeout["summary"]["controlled_dry_run_no_write_ledger_retention_proof_closeout_waiting_count"] == 0 + assert closeout["summary"]["controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["retention_boundary_no_write_archive_proof_count"] == 1 + assert closeout["summary"]["retention_boundary_no_write_archive_proof_field_count"] == 12 + assert closeout["summary"]["retention_archive_write_locked_count"] == 1 + assert closeout["summary"]["retention_archive_write_allowed_count"] == 0 + assert closeout["summary"]["retention_archive_written_count"] == 0 + assert closeout["summary"]["ledger_retention_write_allowed_count"] == 0 + assert closeout["summary"]["ledger_retention_written_count"] == 0 + assert closeout["summary"]["ledger_write_allowed_count"] == 0 + assert closeout["summary"]["ledger_written_count"] == 0 + assert closeout["summary"]["persists_verifier_receipt_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout" + ] + is True + ) + assert future["no_write_ledger_retention_proof_closeout_ready"] is True + assert future["storage_boundary_no_write_ledger_proof_closeout_ready"] is True + assert future["no_write_ledger_retention_proof_ready"] is True + assert future["retention_boundary_no_write_archive_proof_bound"] is True + assert future["retention_archive_write_locked"] is True + assert future["retention_archive_write_allowed"] is False + assert future["retention_archive_written"] is False + assert future["ledger_retention_write_allowed"] is False + assert future["ledger_retention_written"] is False + assert future["ledger_write_allowed"] is False + assert future["ledger_written"] is False + assert future["persists_verifier_receipt"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["database_apply_authorized"] is False + assert archive_closeout["no_write_ledger_retention_proof_closeout_field_count"] == 12 + assert archive_closeout["no_write_ledger_retention_proof_closeout_acceptance_gate_count"] == 10 + assert archive_closeout["no_write_ledger_retention_proof_closeout_only"] is True + assert archive_closeout["retention_boundary_no_write_archive_proof_only"] is True + assert archive_closeout["source_storage_boundary_no_write_ledger_proof_closeout_id"] == source_closeout["storage_boundary_no_write_ledger_proof_closeout_id"] + assert archive_closeout["source_no_write_ledger_retention_proof_id"] == retention["retention_proof_id"] + assert archive["source_no_write_ledger_retention_proof_closeout_id"] == archive_closeout["no_write_ledger_retention_proof_closeout_id"] + assert archive["source_no_write_ledger_retention_proof_id"] == retention["retention_proof_id"] + assert archive["source_storage_boundary_no_write_ledger_proof_closeout_id"] == source_closeout["storage_boundary_no_write_ledger_proof_closeout_id"] + assert archive["required_command_shape_hash"] == retention["required_command_shape_hash"] + assert archive["archive_status"] == "retention_boundary_no_write_archive_proof_preview_ready" + assert archive["archive_mode"] == "retention_boundary_no_write_archive_proof_preview_only" + assert archive["retention_archive_write_locked"] is True + assert archive["retention_archive_write_allowed"] is False + assert archive["retention_archive_written"] is False + assert archive["archive_receipt_present"] is False + assert archive["archive_receipt_required"] is False + assert archive["ledger_retention_write_allowed"] is False + assert archive["ledger_retention_written"] is False + assert archive["ledger_write_allowed"] is False + assert archive["ledger_written"] is False + assert archive["receipt_persistence_storage_write_allowed"] is False + assert archive["receipt_persistence_storage_written"] is False + assert archive["verifier_receipt_persistence_allowed"] is False + assert archive["verifier_receipt_persisted"] is False + assert archive["persists_verifier_receipt"] is False + assert archive["verifier_invoked"] is False + assert archive["dry_run_executor_invoked"] is False + assert archive["runner_invocation_performed"] is False + assert archive["endpoint_executed"] is False + assert archive["sql_executed"] is False + assert archive["database_written"] is False + assert archive["ready_for_database_apply_now"] is False + assert archive["ready_for_retention_boundary_archive_now"] is False + assert archive["endpoint_execution_allowed"] is False + assert archive["sql_execution_allowed"] is False + assert archive["database_write_allowed"] is False + assert archive["database_apply_authorized"] is False + assert archive["stdout_included"] is False + assert archive["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof" + ] + is True + ) + assert contract["retention_archive_write_allowed"] is False + assert contract["ledger_retention_write_allowed"] is False + assert contract["ledger_write_allowed"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_retention_boundary_archive_now"] is False + assert contract["executes_database_apply"] is False + assert contract["database_apply_authorized"] is False + assert "retention_boundary_no_write_archive_proof_bound" in check_keys + assert "retention_boundary_no_write_archive_proof_blocks_archive" in check_keys + assert "preview_has_no_side_effects_no_archive_no_retention_no_ledger_no_storage_no_persistence_no_execution_no_signing" in check_keys + assert closeout["safety"]["persists_verifier_receipt"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_waits_without_ready_handoff(): + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout( + _payload(), + batch_size=1, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof" + ] + handoff_closeout = closeout[ + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout" + ] + handoff = handoff_closeout["archive_retention_sealed_handoff_proof"] + contract = closeout[ + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_checks" + ] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout" + ) + assert closeout["result"] == ( + "WAITING_FOR_DB_APPLY_CONTROLLED_DRY_RUN_NO_WRITE_LEDGER_RETENTION_PROOF_CLOSEOUT" + ) + assert closeout["summary"]["controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_ready_count"] == 0 + assert closeout["summary"]["controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_count"] == 1 + assert closeout["summary"]["controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_field_count"] == 12 + assert closeout["summary"]["controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_acceptance_gate_count"] == 10 + assert closeout["summary"]["controlled_dry_run_no_write_ledger_retention_proof_closeout_ready_count"] == 0 + assert closeout["summary"]["archive_retention_sealed_handoff_proof_count"] == 1 + assert closeout["summary"]["archive_retention_sealed_handoff_proof_field_count"] == 12 + assert closeout["summary"]["sealed_handoff_write_locked_count"] == 1 + assert closeout["summary"]["sealed_handoff_write_allowed_count"] == 0 + assert closeout["summary"]["sealed_handoff_written_count"] == 0 + assert closeout["summary"]["retention_archive_write_allowed_count"] == 0 + assert closeout["summary"]["retention_archive_written_count"] == 0 + assert closeout["summary"]["ledger_retention_write_allowed_count"] == 0 + assert closeout["summary"]["ledger_retention_written_count"] == 0 + assert closeout["summary"]["ledger_write_allowed_count"] == 0 + assert closeout["summary"]["ledger_written_count"] == 0 + assert closeout["summary"]["persists_verifier_receipt_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert future["archive_retention_sealed_handoff_proof_id"].endswith( + "-archive-retention-sealed-handoff-proof" + ) + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof" + ] + is False + ) + assert future["sealed_handoff_write_locked"] is True + assert future["sealed_handoff_write_allowed"] is False + assert future["sealed_handoff_written"] is False + assert future["retention_archive_write_allowed"] is False + assert future["retention_archive_written"] is False + assert future["ledger_retention_write_allowed"] is False + assert future["ledger_retention_written"] is False + assert future["ledger_write_allowed"] is False + assert future["ledger_written"] is False + assert future["persists_verifier_receipt"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["database_apply_authorized"] is False + assert handoff_closeout["retention_boundary_no_write_archive_proof_closeout_only"] is True + assert handoff_closeout["archive_retention_sealed_handoff_proof_only"] is True + assert handoff_closeout["archive_retention_sealed_handoff_proof_count"] == 1 + assert handoff_closeout["archive_retention_sealed_handoff_proof_field_count"] == 12 + assert handoff_closeout["sealed_handoff_write_locked"] is True + assert handoff_closeout["sealed_handoff_write_allowed"] is False + assert handoff_closeout["sealed_handoff_written"] is False + assert handoff_closeout["retention_archive_write_allowed"] is False + assert handoff_closeout["retention_archive_written"] is False + assert handoff_closeout["ledger_retention_write_allowed"] is False + assert handoff_closeout["ledger_retention_written"] is False + assert handoff_closeout["persists_verifier_receipt"] is False + assert handoff_closeout["endpoint_executed"] is False + assert handoff_closeout["sql_executed"] is False + assert handoff_closeout["database_written"] is False + assert handoff["handoff_status"] == "archive_retention_sealed_handoff_proof_preview_ready" + assert handoff["handoff_mode"] == "archive_retention_sealed_handoff_proof_preview_only" + assert len(handoff["sealed_handoff_manifest_hash"]) == 64 + assert handoff["sealed_handoff_write_locked"] is True + assert handoff["sealed_handoff_write_allowed"] is False + assert handoff["sealed_handoff_written"] is False + assert handoff["retention_archive_write_allowed"] is False + assert handoff["retention_archive_written"] is False + assert handoff["ledger_retention_write_allowed"] is False + assert handoff["ledger_retention_written"] is False + assert handoff["ledger_write_allowed"] is False + assert handoff["ledger_written"] is False + assert handoff["persists_verifier_receipt"] is False + assert handoff["endpoint_executed"] is False + assert handoff["sql_executed"] is False + assert handoff["database_written"] is False + assert handoff["database_apply_authorized"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof" + ] + is False + ) + assert contract["sealed_handoff_write_allowed"] is False + assert contract["retention_archive_write_allowed"] is False + assert contract["ledger_retention_write_allowed"] is False + assert contract["persists_verifier_receipt"] is False + assert contract["executes_database_apply"] is False + assert contract["database_apply_authorized"] is False + assert "no_write_ledger_retention_proof_closeout_ready" in check_keys + assert "retention_boundary_no_write_archive_proof_ready" in check_keys + assert "archive_retention_sealed_handoff_proof_bound" in check_keys + assert "archive_retention_sealed_handoff_proof_blocks_handoff_write" in check_keys + assert "sealed_handoff_has_nonsecret_machine_readable_manifest" in check_keys + assert "preview_has_no_side_effects_no_handoff_no_archive_no_retention_no_ledger_no_storage_no_persistence_no_execution_no_signing" in check_keys + assert closeout["safety"]["persists_verifier_receipt"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_ready_after_fake_fetch_but_sealed_handoff_write_is_locked(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof" + ] + handoff_closeout = closeout[ + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout" + ] + source_closeout = handoff_closeout[ + "no_write_ledger_retention_proof_closeout" + ] + archive = handoff_closeout["retention_boundary_no_write_archive_proof"] + handoff = handoff_closeout["archive_retention_sealed_handoff_proof"] + contract = closeout[ + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_checks" + ] + ] + assert closeout["result"] == ( + "DB_APPLY_CONTROLLED_DRY_RUN_RETENTION_BOUNDARY_NO_WRITE_ARCHIVE_PROOF_CLOSEOUT_READY" + ) + assert closeout["summary"]["controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_pass_count"] == 12 + assert closeout["summary"]["controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_waiting_count"] == 0 + assert closeout["summary"]["controlled_dry_run_no_write_ledger_retention_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["archive_retention_sealed_handoff_proof_count"] == 1 + assert closeout["summary"]["archive_retention_sealed_handoff_proof_field_count"] == 12 + assert closeout["summary"]["sealed_handoff_write_locked_count"] == 1 + assert closeout["summary"]["sealed_handoff_write_allowed_count"] == 0 + assert closeout["summary"]["sealed_handoff_written_count"] == 0 + assert closeout["summary"]["retention_archive_write_allowed_count"] == 0 + assert closeout["summary"]["retention_archive_written_count"] == 0 + assert closeout["summary"]["ledger_retention_write_allowed_count"] == 0 + assert closeout["summary"]["ledger_retention_written_count"] == 0 + assert closeout["summary"]["ledger_write_allowed_count"] == 0 + assert closeout["summary"]["ledger_written_count"] == 0 + assert closeout["summary"]["persists_verifier_receipt_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof_closeout" + ] + is True + ) + assert future["retention_boundary_no_write_archive_proof_closeout_ready"] is True + assert future["no_write_ledger_retention_proof_closeout_ready"] is True + assert future["retention_boundary_no_write_archive_proof_ready"] is True + assert future["archive_retention_sealed_handoff_proof_bound"] is True + assert future["sealed_handoff_write_locked"] is True + assert future["sealed_handoff_write_allowed"] is False + assert future["sealed_handoff_written"] is False + assert future["retention_archive_write_allowed"] is False + assert future["retention_archive_written"] is False + assert future["ledger_retention_write_allowed"] is False + assert future["ledger_retention_written"] is False + assert future["ledger_write_allowed"] is False + assert future["ledger_written"] is False + assert future["persists_verifier_receipt"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["database_apply_authorized"] is False + assert handoff_closeout["retention_boundary_no_write_archive_proof_closeout_field_count"] == 12 + assert handoff_closeout["retention_boundary_no_write_archive_proof_closeout_acceptance_gate_count"] == 10 + assert handoff_closeout["retention_boundary_no_write_archive_proof_closeout_only"] is True + assert handoff_closeout["archive_retention_sealed_handoff_proof_only"] is True + assert handoff_closeout["source_no_write_ledger_retention_proof_closeout_id"] == source_closeout["no_write_ledger_retention_proof_closeout_id"] + assert handoff_closeout["source_retention_boundary_no_write_archive_proof_id"] == archive["archive_proof_id"] + assert handoff["source_no_write_ledger_retention_proof_closeout_id"] == source_closeout["no_write_ledger_retention_proof_closeout_id"] + assert handoff["source_retention_boundary_no_write_archive_proof_id"] == archive["archive_proof_id"] + assert handoff["sealed_handoff_manifest"]["source_retention_boundary_no_write_archive_proof_id"] == archive["archive_proof_id"] + assert len(handoff["sealed_handoff_manifest_hash"]) == 64 + assert handoff["handoff_status"] == "archive_retention_sealed_handoff_proof_preview_ready" + assert handoff["handoff_mode"] == "archive_retention_sealed_handoff_proof_preview_only" + assert handoff["sealed_handoff_write_locked"] is True + assert handoff["sealed_handoff_write_allowed"] is False + assert handoff["sealed_handoff_written"] is False + assert handoff["retention_archive_write_allowed"] is False + assert handoff["retention_archive_written"] is False + assert handoff["ledger_retention_write_allowed"] is False + assert handoff["ledger_retention_written"] is False + assert handoff["ledger_write_allowed"] is False + assert handoff["ledger_written"] is False + assert handoff["persists_verifier_receipt"] is False + assert handoff["verifier_invoked"] is False + assert handoff["dry_run_executor_invoked"] is False + assert handoff["runner_invocation_performed"] is False + assert handoff["endpoint_executed"] is False + assert handoff["sql_executed"] is False + assert handoff["database_written"] is False + assert handoff["ready_for_database_apply_now"] is False + assert handoff["ready_for_archive_retention_sealed_handoff_write_now"] is False + assert handoff["endpoint_execution_allowed"] is False + assert handoff["sql_execution_allowed"] is False + assert handoff["database_write_allowed"] is False + assert handoff["database_apply_authorized"] is False + assert handoff["stdout_included"] is False + assert handoff["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof" + ] + is True + ) + assert contract["sealed_handoff_write_allowed"] is False + assert contract["retention_archive_write_allowed"] is False + assert contract["ledger_retention_write_allowed"] is False + assert contract["ledger_write_allowed"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_archive_retention_sealed_handoff_write_now"] is False + assert contract["executes_database_apply"] is False + assert contract["database_apply_authorized"] is False + assert "archive_retention_sealed_handoff_proof_bound" in check_keys + assert "archive_retention_sealed_handoff_proof_blocks_handoff_write" in check_keys + assert "sealed_handoff_has_nonsecret_machine_readable_manifest" in check_keys + assert "preview_has_no_side_effects_no_handoff_no_archive_no_retention_no_ledger_no_storage_no_persistence_no_execution_no_signing" in check_keys + assert closeout["safety"]["persists_verifier_receipt"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_ready_after_fake_fetch_but_verifier_transfer_is_locked(): + class FakeResponse: + status_code = 200 + encoding = "utf-8" + content = b""" + + + + """ + + def fake_get(url, timeout, headers): + return FakeResponse() + + closeout = ( + build_pchome_auto_policy_db_apply_controlled_dry_run_archive_retention_sealed_handoff_proof_closeout( + _payload(), + batch_size=1, + execute_fetch=True, + http_get=fake_get, + ) + ) + + future = closeout[ + "future_database_apply_controlled_dry_run_sealed_handoff_verifier_transfer_proof" + ] + transfer_closeout = closeout[ + "controlled_dry_run_archive_retention_sealed_handoff_proof_closeout" + ] + source_closeout = transfer_closeout[ + "retention_boundary_no_write_archive_proof_closeout" + ] + handoff = transfer_closeout["archive_retention_sealed_handoff_proof"] + transfer = transfer_closeout["sealed_handoff_verifier_transfer_proof"] + contract = closeout[ + "controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_contract" + ] + check_keys = [ + check["key"] + for check in closeout[ + "controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_checks" + ] + ] + assert closeout["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_archive_retention_sealed_handoff_proof_closeout" + ) + assert closeout["result"] == ( + "DB_APPLY_CONTROLLED_DRY_RUN_ARCHIVE_RETENTION_SEALED_HANDOFF_PROOF_CLOSEOUT_READY" + ) + assert closeout["summary"]["controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_check_count"] == 12 + assert closeout["summary"]["controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_pass_count"] == 12 + assert closeout["summary"]["controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_waiting_count"] == 0 + assert closeout["summary"]["controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_ready_count"] == 1 + assert closeout["summary"]["sealed_handoff_verifier_transfer_proof_count"] == 1 + assert closeout["summary"]["sealed_handoff_verifier_transfer_proof_field_count"] == 12 + assert closeout["summary"]["sealed_handoff_manifest_hash_locked_count"] == 1 + assert closeout["summary"]["verifier_transfer_write_locked_count"] == 1 + assert closeout["summary"]["verifier_transfer_write_allowed_count"] == 0 + assert closeout["summary"]["verifier_transfer_written_count"] == 0 + assert closeout["summary"]["persists_verifier_receipt_count"] == 0 + assert closeout["summary"]["verifier_invoked_count"] == 0 + assert closeout["summary"]["executes_endpoint_count"] == 0 + assert closeout["summary"]["executes_sql_count"] == 0 + assert closeout["summary"]["writes_database_count"] == 0 + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_sealed_handoff_verifier_transfer_proof" + ] + is True + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_sealed_handoff_verifier_transfer_proof_closeout" + ] + is True + ) + assert future["archive_retention_sealed_handoff_proof_closeout_ready"] is True + assert future["retention_boundary_no_write_archive_proof_closeout_ready"] is True + assert future["sealed_handoff_manifest_hash_locked"] is True + assert future["sealed_handoff_verifier_transfer_proof_bound"] is True + assert future["verifier_transfer_write_locked"] is True + assert future["verifier_transfer_write_allowed"] is False + assert future["verifier_transfer_written"] is False + assert future["verifier_invocation_allowed"] is False + assert future["verifier_invoked"] is False + assert future["persists_verifier_receipt"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["database_apply_authorized"] is False + assert transfer_closeout["archive_retention_sealed_handoff_proof_closeout_field_count"] == 12 + assert transfer_closeout["archive_retention_sealed_handoff_proof_closeout_acceptance_gate_count"] == 10 + assert transfer_closeout["archive_retention_sealed_handoff_proof_closeout_only"] is True + assert transfer_closeout["sealed_handoff_verifier_transfer_proof_only"] is True + assert transfer_closeout["source_retention_boundary_no_write_archive_proof_closeout_id"] == source_closeout["retention_boundary_no_write_archive_proof_closeout_id"] + assert transfer_closeout["source_archive_retention_sealed_handoff_proof_id"] == handoff["archive_retention_sealed_handoff_proof_id"] + assert transfer["source_retention_boundary_no_write_archive_proof_closeout_id"] == source_closeout["retention_boundary_no_write_archive_proof_closeout_id"] + assert transfer["source_archive_retention_sealed_handoff_proof_id"] == handoff["archive_retention_sealed_handoff_proof_id"] + assert len(transfer["sealed_handoff_manifest_hash"]) == 64 + assert len(transfer["verifier_transfer_manifest_hash"]) == 64 + assert transfer["verifier_transfer_status"] == "sealed_handoff_verifier_transfer_proof_preview_ready" + assert transfer["verifier_transfer_mode"] == "sealed_handoff_verifier_transfer_proof_preview_only" + assert transfer["verifier_transfer_write_locked"] is True + assert transfer["verifier_transfer_write_allowed"] is False + assert transfer["verifier_transfer_written"] is False + assert transfer["sealed_handoff_write_allowed"] is False + assert transfer["sealed_handoff_written"] is False + assert transfer["verifier_invocation_allowed"] is False + assert transfer["verifier_invoked"] is False + assert transfer["persists_verifier_receipt"] is False + assert transfer["endpoint_executed"] is False + assert transfer["sql_executed"] is False + assert transfer["database_written"] is False + assert transfer["ready_for_database_apply_now"] is False + assert transfer["ready_for_verifier_transfer_write_now"] is False + assert transfer["ready_for_verifier_invocation_now"] is False + assert transfer["database_apply_authorized"] is False + assert transfer["stdout_included"] is False + assert transfer["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_sealed_handoff_verifier_transfer_proof" + ] + is True + ) + assert contract["verifier_transfer_write_allowed"] is False + assert contract["verifier_invocation_allowed"] is False + assert contract["persists_verifier_receipt"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["executes_database_apply"] is False + assert contract["database_apply_authorized"] is False + assert "archive_retention_sealed_handoff_proof_ready" in check_keys + assert "sealed_handoff_manifest_hash_locked" in check_keys + assert "sealed_handoff_verifier_transfer_proof_bound" in check_keys + assert "sealed_handoff_verifier_transfer_blocks_verifier_invocation" in check_keys + assert "sealed_handoff_verifier_transfer_has_nonsecret_machine_readable_manifest" in check_keys + assert "preview_has_no_side_effects_no_handoff_no_verifier_no_receipt_no_execution_no_signing" in check_keys + assert closeout["safety"]["persists_verifier_receipt"] is False + assert closeout["safety"]["executes_endpoint"] is False + assert closeout["safety"]["executes_sql"] is False + assert closeout["safety"]["writes_database"] is False + assert closeout["safety"]["executes_database_apply"] is False + + +def test_build_report_keeps_production_report_policy(monkeypatch): + monkeypatch.setattr(report, "fetch_json", lambda url, timeout: _payload()) + monkeypatch.setattr( + report.version_guard, + "build_report", + lambda health_url, timeout: { + "production": {"status": "healthy", "version": "V10.725"}, + "local": {"config_version": "V10.725", "head_config_version": "V10.725"}, + "origin_main": {"matches_local_head": True}, + }, + ) + monkeypatch.setattr(report.version_guard, "evaluate", lambda guard_report, allow_local_version_drift: (True, [])) + + payload = report.build_report( + api_url="https://example.test/path", + limit=20, + timeout=1, + health_url="https://example.test/health", + skip_version_truth=False, + ) + + assert payload["policy"] == "read_only_production_pchome_mapping_backlog" + assert payload["result"] == "NEEDS_MAPPING" + + +def test_main_json_uses_build_report(monkeypatch, capsys): + monkeypatch.setattr( + report, + "build_report", + lambda **kwargs: { + "policy": "read_only_production_pchome_mapping_backlog", + "result": "PASS", + "api_url": "https://example.test/path?limit=20", + "stats": {"mapping_rate": 100.0}, + "backlog": {}, + "errors": [], + }, + ) + + exit_code = report.main(["--json"]) + payload = json.loads(capsys.readouterr().out) + + assert exit_code == 0 + assert payload["policy"] == "read_only_production_pchome_mapping_backlog" + assert payload["stats"]["mapping_rate"] == 100.0 + + +def test_blocked_report_exits_nonzero(monkeypatch, capsys): + monkeypatch.setattr( + report, + "build_report", + lambda **kwargs: { + "policy": "read_only_production_pchome_mapping_backlog", + "result": "BLOCKED", + "api_url": "https://example.test/path?limit=20", + "errors": ["version drift"], + }, + ) + + exit_code = report.main([]) + + assert exit_code == 1 + assert "version drift" in capsys.readouterr().out + + +def test_mapping_backlog_route_uses_cached_growth_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached mapping backlog should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context("/api/ai/pchome-growth/mapping-backlog?limit=20"): + response = routes.api_pchome_growth_mapping_backlog.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_mapping_backlog" + assert payload["source_endpoint"] == "/api/ai/pchome-growth/opportunities" + assert payload["backlog"]["direct_mapping_count"] == 2 + assert payload["backlog"]["review_candidate_count"] == 1 + + +def test_operator_preview_route_uses_cached_growth_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached operator preview should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context("/api/ai/pchome-growth/mapping-backlog/operator-preview?batch_size=1"): + response = routes.api_pchome_growth_mapping_operator_preview.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_mapping_operator_preview" + assert payload["source_endpoint"] == "/api/ai/pchome-growth/mapping-backlog" + assert payload["operator_batch"]["selected_direct_mapping_count"] == 1 + assert payload["safety"]["writes_database"] is False + + +def test_direct_mapping_auto_search_package_route_defaults_to_no_search_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached direct mapping auto search package should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/direct-mapping-auto-search-package?batch_size=1" + ): + response = routes.api_pchome_growth_direct_mapping_auto_search_package.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_direct_mapping_auto_search_package" + assert payload["source_endpoint"] == "/api/ai/pchome-growth/mapping-backlog/operator-preview" + assert payload["summary"]["selected_direct_mapping_count"] == 1 + assert payload["summary"]["search_ready_target_count"] == 1 + assert payload["summary"]["execute_search_count"] == 0 + assert payload["search_execution"]["executed"] is False + assert payload["search_execution"]["writes_database"] is False + assert payload["search_package"]["targets"][0]["pchome_product_id"] == "PCH-2" + assert payload["safety"]["executes_search"] is False + assert payload["safety"]["writes_database"] is False + + +def test_direct_mapping_candidate_decision_package_route_defaults_to_no_search_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached direct mapping candidate decision package should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/direct-mapping-candidate-decision-package?batch_size=1" + ): + response = routes.api_pchome_growth_direct_mapping_candidate_decision_package.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_direct_mapping_candidate_decision_package" + assert payload["source_endpoint"] == "/api/ai/pchome-growth/mapping-backlog/direct-mapping-auto-search-package" + assert payload["result"] == "WAITING_FOR_DIRECT_MAPPING_CANDIDATES" + assert payload["summary"]["selected_direct_mapping_count"] == 1 + assert payload["summary"]["candidate_decision_count"] == 0 + assert payload["decision_package"]["manual_review_mode"] == "exception_only" + assert payload["safety"]["executes_search"] is False + assert payload["safety"]["writes_database"] is False + + +def test_ai_automation_readiness_route_defaults_to_no_search_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached AI automation readiness should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context("/api/ai/pchome-growth/ai-automation-readiness?batch_size=1"): + response = routes.api_pchome_growth_ai_automation_readiness.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_ai_automation_readiness" + assert payload["source_endpoint"] == "/api/ai/pchome-growth/opportunities" + assert payload["summary"]["primary_human_gate_count"] == 0 + assert payload["automation_policy"]["primary_flow"] == "ai_controlled" + assert payload["ai_exception_auto_resolution"]["mode"] == "machine_verifiable_auto_resolution" + assert payload["summary"]["manual_required_as_primary_flow_count"] == 0 + assert payload["manual_policy"]["manual_review_mode"] == "exception_only" + assert payload["safety"]["executes_search"] is False + assert payload["safety"]["executes_fetch"] is False + assert payload["safety"]["writes_database"] is False + + +def test_evidence_enrichment_route_uses_cached_growth_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached evidence enrichment preview should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context("/api/ai/pchome-growth/mapping-backlog/evidence-enrichment-preview?batch_size=1"): + response = routes.api_pchome_growth_evidence_enrichment_preview.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_evidence_enrichment_preview" + assert payload["source_endpoint"] == "/api/ai/pchome-growth/mapping-backlog/operator-preview" + assert payload["summary"]["missing_field_counts"]["image"] == 2 + assert payload["safety"]["fetches_external_sites"] is False + + +def test_evidence_source_preview_route_uses_cached_growth_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached evidence source preview should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context("/api/ai/pchome-growth/mapping-backlog/evidence-source-preview?batch_size=1"): + response = routes.api_pchome_growth_evidence_source_preview.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_evidence_source_preview" + assert payload["source_endpoint"] == "/api/ai/pchome-growth/mapping-backlog/evidence-enrichment-preview" + assert payload["summary"]["field_counts"]["image"]["missing_count"] == 2 + assert payload["safety"]["fetches_external_sites"] is False + + +def test_evidence_fetch_gate_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached evidence fetch gate should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context("/api/ai/pchome-growth/mapping-backlog/evidence-fetch-gate?batch_size=1"): + response = routes.api_pchome_growth_evidence_fetch_gate.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "controlled_read_only_pchome_product_page_evidence_fetch_gate" + assert payload["source_endpoint"] == "/api/ai/pchome-growth/mapping-backlog/evidence-source-preview" + assert payload["fetch_config"]["execute_fetch"] is False + assert payload["summary"]["executed_fetch_count"] == 0 + assert payload["safety"]["writes_database"] is False + + +def test_evidence_merge_preview_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached evidence merge preview should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context("/api/ai/pchome-growth/mapping-backlog/evidence-merge-preview?batch_size=1"): + response = routes.api_pchome_growth_evidence_merge_preview.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_evidence_merge_preview" + assert payload["source_endpoint"] == "/api/ai/pchome-growth/mapping-backlog/evidence-fetch-gate" + assert payload["summary"]["executed_fetch_count"] == 0 + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_receipt_gate_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy receipt gate should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context("/api/ai/pchome-growth/mapping-backlog/auto-policy-receipt-gate?batch_size=1"): + response = routes.api_pchome_growth_auto_policy_receipt_gate.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_auto_policy_receipt_gate" + assert payload["source_endpoint"] == "/api/ai/pchome-growth/mapping-backlog/evidence-merge-preview" + assert payload["summary"]["persists_receipt_count"] == 0 + assert payload["safety"]["writes_database"] is False + assert payload["safety"]["persists_receipt"] is False + + +def test_auto_policy_persistence_gate_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy persistence gate should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context("/api/ai/pchome-growth/mapping-backlog/auto-policy-persistence-gate?batch_size=1"): + response = routes.api_pchome_growth_auto_policy_persistence_gate.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_auto_policy_persistence_gate" + assert payload["source_endpoint"] == "/api/ai/pchome-growth/mapping-backlog/auto-policy-receipt-gate" + assert payload["summary"]["writes_database_count"] == 0 + assert payload["summary"]["persists_receipt_count"] == 0 + assert payload["apply_gate"]["mode"] == "dry_run_only" + assert payload["safety"]["writes_database"] is False + assert payload["safety"]["persists_receipt"] is False + + +def test_auto_policy_schema_migration_preview_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy schema migration preview should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-schema-migration-preview?batch_size=1" + ): + response = routes.api_pchome_growth_auto_policy_schema_migration_preview.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_auto_policy_schema_migration_preview" + assert payload["source_endpoint"] == "/api/ai/pchome-growth/mapping-backlog/auto-policy-persistence-gate" + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["future_apply_gate"]["current_preview_apply_allowed"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_migration_file_preview_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy migration file preview should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-file-preview?batch_size=1" + ): + response = routes.api_pchome_growth_auto_policy_migration_file_preview.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_auto_policy_migration_file_preview" + assert payload["source_endpoint"] == "/api/ai/pchome-growth/mapping-backlog/auto-policy-schema-migration-preview" + assert payload["summary"]["writes_file_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["migration_file_preview"]["writes_file"] is False + assert payload["future_apply_endpoint_verifier"]["executes_endpoint"] is False + assert payload["safety"]["writes_file"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_apply_readiness_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy apply readiness closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-apply-readiness-closeout?batch_size=1" + ): + response = routes.api_pchome_growth_auto_policy_apply_readiness_closeout.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_auto_policy_apply_readiness_closeout" + assert payload["source_endpoint"] == "/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-file-preview" + assert payload["summary"]["readiness_check_count"] == 9 + assert payload["closeout"]["ready_for_database_apply"] is False + assert payload["summary"]["writes_file_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["writes_file"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_migration_file_generation_request_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy migration file generation request should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-file-generation-request?batch_size=1" + ): + response = routes.api_pchome_growth_auto_policy_migration_file_generation_request.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_auto_policy_migration_file_generation_request" + assert payload["source_endpoint"] == "/api/ai/pchome-growth/mapping-backlog/auto-policy-apply-readiness-closeout" + assert payload["summary"]["required_artifact_count"] == 4 + assert payload["file_generation_request"]["ready_for_database_apply"] is False + assert payload["summary"]["writes_file_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["writes_file"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_migration_apply_gate_preview_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy migration apply gate preview should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-apply-gate-preview?batch_size=1" + ): + response = routes.api_pchome_growth_auto_policy_migration_apply_gate_preview.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_auto_policy_migration_apply_gate_preview" + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-file-generation-request" + ) + assert payload["summary"]["generated_file_exists_count"] == 1 + assert payload["summary"]["generated_file_hash_matches_count"] == 1 + assert payload["apply_gate"]["ready_for_database_apply_now"] is False + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_request_gate_preview_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply request gate preview should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-request-gate-preview?batch_size=1" + ): + response = routes.api_pchome_growth_auto_policy_db_apply_request_gate_preview.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_auto_policy_db_apply_request_gate_preview" + assert payload["source_endpoint"] == "/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-apply-gate-preview" + assert payload["summary"]["required_artifact_count"] == 5 + assert payload["db_apply_request_gate"]["ready_for_database_apply_now"] is False + assert payload["db_apply_request_gate"]["command_preview"]["reads_secret_in_preview"] is False + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_execution_preflight_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply execution preflight should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-execution-preflight?batch_size=1" + ): + response = routes.api_pchome_growth_auto_policy_db_apply_execution_preflight.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_auto_policy_db_apply_execution_preflight" + assert payload["source_endpoint"] == "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-request-gate-preview" + assert payload["summary"]["required_artifact_count"] == 6 + assert payload["summary"]["snapshot_plan_count"] == 5 + assert payload["summary"]["readback_plan_count"] == 6 + assert payload["execution_preflight"]["ready_for_database_apply_now"] is False + assert payload["execution_preflight"]["reads_secret_in_preview"] is False + assert payload["prewrite_snapshot_plan"]["writes_database"] is False + assert payload["post_apply_readback_plan"]["executes_sql_in_preview"] is False + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_package_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply authorization package should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-package?batch_size=1" + ): + response = routes.api_pchome_growth_auto_policy_db_apply_authorization_package.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_auto_policy_db_apply_authorization_package" + assert payload["source_endpoint"] == "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-execution-preflight" + assert payload["summary"]["authorization_check_count"] == 11 + assert payload["summary"]["freshness_requirement_count"] == 5 + assert payload["summary"]["manifest_step_count"] == 6 + assert payload["authorization_package"]["ready_for_database_apply_now"] is False + assert payload["authorization_package"]["reads_secret_in_preview"] is False + assert payload["machine_apply_manifest"]["writes_database"] is False + assert payload["verifier_bundle"]["executes_in_preview"] is False + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_verifier_artifact_preview_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply verifier artifact preview should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-verifier-artifact-preview?batch_size=1" + ): + response = routes.api_pchome_growth_auto_policy_db_apply_verifier_artifact_preview.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_auto_policy_db_apply_verifier_artifact_preview" + assert payload["source_endpoint"] == "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-package" + assert payload["summary"]["artifact_schema_count"] == 3 + assert payload["summary"]["artifact_generation_step_count"] == 5 + assert payload["summary"]["verifier_check_count"] == 15 + assert payload["artifact_preview"]["ready_for_database_apply_now"] is False + assert payload["artifact_preview"]["writes_artifact_in_preview"] is False + assert payload["artifact_generation_plan"]["writes_database"] is False + assert payload["verifier_manifest"]["writes_artifact_in_preview"] is False + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_final_handoff_package_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply final handoff package should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-final-handoff-package?batch_size=1" + ): + response = routes.api_pchome_growth_auto_policy_db_apply_final_handoff_package.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_auto_policy_db_apply_final_handoff_package" + assert payload["source_endpoint"] == "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-verifier-artifact-preview" + assert payload["summary"]["handoff_section_count"] == 6 + assert payload["summary"]["final_runbook_step_count"] == 7 + assert payload["summary"]["command_preview_count"] == 3 + assert payload["summary"]["abort_gate_count"] == 10 + assert payload["final_handoff_package"]["ready_for_database_apply_now"] is False + assert payload["final_handoff_package"]["reads_secret_in_preview"] is False + assert payload["final_runbook_manifest"]["writes_database"] is False + assert payload["command_previews"][1]["executes_in_preview"] is False + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_shell_preview_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run shell preview should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-shell-preview?batch_size=1" + ): + response = routes.api_pchome_growth_auto_policy_db_apply_controlled_dry_run_shell_preview.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_shell_preview" + assert payload["source_endpoint"] == "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-final-handoff-package" + assert payload["summary"]["shell_phase_count"] == 9 + assert payload["summary"]["shell_script_line_count"] == 10 + assert payload["summary"]["check_mode_required_check_count"] == 6 + assert payload["summary"]["rollback_hook_count"] == 3 + assert payload["controlled_dry_run_shell_preview"]["ready_for_database_apply_now"] is False + assert payload["controlled_dry_run_shell_preview"]["reads_secret_in_preview"] is False + assert payload["shell_script_preview"]["executes_script_in_preview"] is False + assert payload["check_mode_contract"]["writes_database"] is False + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_shell_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run shell closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-shell-closeout?batch_size=1" + ): + response = routes.api_pchome_growth_auto_policy_db_apply_controlled_dry_run_shell_closeout.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_shell_closeout" + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-shell-preview" + ) + assert payload["summary"]["closeout_check_count"] == 13 + assert payload["summary"]["future_apply_boundary_count"] == 6 + assert payload["explicit_authorization_boundary"]["ready_for_database_apply_now"] is False + assert payload["explicit_authorization_boundary"]["reads_secret_in_preview"] is False + assert payload["explicit_authorization_boundary"]["executes_shell_in_preview"] is False + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_request_intake_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply authorization request intake should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-request-intake?batch_size=1" + ): + response = routes.api_pchome_growth_auto_policy_db_apply_authorization_request_intake.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_auto_policy_db_apply_authorization_request_intake" + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-shell-closeout" + ) + assert payload["summary"]["required_request_evidence_count"] == 7 + assert payload["summary"]["request_payload_required_field_count"] == 10 + assert payload["summary"]["authorization_acceptance_gate_count"] == 11 + assert payload["authorization_request_intake"]["ready_for_database_apply_now"] is False + assert payload["authorization_request_intake"]["reads_secret_in_preview"] is False + assert payload["authorization_request_intake"]["executes_shell_in_preview"] is False + assert payload["authorization_envelope"]["issues_database_apply_authorization"] is False + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_request_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply authorization request closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-request-closeout?batch_size=1" + ): + response = routes.api_pchome_growth_auto_policy_db_apply_authorization_request_closeout.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_auto_policy_db_apply_authorization_request_closeout" + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-request-intake" + ) + assert payload["summary"]["closeout_check_count"] == 12 + assert payload["summary"]["exact_request_payload_field_count"] == 10 + assert payload["summary"]["machine_request_manifest_step_count"] == 6 + assert payload["final_exact_request_package"]["ready_for_database_apply_now"] is False + assert payload["final_exact_request_package"]["issues_database_apply_authorization"] is False + assert payload["final_exact_request_package"]["reads_secret_in_preview"] is False + assert payload["machine_request_manifest"]["writes_database"] is False + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_lane_guard_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply authorization lane guard should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-lane-guard?batch_size=1" + ): + response = routes.api_pchome_growth_auto_policy_db_apply_authorization_lane_guard.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_auto_policy_db_apply_authorization_lane_guard" + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-request-closeout" + ) + assert payload["summary"]["lane_guard_check_count"] == 12 + assert payload["summary"]["lane_entry_requirement_count"] == 6 + assert payload["summary"]["exact_request_payload_field_count"] == 10 + assert payload["summary"]["machine_request_manifest_step_count"] == 6 + assert payload["future_authorization_lane_guard"]["ready_for_database_apply_now"] is False + assert payload["future_authorization_lane_guard"]["issues_database_apply_authorization"] is False + assert payload["future_authorization_lane_guard"]["reads_secret_in_preview"] is False + assert payload["lane_transfer_contract"]["writes_database"] is False + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_decision_preflight_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply authorization decision preflight should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-decision-preflight?batch_size=1" + ): + response = routes.api_pchome_growth_auto_policy_db_apply_authorization_decision_preflight.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_auto_policy_db_apply_authorization_decision_preflight" + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-lane-guard" + ) + assert payload["summary"]["decision_preflight_check_count"] == 12 + assert payload["summary"]["decision_input_requirement_count"] == 8 + assert payload["summary"]["decision_rejection_reason_count"] == 10 + assert payload["future_authorization_decision_preflight"]["ready_for_database_apply_now"] is False + assert payload["future_authorization_decision_preflight"]["issues_database_apply_authorization"] is False + assert payload["future_authorization_decision_preflight"]["reads_secret_in_preview"] is False + assert payload["decision_preflight_envelope"]["requires_post_apply_verifier"] is True + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_decision_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply authorization decision closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-decision-closeout?batch_size=1" + ): + response = routes.api_pchome_growth_auto_policy_db_apply_authorization_decision_closeout.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_decision_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-decision-preflight" + ) + assert payload["summary"]["decision_closeout_check_count"] == 12 + assert payload["summary"]["decision_input_requirement_count"] == 8 + assert payload["summary"]["decision_rejection_reason_count"] == 10 + assert payload["future_authorization_decision_closeout"]["ready_for_database_apply_now"] is False + assert payload["future_authorization_decision_closeout"]["issues_database_apply_authorization"] is False + assert payload["future_authorization_decision_package"]["requires_post_apply_verifier"] is True + assert payload["future_authorization_decision_package"]["reads_secret_in_preview"] is False + assert payload["future_authorization_decision_package"]["writes_database_in_preview"] is False + assert payload["decision_closeout_contract"]["issues_database_apply_authorization"] is False + assert payload["decision_closeout_contract"]["ready_for_database_apply_now"] is False + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_issuer_gate_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply authorization issuer gate should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-issuer-gate?batch_size=1" + ): + response = routes.api_pchome_growth_auto_policy_db_apply_authorization_issuer_gate.__wrapped__() + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == "read_only_pchome_growth_auto_policy_db_apply_authorization_issuer_gate" + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-decision-closeout" + ) + assert payload["summary"]["issuer_gate_check_count"] == 12 + assert payload["summary"]["required_issuer_evidence_count"] == 9 + assert payload["summary"]["nonsecret_authorization_claim_count"] == 8 + assert payload["future_authorization_issuer_gate"]["ready_for_database_apply_now"] is False + assert payload["future_authorization_issuer_gate"]["issues_database_apply_authorization"] is False + assert payload["future_authorization_issuer_gate"]["signs_database_apply_authorization"] is False + assert payload["final_nonsecret_authorization_envelope"]["secret_material_included"] is False + assert payload["final_nonsecret_authorization_envelope"]["reads_secret_in_preview"] is False + assert payload["final_nonsecret_authorization_envelope"]["writes_database_in_preview"] is False + assert payload["issuer_gate_contract"]["issues_database_apply_authorization"] is False + assert payload["issuer_gate_contract"]["ready_for_database_apply_now"] is False + assert payload["issuer_gate_contract"]["signs_database_apply_authorization"] is False + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signing_decision_preflight_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply authorization signing decision preflight should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-decision-preflight?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_authorization_signing_decision_preflight + .__wrapped__() + ) + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signing_decision_preflight" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-issuer-gate" + ) + assert payload["summary"]["signing_decision_preflight_check_count"] == 12 + assert payload["summary"]["signing_decision_input_requirement_count"] == 10 + assert payload["summary"]["signing_decision_rejection_reason_count"] == 11 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + payload["future_authorization_signing_decision_preflight"]["ready_for_database_apply_now"] + is False + ) + assert ( + payload["future_authorization_signing_decision_preflight"][ + "issues_database_apply_authorization" + ] + is False + ) + assert ( + payload["future_authorization_signing_decision_preflight"][ + "signs_database_apply_authorization" + ] + is False + ) + assert payload["signing_decision_preflight_envelope"]["ready_for_database_apply_now"] is False + assert ( + payload["signing_decision_preflight_envelope"]["signs_database_apply_authorization"] + is False + ) + assert payload["signing_decision_preflight_envelope"]["secret_material_required_in_preview"] is False + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["signs_database_apply_authorization"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signing_issuer_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply authorization signing issuer closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-issuer-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_authorization_signing_issuer_closeout + .__wrapped__() + ) + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signing_issuer_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-issuer-guard" + ) + assert payload["summary"]["signing_issuer_closeout_check_count"] == 12 + assert payload["summary"]["signing_issuer_guard_check_count"] == 12 + assert payload["summary"]["signing_decision_input_requirement_count"] == 10 + assert payload["summary"]["signing_decision_rejection_reason_count"] == 11 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + payload["future_authorization_signing_issuer_closeout"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["future_authorization_signing_issuer_closeout"][ + "issues_database_apply_authorization" + ] + is False + ) + assert ( + payload["future_authorization_signing_issuer_closeout"][ + "signs_database_apply_authorization" + ] + is False + ) + assert payload["final_signable_request_package"]["ready_for_database_apply_now"] is False + assert ( + payload["final_signable_request_package"]["signs_database_apply_authorization"] + is False + ) + assert payload["final_signable_request_package"]["secret_material_included"] is False + assert ( + payload["final_signable_request_package"]["secret_material_required_in_preview"] + is False + ) + assert ( + payload["signing_issuer_closeout_contract"]["ready_for_database_apply_now"] + is False + ) + assert ( + payload["signing_issuer_closeout_contract"][ + "signs_database_apply_authorization" + ] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["signs_database_apply_authorization"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signing_execution_preflight_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply authorization signing execution preflight should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-execution-preflight?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_authorization_signing_execution_preflight + .__wrapped__() + ) + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signing_execution_preflight" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-issuer-closeout" + ) + assert payload["summary"]["signing_execution_preflight_check_count"] == 12 + assert payload["summary"]["signing_issuer_closeout_check_count"] == 12 + assert payload["summary"]["operator_held_secret_boundary_count"] == 1 + assert payload["summary"]["signing_execution_input_requirement_count"] == 10 + assert payload["summary"]["signing_execution_abort_condition_count"] == 8 + assert payload["summary"]["rollback_boundary_count"] == 4 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + payload["future_authorization_signing_execution_preflight"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["future_authorization_signing_execution_preflight"][ + "issues_database_apply_authorization" + ] + is False + ) + assert ( + payload["future_authorization_signing_execution_preflight"][ + "signs_database_apply_authorization" + ] + is False + ) + assert payload["signing_execution_preflight_package"]["ready_for_database_apply_now"] is False + assert ( + payload["signing_execution_preflight_package"]["signs_database_apply_authorization"] + is False + ) + assert payload["signing_execution_preflight_package"]["secret_material_included"] is False + assert ( + payload["signing_execution_preflight_package"]["secret_material_required_in_preview"] + is False + ) + assert ( + payload["operator_held_secret_boundary_contract"]["secret_reference_mode"] + == "external_runtime_reference_only" + ) + assert payload["operator_held_secret_boundary_contract"]["reads_secret_in_preview"] is False + assert ( + payload["operator_held_secret_boundary_contract"]["accepts_plaintext_secret"] + is False + ) + assert ( + payload["signing_execution_preflight_contract"]["ready_for_database_apply_now"] + is False + ) + assert ( + payload["signing_execution_preflight_contract"][ + "signs_database_apply_authorization" + ] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["signs_database_apply_authorization"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signing_execution_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply authorization signing execution closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-execution-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_authorization_signing_execution_closeout + .__wrapped__() + ) + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signing_execution_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-execution-preflight" + ) + assert payload["summary"]["signing_execution_closeout_check_count"] == 12 + assert payload["summary"]["signing_execution_preflight_check_count"] == 12 + assert payload["summary"]["unsigned_signed_authorization_receipt_boundary_count"] == 1 + assert payload["summary"]["operator_held_secret_boundary_count"] == 1 + assert payload["summary"]["signing_execution_input_requirement_count"] == 10 + assert payload["summary"]["signing_execution_abort_condition_count"] == 8 + assert payload["summary"]["rollback_boundary_count"] == 4 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + payload["future_authorization_signing_execution_closeout"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["future_authorization_signing_execution_closeout"][ + "issues_database_apply_authorization" + ] + is False + ) + assert ( + payload["future_authorization_signing_execution_closeout"][ + "signs_database_apply_authorization" + ] + is False + ) + assert ( + payload["unsigned_signed_authorization_receipt_boundary"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["unsigned_signed_authorization_receipt_boundary"][ + "signs_database_apply_authorization" + ] + is False + ) + assert ( + payload["unsigned_signed_authorization_receipt_boundary"][ + "signed_authorization_receipt_included" + ] + is False + ) + assert ( + payload["unsigned_signed_authorization_receipt_boundary"][ + "signature_material_included" + ] + is False + ) + assert ( + payload["unsigned_signed_authorization_receipt_boundary"][ + "secret_material_included" + ] + is False + ) + assert ( + payload["signing_execution_closeout_contract"]["ready_for_database_apply_now"] + is False + ) + assert ( + payload["signing_execution_closeout_contract"]["signs_database_apply_authorization"] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["signs_database_apply_authorization"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signed_receipt_preflight_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply authorization signed receipt preflight should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-preflight?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_authorization_signed_receipt_preflight + .__wrapped__() + ) + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signed_receipt_preflight" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-execution-closeout" + ) + assert payload["summary"]["signed_receipt_preflight_check_count"] == 12 + assert payload["summary"]["signing_execution_closeout_check_count"] == 12 + assert payload["summary"]["external_signing_receipt_evidence_boundary_count"] == 1 + assert payload["summary"]["required_external_receipt_evidence_count"] == 10 + assert payload["summary"]["external_receipt_acceptance_gate_count"] == 8 + assert payload["summary"]["operator_held_secret_boundary_count"] == 1 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + payload["future_authorization_signed_receipt_preflight"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["future_authorization_signed_receipt_preflight"][ + "issues_database_apply_authorization" + ] + is False + ) + assert ( + payload["future_authorization_signed_receipt_preflight"][ + "signs_database_apply_authorization" + ] + is False + ) + assert ( + payload["future_authorization_signed_receipt_preflight"][ + "signed_authorization_receipt_included" + ] + is False + ) + assert ( + payload["external_signing_receipt_evidence_boundary"][ + "external_signed_authorization_receipt_included" + ] + is False + ) + assert ( + payload["external_signing_receipt_evidence_boundary"][ + "signed_authorization_receipt_included" + ] + is False + ) + assert ( + payload["external_signing_receipt_evidence_boundary"]["signature_material_included"] + is False + ) + assert ( + payload["external_signing_receipt_evidence_boundary"]["secret_material_included"] + is False + ) + assert ( + payload["external_signing_receipt_evidence_boundary"]["ready_for_database_apply_now"] + is False + ) + assert ( + payload["signed_receipt_preflight_contract"]["ready_for_database_apply_now"] + is False + ) + assert ( + payload["signed_receipt_preflight_contract"]["signs_database_apply_authorization"] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["signs_database_apply_authorization"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signed_receipt_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply authorization signed receipt closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_authorization_signed_receipt_closeout + .__wrapped__() + ) + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signed_receipt_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-preflight" + ) + assert payload["summary"]["signed_receipt_closeout_check_count"] == 12 + assert payload["summary"]["signed_receipt_preflight_check_count"] == 12 + assert payload["summary"]["detached_receipt_verification_boundary_count"] == 1 + assert payload["summary"]["required_external_receipt_evidence_count"] == 10 + assert payload["summary"]["external_receipt_acceptance_gate_count"] == 8 + assert payload["summary"]["detached_receipt_verification_check_count"] == 10 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + payload["future_authorization_signed_receipt_closeout"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["future_authorization_signed_receipt_closeout"][ + "issues_database_apply_authorization" + ] + is False + ) + assert ( + payload["future_authorization_signed_receipt_closeout"][ + "signs_database_apply_authorization" + ] + is False + ) + assert ( + payload["future_authorization_signed_receipt_closeout"][ + "external_signed_authorization_receipt_included" + ] + is False + ) + assert ( + payload["detached_receipt_verification_boundary"][ + "detached_signature_verification_performed" + ] + is False + ) + assert ( + payload["detached_receipt_verification_boundary"][ + "external_signed_authorization_receipt_included" + ] + is False + ) + assert ( + payload["detached_receipt_verification_boundary"][ + "signed_authorization_receipt_included" + ] + is False + ) + assert ( + payload["detached_receipt_verification_boundary"]["signature_material_included"] + is False + ) + assert payload["detached_receipt_verification_boundary"]["secret_material_included"] is False + assert payload["detached_receipt_verification_boundary"]["ready_for_database_apply_now"] is False + assert payload["signed_receipt_closeout_contract"]["ready_for_database_apply_now"] is False + assert ( + payload["signed_receipt_closeout_contract"]["signs_database_apply_authorization"] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["signs_database_apply_authorization"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signed_receipt_evidence_intake_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply authorization signed receipt evidence intake should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-evidence-intake?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_authorization_signed_receipt_evidence_intake + .__wrapped__() + ) + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signed_receipt_evidence_intake" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-closeout" + ) + assert payload["summary"]["signed_receipt_evidence_intake_check_count"] == 12 + assert payload["summary"]["signed_receipt_closeout_check_count"] == 12 + assert payload["summary"]["detached_receipt_verification_boundary_count"] == 1 + assert payload["summary"]["detached_verification_evidence_schema_count"] == 1 + assert payload["summary"]["required_external_receipt_evidence_count"] == 10 + assert payload["summary"]["external_receipt_acceptance_gate_count"] == 8 + assert payload["summary"]["detached_receipt_verification_check_count"] == 10 + assert payload["summary"]["detached_verification_evidence_field_count"] == 12 + assert payload["summary"]["detached_verification_acceptance_gate_count"] == 10 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + payload["future_signed_authorization_receipt_evidence_intake"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["future_signed_authorization_receipt_evidence_intake"][ + "issues_database_apply_authorization" + ] + is False + ) + assert ( + payload["future_signed_authorization_receipt_evidence_intake"][ + "signs_database_apply_authorization" + ] + is False + ) + assert ( + payload["future_signed_authorization_receipt_evidence_intake"][ + "detached_signature_verification_performed" + ] + is False + ) + assert ( + payload["future_signed_authorization_receipt_evidence_intake"][ + "external_signed_authorization_receipt_included" + ] + is False + ) + assert ( + payload["detached_verification_evidence_schema"][ + "detached_signature_verification_performed" + ] + is False + ) + assert ( + payload["detached_verification_evidence_schema"][ + "external_signed_authorization_receipt_included" + ] + is False + ) + assert ( + payload["detached_verification_evidence_schema"][ + "signed_authorization_receipt_included" + ] + is False + ) + assert ( + payload["detached_verification_evidence_schema"]["signature_material_included"] + is False + ) + assert payload["detached_verification_evidence_schema"]["secret_material_included"] is False + assert payload["detached_verification_evidence_schema"]["accepts_plaintext_secret"] is False + assert payload["detached_verification_evidence_schema"]["ready_for_database_apply_now"] is False + assert payload["signed_receipt_evidence_intake_contract"]["ready_for_database_apply_now"] is False + assert ( + payload["signed_receipt_evidence_intake_contract"][ + "signs_database_apply_authorization" + ] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["performs_detached_signature_verification"] is False + assert payload["safety"]["signs_database_apply_authorization"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_detached_verification_evidence_validation_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply authorization detached verification evidence validation should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-detached-verification-evidence-validation?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_authorization_detached_verification_evidence_validation + .__wrapped__() + ) + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_detached_verification_evidence_validation" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-evidence-intake" + ) + assert payload["summary"]["detached_verification_evidence_validation_check_count"] == 12 + assert payload["summary"]["signed_receipt_evidence_intake_check_count"] == 12 + assert payload["summary"]["detached_verification_evidence_schema_count"] == 1 + assert payload["summary"]["verifier_receipt_closeout_boundary_count"] == 1 + assert payload["summary"]["required_external_receipt_evidence_count"] == 10 + assert payload["summary"]["external_receipt_acceptance_gate_count"] == 8 + assert payload["summary"]["detached_receipt_verification_check_count"] == 10 + assert payload["summary"]["detached_verification_evidence_field_count"] == 12 + assert payload["summary"]["detached_verification_acceptance_gate_count"] == 10 + assert payload["summary"]["verifier_receipt_field_count"] == 12 + assert payload["summary"]["verifier_receipt_acceptance_gate_count"] == 10 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + payload["future_detached_verification_evidence_validation"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["future_detached_verification_evidence_validation"][ + "issues_database_apply_authorization" + ] + is False + ) + assert ( + payload["future_detached_verification_evidence_validation"][ + "signs_database_apply_authorization" + ] + is False + ) + assert ( + payload["future_detached_verification_evidence_validation"][ + "detached_signature_verification_performed" + ] + is False + ) + assert ( + payload["future_detached_verification_evidence_validation"][ + "verifier_receipt_persisted" + ] + is False + ) + assert ( + payload["future_detached_verification_evidence_validation"][ + "external_signed_authorization_receipt_included" + ] + is False + ) + assert ( + payload["verifier_receipt_closeout_boundary"][ + "detached_signature_verification_performed" + ] + is False + ) + assert ( + payload["verifier_receipt_closeout_boundary"]["verifier_receipt_persisted"] + is False + ) + assert ( + payload["verifier_receipt_closeout_boundary"][ + "external_signed_authorization_receipt_included" + ] + is False + ) + assert ( + payload["verifier_receipt_closeout_boundary"][ + "signed_authorization_receipt_included" + ] + is False + ) + assert ( + payload["verifier_receipt_closeout_boundary"]["signature_material_included"] + is False + ) + assert payload["verifier_receipt_closeout_boundary"]["secret_material_included"] is False + assert payload["verifier_receipt_closeout_boundary"]["accepts_plaintext_secret"] is False + assert payload["verifier_receipt_closeout_boundary"]["ready_for_database_apply_now"] is False + assert ( + payload["detached_verification_evidence_validation_contract"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["detached_verification_evidence_validation_contract"][ + "signs_database_apply_authorization" + ] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["performs_detached_signature_verification"] is False + assert payload["safety"]["persists_verifier_receipt"] is False + assert payload["safety"]["signs_database_apply_authorization"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_verifier_receipt_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply authorization verifier receipt closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-verifier-receipt-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_authorization_verifier_receipt_closeout + .__wrapped__() + ) + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_verifier_receipt_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-detached-verification-evidence-validation" + ) + assert payload["summary"]["verifier_receipt_closeout_check_count"] == 12 + assert payload["summary"]["detached_verification_evidence_validation_check_count"] == 12 + assert payload["summary"]["verifier_receipt_closeout_boundary_count"] == 1 + assert payload["summary"]["verifier_receipt_evidence_handoff_count"] == 1 + assert payload["summary"]["required_external_receipt_evidence_count"] == 10 + assert payload["summary"]["external_receipt_acceptance_gate_count"] == 8 + assert payload["summary"]["verifier_receipt_field_count"] == 12 + assert payload["summary"]["verifier_receipt_acceptance_gate_count"] == 10 + assert payload["summary"]["verifier_receipt_evidence_handoff_field_count"] == 12 + assert payload["summary"]["verifier_receipt_handoff_acceptance_gate_count"] == 10 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + payload["future_verifier_receipt_closeout"]["ready_for_database_apply_now"] + is False + ) + assert ( + payload["future_verifier_receipt_closeout"]["issues_database_apply_authorization"] + is False + ) + assert ( + payload["future_verifier_receipt_closeout"]["signs_database_apply_authorization"] + is False + ) + assert ( + payload["future_verifier_receipt_closeout"][ + "detached_signature_verification_performed" + ] + is False + ) + assert payload["future_verifier_receipt_closeout"]["verifier_receipt_persisted"] is False + assert ( + payload["future_verifier_receipt_closeout"][ + "external_signed_authorization_receipt_included" + ] + is False + ) + assert ( + payload["verifier_receipt_evidence_handoff"][ + "detached_signature_verification_performed" + ] + is False + ) + assert payload["verifier_receipt_evidence_handoff"]["verifier_receipt_persisted"] is False + assert ( + payload["verifier_receipt_evidence_handoff"][ + "external_signed_authorization_receipt_included" + ] + is False + ) + assert ( + payload["verifier_receipt_evidence_handoff"][ + "signed_authorization_receipt_included" + ] + is False + ) + assert payload["verifier_receipt_evidence_handoff"]["signature_material_included"] is False + assert payload["verifier_receipt_evidence_handoff"]["secret_material_included"] is False + assert payload["verifier_receipt_evidence_handoff"]["accepts_plaintext_secret"] is False + assert payload["verifier_receipt_evidence_handoff"]["ready_for_database_apply_now"] is False + assert payload["verifier_receipt_closeout_contract"]["ready_for_database_apply_now"] is False + assert ( + payload["verifier_receipt_closeout_contract"][ + "signs_database_apply_authorization" + ] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["performs_detached_signature_verification"] is False + assert payload["safety"]["persists_verifier_receipt"] is False + assert payload["safety"]["signs_database_apply_authorization"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_evidence_execution_preflight_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply authorization evidence execution preflight should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-evidence-execution-preflight?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_authorization_evidence_execution_preflight + .__wrapped__() + ) + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_evidence_execution_preflight" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-verifier-receipt-closeout" + ) + assert payload["summary"]["authorization_evidence_execution_preflight_check_count"] == 12 + assert payload["summary"]["verifier_receipt_closeout_check_count"] == 12 + assert payload["summary"]["detached_verification_evidence_validation_check_count"] == 12 + assert payload["summary"]["authorization_evidence_execution_preflight_count"] == 1 + assert payload["summary"]["authorization_evidence_execution_field_count"] == 12 + assert payload["summary"]["authorization_evidence_execution_acceptance_gate_count"] == 10 + assert payload["summary"]["verifier_receipt_field_count"] == 12 + assert payload["summary"]["verifier_receipt_acceptance_gate_count"] == 10 + assert payload["summary"]["verifier_receipt_evidence_handoff_field_count"] == 12 + assert payload["summary"]["verifier_receipt_handoff_acceptance_gate_count"] == 10 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + payload["future_database_apply_authorization_verifier_handoff"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["future_database_apply_authorization_verifier_handoff"][ + "issues_database_apply_authorization" + ] + is False + ) + assert ( + payload["future_database_apply_authorization_verifier_handoff"][ + "signs_database_apply_authorization" + ] + is False + ) + assert ( + payload["future_database_apply_authorization_verifier_handoff"][ + "executes_authorization_evidence" + ] + is False + ) + assert ( + payload["future_database_apply_authorization_verifier_handoff"][ + "detached_signature_verification_performed" + ] + is False + ) + assert ( + payload["future_database_apply_authorization_verifier_handoff"][ + "verifier_receipt_persisted" + ] + is False + ) + assert ( + payload["future_database_apply_authorization_verifier_handoff"][ + "external_signed_authorization_receipt_included" + ] + is False + ) + assert ( + payload["authorization_evidence_execution_preflight"][ + "detached_signature_verification_performed" + ] + is False + ) + assert ( + payload["authorization_evidence_execution_preflight"][ + "verifier_receipt_persisted" + ] + is False + ) + assert ( + payload["authorization_evidence_execution_preflight"][ + "external_signed_authorization_receipt_included" + ] + is False + ) + assert ( + payload["authorization_evidence_execution_preflight"][ + "signed_authorization_receipt_included" + ] + is False + ) + assert ( + payload["authorization_evidence_execution_preflight"][ + "signature_material_included" + ] + is False + ) + assert ( + payload["authorization_evidence_execution_preflight"]["secret_material_included"] + is False + ) + assert ( + payload["authorization_evidence_execution_preflight"]["accepts_plaintext_secret"] + is False + ) + assert ( + payload["authorization_evidence_execution_preflight"][ + "executes_authorization_evidence" + ] + is False + ) + assert ( + payload["authorization_evidence_execution_preflight"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["authorization_evidence_execution_preflight_contract"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["authorization_evidence_execution_preflight_contract"][ + "executes_authorization_evidence" + ] + is False + ) + assert ( + payload["authorization_evidence_execution_preflight_contract"][ + "signs_database_apply_authorization" + ] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["performs_detached_signature_verification"] is False + assert payload["safety"]["persists_verifier_receipt"] is False + assert payload["safety"]["executes_authorization_evidence"] is False + assert payload["safety"]["signs_database_apply_authorization"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_evidence_execution_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply authorization evidence execution closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-evidence-execution-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_authorization_evidence_execution_closeout + .__wrapped__() + ) + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_evidence_execution_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-evidence-execution-preflight" + ) + assert payload["summary"]["authorization_evidence_execution_closeout_check_count"] == 12 + assert payload["summary"]["authorization_evidence_execution_preflight_check_count"] == 12 + assert payload["summary"]["verifier_receipt_closeout_check_count"] == 12 + assert payload["summary"]["authorization_evidence_execution_closeout_count"] == 1 + assert payload["summary"]["database_apply_final_verifier_gate_count"] == 1 + assert payload["summary"]["authorization_evidence_execution_closeout_field_count"] == 12 + assert payload["summary"]["authorization_evidence_execution_closeout_acceptance_gate_count"] == 10 + assert payload["summary"]["authorization_evidence_execution_field_count"] == 12 + assert payload["summary"]["authorization_evidence_execution_acceptance_gate_count"] == 10 + assert payload["summary"]["verifier_receipt_field_count"] == 12 + assert payload["summary"]["verifier_receipt_acceptance_gate_count"] == 10 + assert payload["summary"]["verifier_receipt_evidence_handoff_field_count"] == 12 + assert payload["summary"]["verifier_receipt_handoff_acceptance_gate_count"] == 10 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + payload["future_database_apply_authorization_final_verifier_gate"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["future_database_apply_authorization_final_verifier_gate"][ + "database_apply_authorized" + ] + is False + ) + assert ( + payload["future_database_apply_authorization_final_verifier_gate"][ + "issues_database_apply_authorization" + ] + is False + ) + assert ( + payload["future_database_apply_authorization_final_verifier_gate"][ + "signs_database_apply_authorization" + ] + is False + ) + assert ( + payload["future_database_apply_authorization_final_verifier_gate"][ + "executes_authorization_evidence" + ] + is False + ) + assert ( + payload["future_database_apply_authorization_final_verifier_gate"][ + "executes_database_apply" + ] + is False + ) + assert ( + payload["future_database_apply_authorization_final_verifier_gate"][ + "detached_signature_verification_performed" + ] + is False + ) + assert ( + payload["future_database_apply_authorization_final_verifier_gate"][ + "verifier_receipt_persisted" + ] + is False + ) + assert ( + payload["future_database_apply_authorization_final_verifier_gate"][ + "external_signed_authorization_receipt_included" + ] + is False + ) + assert ( + payload["authorization_evidence_execution_closeout"][ + "detached_signature_verification_performed" + ] + is False + ) + assert ( + payload["authorization_evidence_execution_closeout"][ + "verifier_receipt_persisted" + ] + is False + ) + assert ( + payload["authorization_evidence_execution_closeout"][ + "external_signed_authorization_receipt_included" + ] + is False + ) + assert ( + payload["authorization_evidence_execution_closeout"][ + "signed_authorization_receipt_included" + ] + is False + ) + assert ( + payload["authorization_evidence_execution_closeout"]["signature_material_included"] + is False + ) + assert payload["authorization_evidence_execution_closeout"]["secret_material_included"] is False + assert payload["authorization_evidence_execution_closeout"]["accepts_plaintext_secret"] is False + assert payload["authorization_evidence_execution_closeout"]["executes_authorization_evidence"] is False + assert payload["authorization_evidence_execution_closeout"]["executes_database_apply"] is False + assert payload["authorization_evidence_execution_closeout"]["ready_for_database_apply_now"] is False + assert payload["authorization_evidence_execution_closeout"]["database_apply_authorized"] is False + assert ( + payload["authorization_evidence_execution_closeout_contract"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["authorization_evidence_execution_closeout_contract"][ + "executes_authorization_evidence" + ] + is False + ) + assert ( + payload["authorization_evidence_execution_closeout_contract"][ + "executes_database_apply" + ] + is False + ) + assert ( + payload["authorization_evidence_execution_closeout_contract"][ + "database_apply_authorized" + ] + is False + ) + assert ( + payload["authorization_evidence_execution_closeout_contract"][ + "signs_database_apply_authorization" + ] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["performs_detached_signature_verification"] is False + assert payload["safety"]["persists_verifier_receipt"] is False + assert payload["safety"]["executes_authorization_evidence"] is False + assert payload["safety"]["executes_database_apply"] is False + assert payload["safety"]["signs_database_apply_authorization"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_apply_final_preflight_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled apply final preflight should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-apply-final-preflight?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_apply_final_preflight + .__wrapped__() + ) + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_apply_final_preflight" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-evidence-execution-closeout" + ) + assert payload["summary"]["controlled_apply_final_preflight_check_count"] == 12 + assert payload["summary"]["authorization_evidence_execution_closeout_check_count"] == 12 + assert payload["summary"]["authorization_evidence_execution_preflight_check_count"] == 12 + assert payload["summary"]["controlled_apply_final_preflight_count"] == 1 + assert payload["summary"]["controlled_apply_final_preflight_field_count"] == 12 + assert payload["summary"]["controlled_apply_final_preflight_acceptance_gate_count"] == 10 + assert payload["summary"]["rollback_binding_count"] == 1 + assert payload["summary"]["rollback_binding_field_count"] == 8 + assert payload["summary"]["post_apply_verifier_binding_count"] == 1 + assert payload["summary"]["post_apply_verifier_binding_field_count"] == 8 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + payload["future_database_apply_controlled_apply_final_preflight"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["future_database_apply_controlled_apply_final_preflight"][ + "database_apply_authorized" + ] + is False + ) + assert ( + payload["future_database_apply_controlled_apply_final_preflight"][ + "issues_database_apply_authorization" + ] + is False + ) + assert ( + payload["future_database_apply_controlled_apply_final_preflight"][ + "signs_database_apply_authorization" + ] + is False + ) + assert ( + payload["future_database_apply_controlled_apply_final_preflight"][ + "executes_authorization_evidence" + ] + is False + ) + assert ( + payload["future_database_apply_controlled_apply_final_preflight"][ + "executes_database_apply" + ] + is False + ) + assert ( + payload["future_database_apply_controlled_apply_final_preflight"][ + "executes_endpoint" + ] + is False + ) + assert ( + payload["future_database_apply_controlled_apply_final_preflight"][ + "executes_sql" + ] + is False + ) + assert ( + payload["future_database_apply_controlled_apply_final_preflight"][ + "writes_database" + ] + is False + ) + assert payload["controlled_apply_final_preflight"]["dry_run_only"] is True + assert payload["controlled_apply_final_preflight"]["check_mode_only"] is True + assert payload["controlled_apply_final_preflight"]["accepts_plaintext_secret"] is False + assert payload["controlled_apply_final_preflight"]["reads_secret_in_preview"] is False + assert payload["controlled_apply_final_preflight"]["signature_material_included"] is False + assert payload["controlled_apply_final_preflight"]["secret_material_included"] is False + assert ( + payload["controlled_apply_final_preflight"]["signs_database_apply_authorization"] + is False + ) + assert payload["controlled_apply_final_preflight"]["executes_authorization_evidence"] is False + assert payload["controlled_apply_final_preflight"]["executes_database_apply"] is False + assert payload["controlled_apply_final_preflight"]["executes_endpoint_in_preview"] is False + assert payload["controlled_apply_final_preflight"]["executes_sql_in_preview"] is False + assert payload["controlled_apply_final_preflight"]["writes_database_in_preview"] is False + assert payload["controlled_apply_final_preflight"]["ready_for_database_apply_now"] is False + assert payload["controlled_apply_final_preflight"]["database_apply_authorized"] is False + assert ( + payload["controlled_apply_final_preflight"]["rollback_binding"][ + "rollback_execution_authorized" + ] + is False + ) + assert ( + payload["controlled_apply_final_preflight"]["rollback_binding"][ + "rollback_executes_sql" + ] + is False + ) + assert ( + payload["controlled_apply_final_preflight"]["post_apply_verifier_binding"][ + "verifier_execution_authorized_in_preview" + ] + is False + ) + assert ( + payload["controlled_apply_final_preflight"]["post_apply_verifier_binding"][ + "database_apply_authorized" + ] + is False + ) + assert ( + payload["controlled_apply_final_preflight_contract"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["controlled_apply_final_preflight_contract"][ + "executes_database_apply" + ] + is False + ) + assert ( + payload["controlled_apply_final_preflight_contract"]["executes_endpoint"] + is False + ) + assert payload["controlled_apply_final_preflight_contract"]["executes_sql"] is False + assert ( + payload["controlled_apply_final_preflight_contract"]["database_apply_authorized"] + is False + ) + assert ( + payload["controlled_apply_final_preflight_contract"][ + "signs_database_apply_authorization" + ] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_post_receipt_parser_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run post-receipt parser closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-post-receipt-parser-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_post_receipt_parser_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_no_apply_enforcement_verification" + ] + parser_closeout = payload[ + "controlled_dry_run_post_receipt_parser_closeout" + ] + enforcement = parser_closeout["no_apply_enforcement_verification"] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_post_receipt_parser_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-execution-receipt-closeout" + ) + assert payload["summary"]["controlled_dry_run_post_receipt_parser_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_runner_execution_receipt_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_command_artifact_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_post_receipt_parser_closeout_count"] == 1 + assert payload["summary"]["controlled_dry_run_post_receipt_parser_closeout_field_count"] == 12 + assert payload["summary"]["controlled_dry_run_post_receipt_parser_closeout_acceptance_gate_count"] == 10 + assert payload["summary"]["no_apply_enforcement_verification_count"] == 1 + assert payload["summary"]["no_apply_enforcement_verification_field_count"] == 12 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert future["issues_database_apply_authorization"] is False + assert future["signs_database_apply_authorization"] is False + assert future["executes_authorization_evidence"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert parser_closeout["post_receipt_parser_closeout_only"] is True + assert parser_closeout["no_apply_enforcement_verification_only"] is True + assert parser_closeout["dry_run_only"] is True + assert parser_closeout["check_mode_only"] is True + assert parser_closeout["accepts_plaintext_secret"] is False + assert parser_closeout["reads_secret_in_preview"] is False + assert parser_closeout["signature_material_included"] is False + assert parser_closeout["secret_material_included"] is False + assert parser_closeout["signs_database_apply_authorization"] is False + assert parser_closeout["executes_authorization_evidence"] is False + assert parser_closeout["executes_database_apply"] is False + assert parser_closeout["executes_endpoint_in_preview"] is False + assert parser_closeout["executes_sql_in_preview"] is False + assert parser_closeout["writes_database_in_preview"] is False + assert enforcement["endpoint_execution_allowed"] is False + assert enforcement["sql_execution_allowed"] is False + assert enforcement["database_write_allowed"] is False + assert enforcement["database_apply_authorized"] is False + assert enforcement["executes_database_apply"] is False + assert enforcement["executes_endpoint"] is False + assert enforcement["executes_sql"] is False + assert enforcement["writes_database"] is False + assert ( + payload["controlled_dry_run_post_receipt_parser_closeout_contract"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["controlled_dry_run_post_receipt_parser_closeout_contract"][ + "executes_database_apply" + ] + is False + ) + assert ( + payload["controlled_dry_run_post_receipt_parser_closeout_contract"][ + "executes_endpoint" + ] + is False + ) + assert ( + payload["controlled_dry_run_post_receipt_parser_closeout_contract"][ + "executes_sql" + ] + is False + ) + assert ( + payload["controlled_dry_run_post_receipt_parser_closeout_contract"][ + "database_apply_authorized" + ] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + assert payload["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_no_apply_enforcement_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run no-apply enforcement closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-apply-enforcement-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_no_apply_enforcement_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_final_dry_run_executor_guard" + ] + enforcement_closeout = payload[ + "controlled_dry_run_no_apply_enforcement_closeout" + ] + final_guard = enforcement_closeout["final_dry_run_executor_guard"] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_no_apply_enforcement_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-post-receipt-parser-closeout" + ) + assert payload["summary"]["controlled_dry_run_no_apply_enforcement_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_post_receipt_parser_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_runner_execution_receipt_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_no_apply_enforcement_closeout_count"] == 1 + assert payload["summary"]["controlled_dry_run_no_apply_enforcement_closeout_field_count"] == 12 + assert payload["summary"]["controlled_dry_run_no_apply_enforcement_closeout_acceptance_gate_count"] == 10 + assert payload["summary"]["final_dry_run_executor_guard_count"] == 1 + assert payload["summary"]["final_dry_run_executor_guard_field_count"] == 12 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["dry_run_executor_invocation_allowed"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert future["issues_database_apply_authorization"] is False + assert future["signs_database_apply_authorization"] is False + assert future["executes_authorization_evidence"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert enforcement_closeout["no_apply_enforcement_closeout_only"] is True + assert enforcement_closeout["final_dry_run_executor_guard_only"] is True + assert enforcement_closeout["dry_run_only"] is True + assert enforcement_closeout["check_mode_only"] is True + assert enforcement_closeout["accepts_plaintext_secret"] is False + assert enforcement_closeout["reads_secret_in_preview"] is False + assert enforcement_closeout["signature_material_included"] is False + assert enforcement_closeout["secret_material_included"] is False + assert enforcement_closeout["signs_database_apply_authorization"] is False + assert enforcement_closeout["executes_authorization_evidence"] is False + assert enforcement_closeout["executes_database_apply"] is False + assert enforcement_closeout["executes_endpoint_in_preview"] is False + assert enforcement_closeout["executes_sql_in_preview"] is False + assert enforcement_closeout["writes_database_in_preview"] is False + assert final_guard["dry_run_executor_invocation_allowed"] is False + assert final_guard["endpoint_execution_allowed"] is False + assert final_guard["sql_execution_allowed"] is False + assert final_guard["database_write_allowed"] is False + assert final_guard["database_apply_authorized"] is False + assert final_guard["executes_database_apply"] is False + assert final_guard["executes_endpoint"] is False + assert final_guard["executes_sql"] is False + assert final_guard["writes_database"] is False + assert ( + payload["controlled_dry_run_no_apply_enforcement_closeout_contract"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["controlled_dry_run_no_apply_enforcement_closeout_contract"][ + "executes_database_apply" + ] + is False + ) + assert ( + payload["controlled_dry_run_no_apply_enforcement_closeout_contract"][ + "executes_endpoint" + ] + is False + ) + assert ( + payload["controlled_dry_run_no_apply_enforcement_closeout_contract"][ + "executes_sql" + ] + is False + ) + assert ( + payload["controlled_dry_run_no_apply_enforcement_closeout_contract"][ + "database_apply_authorized" + ] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_final_executor_guard_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run final executor guard closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-final-executor-guard-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_final_executor_guard_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_pre_apply_replay_verifier" + ] + guard_closeout = payload[ + "controlled_dry_run_final_executor_guard_closeout" + ] + replay = guard_closeout["pre_apply_replay_verifier"] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_final_executor_guard_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-apply-enforcement-closeout" + ) + assert payload["summary"]["controlled_dry_run_final_executor_guard_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_no_apply_enforcement_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_post_receipt_parser_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_final_executor_guard_closeout_count"] == 1 + assert payload["summary"]["controlled_dry_run_final_executor_guard_closeout_field_count"] == 12 + assert payload["summary"]["controlled_dry_run_final_executor_guard_closeout_acceptance_gate_count"] == 10 + assert payload["summary"]["pre_apply_replay_verifier_count"] == 1 + assert payload["summary"]["pre_apply_replay_verifier_field_count"] == 12 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["dry_run_executor_invocation_allowed"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert future["issues_database_apply_authorization"] is False + assert future["signs_database_apply_authorization"] is False + assert future["executes_authorization_evidence"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert guard_closeout["final_executor_guard_closeout_only"] is True + assert guard_closeout["pre_apply_replay_verifier_only"] is True + assert guard_closeout["dry_run_only"] is True + assert guard_closeout["check_mode_only"] is True + assert guard_closeout["accepts_plaintext_secret"] is False + assert guard_closeout["reads_secret_in_preview"] is False + assert guard_closeout["signature_material_included"] is False + assert guard_closeout["secret_material_included"] is False + assert guard_closeout["signs_database_apply_authorization"] is False + assert guard_closeout["executes_authorization_evidence"] is False + assert guard_closeout["executes_database_apply"] is False + assert guard_closeout["executes_endpoint_in_preview"] is False + assert guard_closeout["executes_sql_in_preview"] is False + assert guard_closeout["writes_database_in_preview"] is False + assert replay["replay_mode"] == "pre_apply_replay_preview_only" + assert replay["dry_run_executor_invocation_allowed"] is False + assert replay["endpoint_execution_allowed"] is False + assert replay["sql_execution_allowed"] is False + assert replay["database_write_allowed"] is False + assert replay["database_apply_authorized"] is False + assert replay["executes_database_apply"] is False + assert replay["executes_endpoint"] is False + assert replay["executes_sql"] is False + assert replay["writes_database"] is False + assert ( + payload["controlled_dry_run_final_executor_guard_closeout_contract"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["controlled_dry_run_final_executor_guard_closeout_contract"][ + "executes_database_apply" + ] + is False + ) + assert ( + payload["controlled_dry_run_final_executor_guard_closeout_contract"][ + "executes_endpoint" + ] + is False + ) + assert ( + payload["controlled_dry_run_final_executor_guard_closeout_contract"][ + "executes_sql" + ] + is False + ) + assert ( + payload["controlled_dry_run_final_executor_guard_closeout_contract"][ + "database_apply_authorized" + ] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_pre_apply_replay_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run pre-apply replay closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-pre-apply-replay-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_pre_apply_replay_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_apply_executor_readiness_contract" + ] + replay_closeout = payload[ + "controlled_dry_run_pre_apply_replay_closeout" + ] + readiness = replay_closeout["apply_executor_readiness_contract"] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_pre_apply_replay_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-final-executor-guard-closeout" + ) + assert payload["summary"]["controlled_dry_run_pre_apply_replay_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_final_executor_guard_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_no_apply_enforcement_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_pre_apply_replay_closeout_count"] == 1 + assert payload["summary"]["controlled_dry_run_pre_apply_replay_closeout_field_count"] == 12 + assert payload["summary"]["controlled_dry_run_pre_apply_replay_closeout_acceptance_gate_count"] == 10 + assert payload["summary"]["apply_executor_readiness_contract_count"] == 1 + assert payload["summary"]["apply_executor_readiness_contract_field_count"] == 12 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["dry_run_executor_invocation_allowed"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert future["issues_database_apply_authorization"] is False + assert future["signs_database_apply_authorization"] is False + assert future["executes_authorization_evidence"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert replay_closeout["pre_apply_replay_closeout_only"] is True + assert replay_closeout["apply_executor_readiness_contract_only"] is True + assert replay_closeout["dry_run_only"] is True + assert replay_closeout["check_mode_only"] is True + assert replay_closeout["accepts_plaintext_secret"] is False + assert replay_closeout["reads_secret_in_preview"] is False + assert replay_closeout["signature_material_included"] is False + assert replay_closeout["secret_material_included"] is False + assert replay_closeout["signs_database_apply_authorization"] is False + assert replay_closeout["executes_authorization_evidence"] is False + assert replay_closeout["executes_database_apply"] is False + assert replay_closeout["executes_endpoint_in_preview"] is False + assert replay_closeout["executes_sql_in_preview"] is False + assert replay_closeout["writes_database_in_preview"] is False + assert readiness["readiness_mode"] == "apply_executor_readiness_contract_preview_only" + assert readiness["dry_run_executor_invocation_allowed"] is False + assert readiness["endpoint_execution_allowed"] is False + assert readiness["sql_execution_allowed"] is False + assert readiness["database_write_allowed"] is False + assert readiness["ready_for_database_apply_now"] is False + assert readiness["database_apply_authorized"] is False + assert readiness["executes_database_apply"] is False + assert readiness["executes_endpoint"] is False + assert readiness["executes_sql"] is False + assert readiness["writes_database"] is False + assert ( + payload["controlled_dry_run_pre_apply_replay_closeout_contract"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["controlled_dry_run_pre_apply_replay_closeout_contract"][ + "ready_for_dry_run_executor_invocation_now" + ] + is False + ) + assert ( + payload["controlled_dry_run_pre_apply_replay_closeout_contract"][ + "executes_database_apply" + ] + is False + ) + assert ( + payload["controlled_dry_run_pre_apply_replay_closeout_contract"][ + "executes_endpoint" + ] + is False + ) + assert ( + payload["controlled_dry_run_pre_apply_replay_closeout_contract"][ + "executes_sql" + ] + is False + ) + assert ( + payload["controlled_dry_run_pre_apply_replay_closeout_contract"][ + "database_apply_authorized" + ] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_apply_executor_readiness_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run apply executor readiness closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-apply-executor-readiness-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_apply_executor_readiness_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_invocation_readiness_receipt" + ] + readiness_closeout = payload[ + "controlled_dry_run_apply_executor_readiness_closeout" + ] + receipt = readiness_closeout["dry_run_invocation_readiness_receipt"] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_apply_executor_readiness_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-pre-apply-replay-closeout" + ) + assert payload["summary"]["controlled_dry_run_apply_executor_readiness_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_pre_apply_replay_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_final_executor_guard_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_apply_executor_readiness_closeout_count"] == 1 + assert payload["summary"]["controlled_dry_run_apply_executor_readiness_closeout_field_count"] == 12 + assert payload["summary"]["controlled_dry_run_apply_executor_readiness_closeout_acceptance_gate_count"] == 10 + assert payload["summary"]["dry_run_invocation_readiness_receipt_count"] == 1 + assert payload["summary"]["dry_run_invocation_readiness_receipt_field_count"] == 12 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["dry_run_executor_invocation_allowed"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert future["issues_database_apply_authorization"] is False + assert future["signs_database_apply_authorization"] is False + assert future["executes_authorization_evidence"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert readiness_closeout["apply_executor_readiness_closeout_only"] is True + assert readiness_closeout["dry_run_invocation_readiness_receipt_only"] is True + assert readiness_closeout["dry_run_only"] is True + assert readiness_closeout["check_mode_only"] is True + assert readiness_closeout["accepts_plaintext_secret"] is False + assert readiness_closeout["reads_secret_in_preview"] is False + assert readiness_closeout["signature_material_included"] is False + assert readiness_closeout["secret_material_included"] is False + assert readiness_closeout["signs_database_apply_authorization"] is False + assert readiness_closeout["executes_authorization_evidence"] is False + assert readiness_closeout["executes_database_apply"] is False + assert readiness_closeout["executes_endpoint_in_preview"] is False + assert readiness_closeout["executes_sql_in_preview"] is False + assert readiness_closeout["writes_database_in_preview"] is False + assert receipt["receipt_mode"] == "dry_run_invocation_readiness_preview_only" + assert receipt["dry_run_executor_invocation_allowed"] is False + assert receipt["ready_for_dry_run_executor_invocation_now"] is False + assert receipt["endpoint_execution_allowed"] is False + assert receipt["sql_execution_allowed"] is False + assert receipt["database_write_allowed"] is False + assert receipt["ready_for_database_apply_now"] is False + assert receipt["database_apply_authorized"] is False + assert receipt["executes_database_apply"] is False + assert receipt["executes_endpoint"] is False + assert receipt["executes_sql"] is False + assert receipt["writes_database"] is False + assert ( + payload["controlled_dry_run_apply_executor_readiness_closeout_contract"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["controlled_dry_run_apply_executor_readiness_closeout_contract"][ + "ready_for_dry_run_executor_invocation_now" + ] + is False + ) + assert ( + payload["controlled_dry_run_apply_executor_readiness_closeout_contract"][ + "ready_for_actual_dry_run_execution_now" + ] + is False + ) + assert ( + payload["controlled_dry_run_apply_executor_readiness_closeout_contract"][ + "executes_database_apply" + ] + is False + ) + assert ( + payload["controlled_dry_run_apply_executor_readiness_closeout_contract"][ + "executes_endpoint" + ] + is False + ) + assert ( + payload["controlled_dry_run_apply_executor_readiness_closeout_contract"][ + "executes_sql" + ] + is False + ) + assert ( + payload["controlled_dry_run_apply_executor_readiness_closeout_contract"][ + "database_apply_authorized" + ] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_invocation_receipt_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run invocation receipt closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-invocation-receipt-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_invocation_receipt_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_no_write_invocation_package" + ] + invocation_closeout = payload[ + "controlled_dry_run_invocation_receipt_closeout" + ] + package = invocation_closeout["no_write_invocation_package"] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_invocation_receipt_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-apply-executor-readiness-closeout" + ) + assert payload["summary"]["controlled_dry_run_invocation_receipt_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_apply_executor_readiness_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_pre_apply_replay_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_invocation_receipt_closeout_count"] == 1 + assert payload["summary"]["controlled_dry_run_invocation_receipt_closeout_field_count"] == 12 + assert payload["summary"]["controlled_dry_run_invocation_receipt_closeout_acceptance_gate_count"] == 10 + assert payload["summary"]["no_write_invocation_package_count"] == 1 + assert payload["summary"]["no_write_invocation_package_field_count"] == 12 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["dry_run_executor_invocation_allowed"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert future["issues_database_apply_authorization"] is False + assert future["signs_database_apply_authorization"] is False + assert future["executes_authorization_evidence"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert invocation_closeout["invocation_receipt_closeout_only"] is True + assert invocation_closeout["no_write_invocation_package_only"] is True + assert invocation_closeout["dry_run_only"] is True + assert invocation_closeout["check_mode_only"] is True + assert invocation_closeout["accepts_plaintext_secret"] is False + assert invocation_closeout["reads_secret_in_preview"] is False + assert invocation_closeout["signature_material_included"] is False + assert invocation_closeout["secret_material_included"] is False + assert invocation_closeout["signs_database_apply_authorization"] is False + assert invocation_closeout["executes_authorization_evidence"] is False + assert invocation_closeout["executes_database_apply"] is False + assert invocation_closeout["executes_endpoint_in_preview"] is False + assert invocation_closeout["executes_sql_in_preview"] is False + assert invocation_closeout["writes_database_in_preview"] is False + assert package["package_mode"] == "no_write_invocation_package_preview_only" + assert package["dry_run_executor_invocation_allowed"] is False + assert package["ready_for_no_write_dry_run_invocation_package_now"] is False + assert package["ready_for_actual_dry_run_execution_now"] is False + assert package["endpoint_execution_allowed"] is False + assert package["sql_execution_allowed"] is False + assert package["database_write_allowed"] is False + assert package["ready_for_database_apply_now"] is False + assert package["database_apply_authorized"] is False + assert package["executes_database_apply"] is False + assert package["executes_endpoint"] is False + assert package["executes_sql"] is False + assert package["writes_database"] is False + assert ( + payload["controlled_dry_run_invocation_receipt_closeout_contract"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["controlled_dry_run_invocation_receipt_closeout_contract"][ + "ready_for_dry_run_executor_invocation_now" + ] + is False + ) + assert ( + payload["controlled_dry_run_invocation_receipt_closeout_contract"][ + "ready_for_actual_dry_run_execution_now" + ] + is False + ) + assert ( + payload["controlled_dry_run_invocation_receipt_closeout_contract"][ + "executes_database_apply" + ] + is False + ) + assert ( + payload["controlled_dry_run_invocation_receipt_closeout_contract"][ + "executes_endpoint" + ] + is False + ) + assert ( + payload["controlled_dry_run_invocation_receipt_closeout_contract"][ + "executes_sql" + ] + is False + ) + assert ( + payload["controlled_dry_run_invocation_receipt_closeout_contract"][ + "database_apply_authorized" + ] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_no_write_invocation_package_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run no-write invocation package closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-write-invocation-package-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_no_write_invocation_package_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_execution_preflight_guard" + ] + package_closeout = payload[ + "controlled_dry_run_no_write_invocation_package_closeout" + ] + guard = package_closeout["execution_preflight_guard"] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_no_write_invocation_package_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-invocation-receipt-closeout" + ) + assert payload["summary"]["controlled_dry_run_no_write_invocation_package_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_invocation_receipt_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_apply_executor_readiness_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_no_write_invocation_package_closeout_count"] == 1 + assert payload["summary"]["controlled_dry_run_no_write_invocation_package_closeout_field_count"] == 12 + assert payload["summary"]["controlled_dry_run_no_write_invocation_package_closeout_acceptance_gate_count"] == 10 + assert payload["summary"]["execution_preflight_guard_count"] == 1 + assert payload["summary"]["execution_preflight_guard_field_count"] == 12 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["dry_run_executor_invocation_allowed"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert future["issues_database_apply_authorization"] is False + assert future["signs_database_apply_authorization"] is False + assert future["executes_authorization_evidence"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert package_closeout["no_write_invocation_package_closeout_only"] is True + assert package_closeout["execution_preflight_guard_only"] is True + assert package_closeout["dry_run_only"] is True + assert package_closeout["check_mode_only"] is True + assert package_closeout["accepts_plaintext_secret"] is False + assert package_closeout["reads_secret_in_preview"] is False + assert package_closeout["signature_material_included"] is False + assert package_closeout["secret_material_included"] is False + assert package_closeout["signs_database_apply_authorization"] is False + assert package_closeout["executes_authorization_evidence"] is False + assert package_closeout["executes_database_apply"] is False + assert package_closeout["executes_endpoint_in_preview"] is False + assert package_closeout["executes_sql_in_preview"] is False + assert package_closeout["writes_database_in_preview"] is False + assert guard["guard_mode"] == "execution_preflight_guard_preview_only" + assert guard["dry_run_executor_invocation_allowed"] is False + assert guard["ready_for_execution_preflight_guard_now"] is False + assert guard["ready_for_dry_run_executor_invocation_now"] is False + assert guard["ready_for_actual_dry_run_execution_now"] is False + assert guard["endpoint_execution_allowed"] is False + assert guard["sql_execution_allowed"] is False + assert guard["database_write_allowed"] is False + assert guard["ready_for_database_apply_now"] is False + assert guard["database_apply_authorized"] is False + assert guard["executes_database_apply"] is False + assert guard["executes_endpoint"] is False + assert guard["executes_sql"] is False + assert guard["writes_database"] is False + assert ( + payload[ + "controlled_dry_run_no_write_invocation_package_closeout_contract" + ]["ready_for_database_apply_now"] + is False + ) + assert ( + payload[ + "controlled_dry_run_no_write_invocation_package_closeout_contract" + ]["ready_for_dry_run_executor_invocation_now"] + is False + ) + assert ( + payload[ + "controlled_dry_run_no_write_invocation_package_closeout_contract" + ]["ready_for_actual_dry_run_execution_now"] + is False + ) + assert ( + payload[ + "controlled_dry_run_no_write_invocation_package_closeout_contract" + ]["executes_database_apply"] + is False + ) + assert ( + payload[ + "controlled_dry_run_no_write_invocation_package_closeout_contract" + ]["executes_endpoint"] + is False + ) + assert ( + payload[ + "controlled_dry_run_no_write_invocation_package_closeout_contract" + ]["executes_sql"] + is False + ) + assert ( + payload[ + "controlled_dry_run_no_write_invocation_package_closeout_contract" + ]["database_apply_authorized"] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_execution_preflight_guard_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run execution preflight guard closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-preflight-guard-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_execution_preflight_guard_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_runner_invocation_boundary" + ] + guard_closeout = payload[ + "controlled_dry_run_execution_preflight_guard_closeout" + ] + boundary = guard_closeout["runner_invocation_boundary"] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_execution_preflight_guard_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-write-invocation-package-closeout" + ) + assert payload["summary"]["controlled_dry_run_execution_preflight_guard_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_no_write_invocation_package_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_invocation_receipt_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_execution_preflight_guard_closeout_count"] == 1 + assert payload["summary"]["controlled_dry_run_execution_preflight_guard_closeout_field_count"] == 12 + assert payload["summary"]["controlled_dry_run_execution_preflight_guard_closeout_acceptance_gate_count"] == 10 + assert payload["summary"]["runner_invocation_boundary_count"] == 1 + assert payload["summary"]["runner_invocation_boundary_field_count"] == 12 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["dry_run_executor_invocation_allowed"] is False + assert future["runner_invocation_allowed"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert future["issues_database_apply_authorization"] is False + assert future["signs_database_apply_authorization"] is False + assert future["executes_authorization_evidence"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert guard_closeout["execution_preflight_guard_closeout_only"] is True + assert guard_closeout["runner_invocation_boundary_only"] is True + assert guard_closeout["dry_run_only"] is True + assert guard_closeout["check_mode_only"] is True + assert guard_closeout["accepts_plaintext_secret"] is False + assert guard_closeout["reads_secret_in_preview"] is False + assert guard_closeout["signature_material_included"] is False + assert guard_closeout["secret_material_included"] is False + assert guard_closeout["signs_database_apply_authorization"] is False + assert guard_closeout["executes_authorization_evidence"] is False + assert guard_closeout["executes_database_apply"] is False + assert guard_closeout["executes_endpoint_in_preview"] is False + assert guard_closeout["executes_sql_in_preview"] is False + assert guard_closeout["writes_database_in_preview"] is False + assert boundary["boundary_mode"] == "runner_invocation_boundary_preview_only" + assert boundary["dry_run_executor_invocation_allowed"] is False + assert boundary["runner_invocation_allowed"] is False + assert boundary["ready_for_runner_invocation_boundary_now"] is False + assert boundary["ready_for_dry_run_executor_invocation_now"] is False + assert boundary["ready_for_actual_dry_run_execution_now"] is False + assert boundary["endpoint_execution_allowed"] is False + assert boundary["sql_execution_allowed"] is False + assert boundary["database_write_allowed"] is False + assert boundary["ready_for_database_apply_now"] is False + assert boundary["database_apply_authorized"] is False + assert boundary["executes_database_apply"] is False + assert boundary["executes_endpoint"] is False + assert boundary["executes_sql"] is False + assert boundary["writes_database"] is False + assert boundary["captures_stdout"] is False + assert boundary["captures_stderr"] is False + assert ( + payload[ + "controlled_dry_run_execution_preflight_guard_closeout_contract" + ]["ready_for_database_apply_now"] + is False + ) + assert ( + payload[ + "controlled_dry_run_execution_preflight_guard_closeout_contract" + ]["ready_for_dry_run_executor_invocation_now"] + is False + ) + assert ( + payload[ + "controlled_dry_run_execution_preflight_guard_closeout_contract" + ]["ready_for_actual_dry_run_execution_now"] + is False + ) + assert ( + payload[ + "controlled_dry_run_execution_preflight_guard_closeout_contract" + ]["executes_database_apply"] + is False + ) + assert ( + payload[ + "controlled_dry_run_execution_preflight_guard_closeout_contract" + ]["executes_endpoint"] + is False + ) + assert ( + payload[ + "controlled_dry_run_execution_preflight_guard_closeout_contract" + ]["executes_sql"] + is False + ) + assert ( + payload[ + "controlled_dry_run_execution_preflight_guard_closeout_contract" + ]["database_apply_authorized"] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_runner_invocation_boundary_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run runner invocation boundary closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-invocation-boundary-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_runner_invocation_boundary_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_no_execution_receipt_handoff" + ] + boundary_closeout = payload[ + "controlled_dry_run_runner_invocation_boundary_closeout" + ] + handoff = boundary_closeout["no_execution_receipt_handoff"] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_runner_invocation_boundary_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-preflight-guard-closeout" + ) + assert payload["summary"]["controlled_dry_run_runner_invocation_boundary_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_execution_preflight_guard_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_no_write_invocation_package_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_runner_invocation_boundary_closeout_count"] == 1 + assert payload["summary"]["controlled_dry_run_runner_invocation_boundary_closeout_field_count"] == 12 + assert payload["summary"]["controlled_dry_run_runner_invocation_boundary_closeout_acceptance_gate_count"] == 10 + assert payload["summary"]["no_execution_receipt_handoff_count"] == 1 + assert payload["summary"]["no_execution_receipt_handoff_field_count"] == 12 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["dry_run_executor_invocation_allowed"] is False + assert future["runner_invocation_allowed"] is False + assert future["execution_receipt_present"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert future["issues_database_apply_authorization"] is False + assert future["signs_database_apply_authorization"] is False + assert future["executes_authorization_evidence"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert future["stdout_included"] is False + assert future["stderr_included"] is False + assert boundary_closeout["runner_invocation_boundary_closeout_only"] is True + assert boundary_closeout["no_execution_receipt_handoff_only"] is True + assert boundary_closeout["dry_run_only"] is True + assert boundary_closeout["check_mode_only"] is True + assert boundary_closeout["accepts_plaintext_secret"] is False + assert boundary_closeout["reads_secret_in_preview"] is False + assert boundary_closeout["signature_material_included"] is False + assert boundary_closeout["secret_material_included"] is False + assert boundary_closeout["signs_database_apply_authorization"] is False + assert boundary_closeout["executes_authorization_evidence"] is False + assert boundary_closeout["executes_database_apply"] is False + assert boundary_closeout["executes_endpoint_in_preview"] is False + assert boundary_closeout["executes_sql_in_preview"] is False + assert boundary_closeout["writes_database_in_preview"] is False + assert boundary_closeout["captures_stdout"] is False + assert boundary_closeout["captures_stderr"] is False + assert handoff["handoff_mode"] == "no_execution_receipt_handoff_preview_only" + assert handoff["execution_receipt_present"] is False + assert handoff["execution_receipt_required"] is False + assert handoff["dry_run_executor_invocation_allowed"] is False + assert handoff["runner_invocation_allowed"] is False + assert handoff["ready_for_no_execution_receipt_handoff_now"] is False + assert handoff["ready_for_dry_run_executor_invocation_now"] is False + assert handoff["ready_for_actual_dry_run_execution_now"] is False + assert handoff["endpoint_execution_allowed"] is False + assert handoff["sql_execution_allowed"] is False + assert handoff["database_write_allowed"] is False + assert handoff["ready_for_database_apply_now"] is False + assert handoff["database_apply_authorized"] is False + assert handoff["executes_database_apply"] is False + assert handoff["executes_endpoint"] is False + assert handoff["executes_sql"] is False + assert handoff["writes_database"] is False + assert handoff["stdout_included"] is False + assert handoff["stderr_included"] is False + assert ( + payload[ + "controlled_dry_run_runner_invocation_boundary_closeout_contract" + ]["ready_for_database_apply_now"] + is False + ) + assert ( + payload[ + "controlled_dry_run_runner_invocation_boundary_closeout_contract" + ]["ready_for_dry_run_executor_invocation_now"] + is False + ) + assert ( + payload[ + "controlled_dry_run_runner_invocation_boundary_closeout_contract" + ]["ready_for_actual_dry_run_execution_now"] + is False + ) + assert ( + payload[ + "controlled_dry_run_runner_invocation_boundary_closeout_contract" + ]["executes_database_apply"] + is False + ) + assert ( + payload[ + "controlled_dry_run_runner_invocation_boundary_closeout_contract" + ]["executes_endpoint"] + is False + ) + assert ( + payload[ + "controlled_dry_run_runner_invocation_boundary_closeout_contract" + ]["executes_sql"] + is False + ) + assert ( + payload[ + "controlled_dry_run_runner_invocation_boundary_closeout_contract" + ]["database_apply_authorized"] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_no_execution_receipt_handoff_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run no-execution receipt handoff closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-execution-receipt-handoff-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_no_execution_receipt_handoff_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_final_no_runner_execution_proof" + ] + handoff_closeout = payload[ + "controlled_dry_run_no_execution_receipt_handoff_closeout" + ] + proof = handoff_closeout["final_no_runner_execution_proof"] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_no_execution_receipt_handoff_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-invocation-boundary-closeout" + ) + assert payload["summary"]["controlled_dry_run_no_execution_receipt_handoff_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_runner_invocation_boundary_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_execution_preflight_guard_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_no_execution_receipt_handoff_closeout_count"] == 1 + assert payload["summary"]["controlled_dry_run_no_execution_receipt_handoff_closeout_field_count"] == 12 + assert payload["summary"]["controlled_dry_run_no_execution_receipt_handoff_closeout_acceptance_gate_count"] == 10 + assert payload["summary"]["final_no_runner_execution_proof_count"] == 1 + assert payload["summary"]["final_no_runner_execution_proof_field_count"] == 12 + assert payload["summary"]["dry_run_executor_invoked_count"] == 0 + assert payload["summary"]["runner_invocation_performed_count"] == 0 + assert payload["summary"]["endpoint_executed_count"] == 0 + assert payload["summary"]["sql_executed_count"] == 0 + assert payload["summary"]["database_written_count"] == 0 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["dry_run_executor_invoked"] is False + assert future["runner_invocation_performed"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["execution_receipt_present"] is False + assert future["dry_run_executor_invocation_allowed"] is False + assert future["runner_invocation_allowed"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert future["issues_database_apply_authorization"] is False + assert future["signs_database_apply_authorization"] is False + assert future["executes_authorization_evidence"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert future["stdout_included"] is False + assert future["stderr_included"] is False + assert handoff_closeout["no_execution_receipt_handoff_closeout_only"] is True + assert handoff_closeout["final_no_runner_execution_proof_only"] is True + assert handoff_closeout["dry_run_only"] is True + assert handoff_closeout["check_mode_only"] is True + assert handoff_closeout["accepts_plaintext_secret"] is False + assert handoff_closeout["reads_secret_in_preview"] is False + assert handoff_closeout["signature_material_included"] is False + assert handoff_closeout["secret_material_included"] is False + assert handoff_closeout["signs_database_apply_authorization"] is False + assert handoff_closeout["executes_authorization_evidence"] is False + assert handoff_closeout["executes_database_apply"] is False + assert handoff_closeout["executes_endpoint_in_preview"] is False + assert handoff_closeout["executes_sql_in_preview"] is False + assert handoff_closeout["writes_database_in_preview"] is False + assert handoff_closeout["stdout_included"] is False + assert handoff_closeout["stderr_included"] is False + assert proof["proof_mode"] == "final_no_runner_execution_proof_preview_only" + assert proof["execution_receipt_present"] is False + assert proof["execution_receipt_required"] is False + assert proof["dry_run_executor_invoked"] is False + assert proof["runner_invocation_performed"] is False + assert proof["endpoint_executed"] is False + assert proof["sql_executed"] is False + assert proof["database_written"] is False + assert proof["dry_run_executor_invocation_allowed"] is False + assert proof["runner_invocation_allowed"] is False + assert proof["ready_for_final_no_runner_execution_proof_now"] is False + assert proof["ready_for_dry_run_executor_invocation_now"] is False + assert proof["ready_for_actual_dry_run_execution_now"] is False + assert proof["endpoint_execution_allowed"] is False + assert proof["sql_execution_allowed"] is False + assert proof["database_write_allowed"] is False + assert proof["ready_for_database_apply_now"] is False + assert proof["database_apply_authorized"] is False + assert proof["executes_database_apply"] is False + assert proof["executes_endpoint"] is False + assert proof["executes_sql"] is False + assert proof["writes_database"] is False + assert proof["stdout_included"] is False + assert proof["stderr_included"] is False + assert ( + payload[ + "controlled_dry_run_no_execution_receipt_handoff_closeout_contract" + ]["ready_for_database_apply_now"] + is False + ) + assert ( + payload[ + "controlled_dry_run_no_execution_receipt_handoff_closeout_contract" + ]["ready_for_dry_run_executor_invocation_now"] + is False + ) + assert ( + payload[ + "controlled_dry_run_no_execution_receipt_handoff_closeout_contract" + ]["ready_for_actual_dry_run_execution_now"] + is False + ) + assert ( + payload[ + "controlled_dry_run_no_execution_receipt_handoff_closeout_contract" + ]["executes_database_apply"] + is False + ) + assert ( + payload[ + "controlled_dry_run_no_execution_receipt_handoff_closeout_contract" + ]["executes_endpoint"] + is False + ) + assert ( + payload[ + "controlled_dry_run_no_execution_receipt_handoff_closeout_contract" + ]["executes_sql"] + is False + ) + assert ( + payload[ + "controlled_dry_run_no_execution_receipt_handoff_closeout_contract" + ]["database_apply_authorized"] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_final_no_runner_execution_proof_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run final no-runner proof closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-final-no-runner-execution-proof-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_final_no_runner_execution_proof_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_controlled_executor_quarantine_proof" + ] + proof_closeout = payload[ + "controlled_dry_run_final_no_runner_execution_proof_closeout" + ] + quarantine = proof_closeout["controlled_executor_quarantine_proof"] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_final_no_runner_execution_proof_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-execution-receipt-handoff-closeout" + ) + assert payload["summary"]["controlled_dry_run_final_no_runner_execution_proof_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_no_execution_receipt_handoff_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_runner_invocation_boundary_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_final_no_runner_execution_proof_closeout_count"] == 1 + assert payload["summary"]["controlled_dry_run_final_no_runner_execution_proof_closeout_field_count"] == 12 + assert payload["summary"]["controlled_dry_run_final_no_runner_execution_proof_closeout_acceptance_gate_count"] == 10 + assert payload["summary"]["controlled_executor_quarantine_proof_count"] == 1 + assert payload["summary"]["controlled_executor_quarantine_proof_field_count"] == 12 + assert payload["summary"]["dry_run_executor_invoked_count"] == 0 + assert payload["summary"]["runner_invocation_performed_count"] == 0 + assert payload["summary"]["endpoint_executed_count"] == 0 + assert payload["summary"]["sql_executed_count"] == 0 + assert payload["summary"]["database_written_count"] == 0 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["controlled_executor_quarantine_bound"] is True + assert future["executor_quarantine_enforced"] is True + assert future["dry_run_executor_invoked"] is False + assert future["runner_invocation_performed"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["execution_receipt_present"] is False + assert future["dry_run_executor_invocation_allowed"] is False + assert future["runner_invocation_allowed"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert future["issues_database_apply_authorization"] is False + assert future["signs_database_apply_authorization"] is False + assert future["executes_authorization_evidence"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert future["stdout_included"] is False + assert future["stderr_included"] is False + assert proof_closeout["final_no_runner_execution_proof_closeout_only"] is True + assert proof_closeout["controlled_executor_quarantine_proof_only"] is True + assert proof_closeout["dry_run_only"] is True + assert proof_closeout["check_mode_only"] is True + assert proof_closeout["accepts_plaintext_secret"] is False + assert proof_closeout["reads_secret_in_preview"] is False + assert proof_closeout["signature_material_included"] is False + assert proof_closeout["secret_material_included"] is False + assert proof_closeout["signs_database_apply_authorization"] is False + assert proof_closeout["executes_authorization_evidence"] is False + assert proof_closeout["executes_database_apply"] is False + assert proof_closeout["executes_endpoint_in_preview"] is False + assert proof_closeout["executes_sql_in_preview"] is False + assert proof_closeout["writes_database_in_preview"] is False + assert proof_closeout["stdout_included"] is False + assert proof_closeout["stderr_included"] is False + assert quarantine["quarantine_mode"] == "controlled_executor_quarantine_proof_preview_only" + assert quarantine["controlled_executor_quarantine_bound"] is True + assert quarantine["executor_quarantine_enforced"] is True + assert quarantine["execution_receipt_present"] is False + assert quarantine["execution_receipt_required"] is False + assert quarantine["dry_run_executor_invoked"] is False + assert quarantine["runner_invocation_performed"] is False + assert quarantine["endpoint_executed"] is False + assert quarantine["sql_executed"] is False + assert quarantine["database_written"] is False + assert quarantine["dry_run_executor_invocation_allowed"] is False + assert quarantine["runner_invocation_allowed"] is False + assert quarantine["ready_for_controlled_executor_quarantine_now"] is False + assert quarantine["ready_for_dry_run_executor_invocation_now"] is False + assert quarantine["ready_for_actual_dry_run_execution_now"] is False + assert quarantine["endpoint_execution_allowed"] is False + assert quarantine["sql_execution_allowed"] is False + assert quarantine["database_write_allowed"] is False + assert quarantine["ready_for_database_apply_now"] is False + assert quarantine["database_apply_authorized"] is False + assert quarantine["executes_database_apply"] is False + assert quarantine["executes_endpoint"] is False + assert quarantine["executes_sql"] is False + assert quarantine["writes_database"] is False + assert quarantine["stdout_included"] is False + assert quarantine["stderr_included"] is False + assert ( + payload[ + "controlled_dry_run_final_no_runner_execution_proof_closeout_contract" + ]["ready_for_database_apply_now"] + is False + ) + assert ( + payload[ + "controlled_dry_run_final_no_runner_execution_proof_closeout_contract" + ]["ready_for_dry_run_executor_invocation_now"] + is False + ) + assert ( + payload[ + "controlled_dry_run_final_no_runner_execution_proof_closeout_contract" + ]["ready_for_actual_dry_run_execution_now"] + is False + ) + assert ( + payload[ + "controlled_dry_run_final_no_runner_execution_proof_closeout_contract" + ]["executes_database_apply"] + is False + ) + assert ( + payload[ + "controlled_dry_run_final_no_runner_execution_proof_closeout_contract" + ]["executes_endpoint"] + is False + ) + assert ( + payload[ + "controlled_dry_run_final_no_runner_execution_proof_closeout_contract" + ]["executes_sql"] + is False + ) + assert ( + payload[ + "controlled_dry_run_final_no_runner_execution_proof_closeout_contract" + ]["database_apply_authorized"] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_controlled_executor_quarantine_proof_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run controlled executor quarantine proof closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-controlled-executor-quarantine-proof-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_controlled_executor_quarantine_proof_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_execution_envelope_freeze_proof" + ] + quarantine_closeout = payload[ + "controlled_dry_run_controlled_executor_quarantine_proof_closeout" + ] + freeze = quarantine_closeout["dry_run_execution_envelope_freeze_proof"] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_controlled_executor_quarantine_proof_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-final-no-runner-execution-proof-closeout" + ) + assert payload["summary"]["controlled_dry_run_controlled_executor_quarantine_proof_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_final_no_runner_execution_proof_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_no_execution_receipt_handoff_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_controlled_executor_quarantine_proof_closeout_count"] == 1 + assert payload["summary"]["controlled_dry_run_controlled_executor_quarantine_proof_closeout_field_count"] == 12 + assert payload["summary"]["controlled_dry_run_controlled_executor_quarantine_proof_closeout_acceptance_gate_count"] == 10 + assert payload["summary"]["dry_run_execution_envelope_freeze_proof_count"] == 1 + assert payload["summary"]["dry_run_execution_envelope_freeze_proof_field_count"] == 12 + assert payload["summary"]["dry_run_executor_invoked_count"] == 0 + assert payload["summary"]["runner_invocation_performed_count"] == 0 + assert payload["summary"]["endpoint_executed_count"] == 0 + assert payload["summary"]["sql_executed_count"] == 0 + assert payload["summary"]["database_written_count"] == 0 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["controlled_executor_quarantine_bound"] is True + assert future["executor_quarantine_enforced"] is True + assert future["execution_envelope_frozen"] is True + assert future["execution_envelope_mutation_allowed"] is False + assert future["dry_run_executor_invoked"] is False + assert future["runner_invocation_performed"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["execution_receipt_present"] is False + assert future["dry_run_executor_invocation_allowed"] is False + assert future["runner_invocation_allowed"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert future["issues_database_apply_authorization"] is False + assert future["signs_database_apply_authorization"] is False + assert future["executes_authorization_evidence"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert future["stdout_included"] is False + assert future["stderr_included"] is False + assert quarantine_closeout["controlled_executor_quarantine_proof_closeout_only"] is True + assert quarantine_closeout["dry_run_execution_envelope_freeze_proof_only"] is True + assert quarantine_closeout["dry_run_only"] is True + assert quarantine_closeout["check_mode_only"] is True + assert quarantine_closeout["execution_envelope_frozen"] is True + assert quarantine_closeout["execution_envelope_mutation_allowed"] is False + assert quarantine_closeout["accepts_plaintext_secret"] is False + assert quarantine_closeout["reads_secret_in_preview"] is False + assert quarantine_closeout["signature_material_included"] is False + assert quarantine_closeout["secret_material_included"] is False + assert quarantine_closeout["signs_database_apply_authorization"] is False + assert quarantine_closeout["executes_authorization_evidence"] is False + assert quarantine_closeout["executes_database_apply"] is False + assert quarantine_closeout["executes_endpoint_in_preview"] is False + assert quarantine_closeout["executes_sql_in_preview"] is False + assert quarantine_closeout["writes_database_in_preview"] is False + assert quarantine_closeout["stdout_included"] is False + assert quarantine_closeout["stderr_included"] is False + assert freeze["freeze_mode"] == "dry_run_execution_envelope_freeze_proof_preview_only" + assert freeze["execution_envelope_frozen"] is True + assert freeze["execution_envelope_mutation_allowed"] is False + assert freeze["execution_receipt_present"] is False + assert freeze["execution_receipt_required"] is False + assert freeze["dry_run_executor_invoked"] is False + assert freeze["runner_invocation_performed"] is False + assert freeze["endpoint_executed"] is False + assert freeze["sql_executed"] is False + assert freeze["database_written"] is False + assert freeze["dry_run_executor_invocation_allowed"] is False + assert freeze["runner_invocation_allowed"] is False + assert freeze["ready_for_dry_run_executor_invocation_now"] is False + assert freeze["ready_for_actual_dry_run_execution_now"] is False + assert freeze["endpoint_execution_allowed"] is False + assert freeze["sql_execution_allowed"] is False + assert freeze["database_write_allowed"] is False + assert freeze["ready_for_database_apply_now"] is False + assert freeze["database_apply_authorized"] is False + assert freeze["executes_database_apply"] is False + assert freeze["executes_endpoint"] is False + assert freeze["executes_sql"] is False + assert freeze["writes_database"] is False + assert freeze["stdout_included"] is False + assert freeze["stderr_included"] is False + assert ( + payload[ + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_contract" + ]["ready_for_database_apply_now"] + is False + ) + assert ( + payload[ + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_contract" + ]["ready_for_dry_run_executor_invocation_now"] + is False + ) + assert ( + payload[ + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_contract" + ]["ready_for_actual_dry_run_execution_now"] + is False + ) + assert ( + payload[ + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_contract" + ]["executes_database_apply"] + is False + ) + assert ( + payload[ + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_contract" + ]["executes_endpoint"] + is False + ) + assert ( + payload[ + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_contract" + ]["executes_sql"] + is False + ) + assert ( + payload[ + "controlled_dry_run_controlled_executor_quarantine_proof_closeout_contract" + ]["database_apply_authorized"] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_execution_envelope_freeze_proof_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run execution envelope freeze proof closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-envelope-freeze-proof-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_execution_envelope_freeze_proof_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_frozen_envelope_verifier_handoff" + ] + freeze_closeout = payload[ + "controlled_dry_run_execution_envelope_freeze_proof_closeout" + ] + handoff = freeze_closeout["frozen_envelope_verifier_handoff"] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_execution_envelope_freeze_proof_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-controlled-executor-quarantine-proof-closeout" + ) + assert payload["summary"]["controlled_dry_run_execution_envelope_freeze_proof_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_controlled_executor_quarantine_proof_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_execution_envelope_freeze_proof_closeout_count"] == 1 + assert payload["summary"]["controlled_dry_run_execution_envelope_freeze_proof_closeout_field_count"] == 12 + assert payload["summary"]["controlled_dry_run_execution_envelope_freeze_proof_closeout_acceptance_gate_count"] == 10 + assert payload["summary"]["frozen_envelope_verifier_handoff_count"] == 1 + assert payload["summary"]["frozen_envelope_verifier_handoff_field_count"] == 12 + assert payload["summary"]["verifier_invoked_count"] == 0 + assert payload["summary"]["verifier_receipt_present_count"] == 0 + assert payload["summary"]["dry_run_executor_invoked_count"] == 0 + assert payload["summary"]["runner_invocation_performed_count"] == 0 + assert payload["summary"]["endpoint_executed_count"] == 0 + assert payload["summary"]["sql_executed_count"] == 0 + assert payload["summary"]["database_written_count"] == 0 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["execution_envelope_frozen"] is True + assert future["execution_envelope_mutation_allowed"] is False + assert future["verifier_invocation_allowed"] is False + assert future["verifier_invoked"] is False + assert future["verifier_receipt_present"] is False + assert future["dry_run_executor_invoked"] is False + assert future["runner_invocation_performed"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["ready_for_database_apply_now"] is False + assert future["ready_for_verifier_invocation_now"] is False + assert future["ready_for_dry_run_executor_invocation_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert future["stdout_included"] is False + assert future["stderr_included"] is False + assert freeze_closeout["execution_envelope_freeze_proof_closeout_only"] is True + assert freeze_closeout["frozen_envelope_verifier_handoff_only"] is True + assert freeze_closeout["dry_run_only"] is True + assert freeze_closeout["check_mode_only"] is True + assert freeze_closeout["execution_envelope_frozen"] is True + assert freeze_closeout["execution_envelope_mutation_allowed"] is False + assert freeze_closeout["verifier_invocation_allowed"] is False + assert freeze_closeout["verifier_invoked"] is False + assert freeze_closeout["verifier_receipt_present"] is False + assert freeze_closeout["accepts_plaintext_secret"] is False + assert freeze_closeout["reads_secret_in_preview"] is False + assert freeze_closeout["signature_material_included"] is False + assert freeze_closeout["secret_material_included"] is False + assert freeze_closeout["signs_database_apply_authorization"] is False + assert freeze_closeout["executes_authorization_evidence"] is False + assert freeze_closeout["executes_database_apply"] is False + assert freeze_closeout["executes_endpoint_in_preview"] is False + assert freeze_closeout["executes_sql_in_preview"] is False + assert freeze_closeout["writes_database_in_preview"] is False + assert handoff["verifier_handoff_mode"] == "frozen_envelope_verifier_handoff_preview_only" + assert handoff["execution_envelope_frozen"] is True + assert handoff["execution_envelope_mutation_allowed"] is False + assert handoff["verifier_invocation_allowed"] is False + assert handoff["verifier_invoked"] is False + assert handoff["verifier_receipt_present"] is False + assert handoff["dry_run_executor_invoked"] is False + assert handoff["runner_invocation_performed"] is False + assert handoff["endpoint_executed"] is False + assert handoff["sql_executed"] is False + assert handoff["database_written"] is False + assert handoff["ready_for_verifier_invocation_now"] is False + assert handoff["ready_for_dry_run_executor_invocation_now"] is False + assert handoff["endpoint_execution_allowed"] is False + assert handoff["sql_execution_allowed"] is False + assert handoff["database_write_allowed"] is False + assert handoff["ready_for_database_apply_now"] is False + assert handoff["database_apply_authorized"] is False + assert handoff["executes_database_apply"] is False + assert handoff["executes_endpoint"] is False + assert handoff["executes_sql"] is False + assert handoff["writes_database"] is False + assert ( + payload[ + "controlled_dry_run_execution_envelope_freeze_proof_closeout_contract" + ]["ready_for_database_apply_now"] + is False + ) + assert ( + payload[ + "controlled_dry_run_execution_envelope_freeze_proof_closeout_contract" + ]["ready_for_verifier_invocation_now"] + is False + ) + assert ( + payload[ + "controlled_dry_run_execution_envelope_freeze_proof_closeout_contract" + ]["executes_database_apply"] + is False + ) + assert ( + payload[ + "controlled_dry_run_execution_envelope_freeze_proof_closeout_contract" + ]["database_apply_authorized"] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_frozen_envelope_verifier_handoff_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run frozen envelope verifier handoff closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-frozen-envelope-verifier-handoff-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_frozen_envelope_verifier_handoff_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_verifier_invocation_lock_proof" + ] + handoff_closeout = payload[ + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout" + ] + lock = handoff_closeout["verifier_invocation_lock_proof"] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_frozen_envelope_verifier_handoff_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-envelope-freeze-proof-closeout" + ) + assert payload["summary"]["controlled_dry_run_frozen_envelope_verifier_handoff_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_execution_envelope_freeze_proof_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_frozen_envelope_verifier_handoff_closeout_count"] == 1 + assert payload["summary"]["controlled_dry_run_frozen_envelope_verifier_handoff_closeout_field_count"] == 12 + assert payload["summary"]["controlled_dry_run_frozen_envelope_verifier_handoff_closeout_acceptance_gate_count"] == 10 + assert payload["summary"]["verifier_invocation_lock_proof_count"] == 1 + assert payload["summary"]["verifier_invocation_lock_proof_field_count"] == 12 + assert payload["summary"]["verifier_invocation_locked_count"] == 1 + assert payload["summary"]["verifier_invoked_count"] == 0 + assert payload["summary"]["verifier_receipt_present_count"] == 0 + assert payload["summary"]["dry_run_executor_invoked_count"] == 0 + assert payload["summary"]["runner_invocation_performed_count"] == 0 + assert payload["summary"]["endpoint_executed_count"] == 0 + assert payload["summary"]["sql_executed_count"] == 0 + assert payload["summary"]["database_written_count"] == 0 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["verifier_invocation_locked"] is True + assert future["verifier_invocation_allowed"] is False + assert future["verifier_invoked"] is False + assert future["verifier_receipt_present"] is False + assert future["dry_run_executor_invoked"] is False + assert future["runner_invocation_performed"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["ready_for_database_apply_now"] is False + assert future["ready_for_verifier_invocation_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert future["stdout_included"] is False + assert future["stderr_included"] is False + assert handoff_closeout["frozen_envelope_verifier_handoff_closeout_only"] is True + assert handoff_closeout["verifier_invocation_lock_proof_only"] is True + assert handoff_closeout["dry_run_only"] is True + assert handoff_closeout["check_mode_only"] is True + assert handoff_closeout["verifier_invocation_locked"] is True + assert handoff_closeout["verifier_invocation_allowed"] is False + assert handoff_closeout["verifier_invoked"] is False + assert handoff_closeout["verifier_receipt_present"] is False + assert handoff_closeout["accepts_plaintext_secret"] is False + assert handoff_closeout["reads_secret_in_preview"] is False + assert handoff_closeout["signature_material_included"] is False + assert handoff_closeout["secret_material_included"] is False + assert handoff_closeout["signs_database_apply_authorization"] is False + assert handoff_closeout["executes_authorization_evidence"] is False + assert handoff_closeout["executes_database_apply"] is False + assert handoff_closeout["executes_endpoint_in_preview"] is False + assert handoff_closeout["executes_sql_in_preview"] is False + assert handoff_closeout["writes_database_in_preview"] is False + assert lock["lock_mode"] == "verifier_invocation_lock_proof_preview_only" + assert lock["verifier_invocation_locked"] is True + assert lock["verifier_invocation_allowed"] is False + assert lock["verifier_invoked"] is False + assert lock["verifier_receipt_present"] is False + assert lock["dry_run_executor_invoked"] is False + assert lock["runner_invocation_performed"] is False + assert lock["endpoint_executed"] is False + assert lock["sql_executed"] is False + assert lock["database_written"] is False + assert lock["ready_for_verifier_invocation_now"] is False + assert lock["endpoint_execution_allowed"] is False + assert lock["sql_execution_allowed"] is False + assert lock["database_write_allowed"] is False + assert lock["ready_for_database_apply_now"] is False + assert lock["database_apply_authorized"] is False + assert lock["executes_database_apply"] is False + assert lock["executes_endpoint"] is False + assert lock["executes_sql"] is False + assert lock["writes_database"] is False + assert ( + payload[ + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_contract" + ]["verifier_invocation_locked"] + is True + ) + assert ( + payload[ + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_contract" + ]["ready_for_database_apply_now"] + is False + ) + assert ( + payload[ + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_contract" + ]["ready_for_verifier_invocation_now"] + is False + ) + assert ( + payload[ + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_contract" + ]["executes_database_apply"] + is False + ) + assert ( + payload[ + "controlled_dry_run_frozen_envelope_verifier_handoff_closeout_contract" + ]["database_apply_authorized"] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_verifier_invocation_lock_proof_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run verifier invocation lock proof closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-invocation-lock-proof-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_verifier_invocation_lock_proof_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof" + ] + lock_closeout = payload[ + "controlled_dry_run_verifier_invocation_lock_proof_closeout" + ] + receipt = lock_closeout["verifier_no_execution_receipt_proof"] + contract = payload[ + "controlled_dry_run_verifier_invocation_lock_proof_closeout_contract" + ] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_verifier_invocation_lock_proof_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-frozen-envelope-verifier-handoff-closeout" + ) + assert payload["summary"]["controlled_dry_run_verifier_invocation_lock_proof_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_frozen_envelope_verifier_handoff_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_verifier_invocation_lock_proof_closeout_count"] == 1 + assert payload["summary"]["controlled_dry_run_verifier_invocation_lock_proof_closeout_field_count"] == 12 + assert payload["summary"]["controlled_dry_run_verifier_invocation_lock_proof_closeout_acceptance_gate_count"] == 10 + assert payload["summary"]["verifier_no_execution_receipt_proof_count"] == 1 + assert payload["summary"]["verifier_no_execution_receipt_proof_field_count"] == 12 + assert payload["summary"]["verifier_invocation_locked_count"] == 1 + assert payload["summary"]["verifier_invoked_count"] == 0 + assert payload["summary"]["verifier_receipt_present_count"] == 0 + assert payload["summary"]["dry_run_executor_invoked_count"] == 0 + assert payload["summary"]["runner_invocation_performed_count"] == 0 + assert payload["summary"]["endpoint_executed_count"] == 0 + assert payload["summary"]["sql_executed_count"] == 0 + assert payload["summary"]["database_written_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["executes_sql_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["verifier_invocation_locked"] is True + assert future["verifier_invocation_allowed"] is False + assert future["verifier_invoked"] is False + assert future["verifier_receipt_present"] is False + assert future["dry_run_executor_invoked"] is False + assert future["runner_invocation_performed"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["ready_for_database_apply_now"] is False + assert future["ready_for_verifier_invocation_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert lock_closeout["verifier_invocation_lock_proof_closeout_only"] is True + assert lock_closeout["verifier_no_execution_receipt_proof_only"] is True + assert lock_closeout["verifier_invocation_locked"] is True + assert lock_closeout["verifier_invocation_allowed"] is False + assert lock_closeout["verifier_invoked"] is False + assert lock_closeout["verifier_receipt_present"] is False + assert lock_closeout["dry_run_executor_invoked"] is False + assert lock_closeout["runner_invocation_performed"] is False + assert lock_closeout["endpoint_executed"] is False + assert lock_closeout["sql_executed"] is False + assert lock_closeout["database_written"] is False + assert receipt["receipt_mode"] == "verifier_no_execution_receipt_proof_preview_only" + assert receipt["verifier_invocation_locked"] is True + assert receipt["verifier_invocation_allowed"] is False + assert receipt["verifier_invoked"] is False + assert receipt["verifier_receipt_present"] is False + assert receipt["dry_run_executor_invoked"] is False + assert receipt["runner_invocation_performed"] is False + assert receipt["endpoint_executed"] is False + assert receipt["sql_executed"] is False + assert receipt["database_written"] is False + assert receipt["endpoint_execution_allowed"] is False + assert receipt["sql_execution_allowed"] is False + assert receipt["database_write_allowed"] is False + assert receipt["database_apply_authorized"] is False + assert receipt["executes_database_apply"] is False + assert receipt["executes_endpoint"] is False + assert receipt["executes_sql"] is False + assert receipt["writes_database"] is False + assert receipt["stdout_included"] is False + assert receipt["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_verifier_no_execution_receipt_proof" + ] + is False + ) + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_verifier_invocation_now"] is False + assert contract["executes_database_apply"] is False + assert contract["database_apply_authorized"] is False + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_verifier_no_execution_receipt_proof_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run verifier no-execution receipt proof closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-no-execution-receipt-proof-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_verifier_no_execution_receipt_proof_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof" + ] + receipt_closeout = payload[ + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout" + ] + guard = receipt_closeout["verifier_receipt_persistence_guard_proof"] + contract = payload[ + "controlled_dry_run_verifier_no_execution_receipt_proof_closeout_contract" + ] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_verifier_no_execution_receipt_proof_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-invocation-lock-proof-closeout" + ) + assert payload["summary"]["controlled_dry_run_verifier_no_execution_receipt_proof_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_verifier_invocation_lock_proof_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_verifier_no_execution_receipt_proof_closeout_count"] == 1 + assert payload["summary"]["controlled_dry_run_verifier_no_execution_receipt_proof_closeout_field_count"] == 12 + assert payload["summary"]["controlled_dry_run_verifier_no_execution_receipt_proof_closeout_acceptance_gate_count"] == 10 + assert payload["summary"]["verifier_receipt_persistence_guard_proof_count"] == 1 + assert payload["summary"]["verifier_receipt_persistence_guard_proof_field_count"] == 12 + assert payload["summary"]["verifier_receipt_persistence_locked_count"] == 1 + assert payload["summary"]["verifier_receipt_persistence_allowed_count"] == 0 + assert payload["summary"]["verifier_receipt_persisted_count"] == 0 + assert payload["summary"]["persists_verifier_receipt_count"] == 0 + assert payload["summary"]["verifier_invoked_count"] == 0 + assert payload["summary"]["verifier_receipt_present_count"] == 0 + assert payload["summary"]["dry_run_executor_invoked_count"] == 0 + assert payload["summary"]["runner_invocation_performed_count"] == 0 + assert payload["summary"]["endpoint_executed_count"] == 0 + assert payload["summary"]["sql_executed_count"] == 0 + assert payload["summary"]["database_written_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["executes_sql_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["verifier_receipt_persistence_locked"] is True + assert future["verifier_receipt_persistence_allowed"] is False + assert future["verifier_receipt_persisted"] is False + assert future["persists_verifier_receipt"] is False + assert future["verifier_invoked"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["ready_for_database_apply_now"] is False + assert future["ready_for_verifier_receipt_persistence_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert receipt_closeout["verifier_no_execution_receipt_proof_closeout_only"] is True + assert receipt_closeout["verifier_receipt_persistence_guard_proof_only"] is True + assert receipt_closeout["verifier_receipt_persistence_locked"] is True + assert receipt_closeout["verifier_receipt_persistence_allowed"] is False + assert receipt_closeout["verifier_receipt_persisted"] is False + assert receipt_closeout["persists_verifier_receipt"] is False + assert receipt_closeout["verifier_invoked"] is False + assert receipt_closeout["endpoint_executed"] is False + assert receipt_closeout["sql_executed"] is False + assert receipt_closeout["database_written"] is False + assert guard["guard_mode"] == "verifier_receipt_persistence_guard_proof_preview_only" + assert guard["verifier_receipt_persistence_locked"] is True + assert guard["verifier_receipt_persistence_allowed"] is False + assert guard["verifier_receipt_persisted"] is False + assert guard["persists_verifier_receipt"] is False + assert guard["endpoint_execution_allowed"] is False + assert guard["sql_execution_allowed"] is False + assert guard["database_write_allowed"] is False + assert guard["database_apply_authorized"] is False + assert guard["stdout_included"] is False + assert guard["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof" + ] + is False + ) + assert contract["verifier_receipt_persistence_locked"] is True + assert contract["verifier_receipt_persisted"] is False + assert contract["persists_verifier_receipt"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_verifier_receipt_persistence_now"] is False + assert contract["executes_database_apply"] is False + assert contract["database_apply_authorized"] is False + assert payload["safety"]["persists_verifier_receipt"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run verifier receipt persistence guard proof closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-receipt-persistence-guard-proof-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof" + ] + storage_closeout = payload[ + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout" + ] + storage = storage_closeout["receipt_persistence_storage_boundary_proof"] + contract = payload[ + "controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_contract" + ] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-no-execution-receipt-proof-closeout" + ) + assert payload["summary"]["controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_verifier_no_execution_receipt_proof_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_count"] == 1 + assert payload["summary"]["controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_field_count"] == 12 + assert payload["summary"]["controlled_dry_run_verifier_receipt_persistence_guard_proof_closeout_acceptance_gate_count"] == 10 + assert payload["summary"]["receipt_persistence_storage_boundary_proof_count"] == 1 + assert payload["summary"]["receipt_persistence_storage_boundary_proof_field_count"] == 12 + assert payload["summary"]["receipt_persistence_storage_boundary_locked_count"] == 1 + assert payload["summary"]["receipt_persistence_storage_write_allowed_count"] == 0 + assert payload["summary"]["receipt_persistence_storage_written_count"] == 0 + assert payload["summary"]["verifier_receipt_persistence_allowed_count"] == 0 + assert payload["summary"]["verifier_receipt_persisted_count"] == 0 + assert payload["summary"]["persists_verifier_receipt_count"] == 0 + assert payload["summary"]["endpoint_executed_count"] == 0 + assert payload["summary"]["sql_executed_count"] == 0 + assert payload["summary"]["database_written_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["executes_sql_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["receipt_persistence_storage_boundary_locked"] is True + assert future["receipt_persistence_storage_write_allowed"] is False + assert future["receipt_persistence_storage_written"] is False + assert future["verifier_receipt_persistence_allowed"] is False + assert future["verifier_receipt_persisted"] is False + assert future["persists_verifier_receipt"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["ready_for_database_apply_now"] is False + assert future["ready_for_receipt_persistence_storage_now"] is False + assert future["endpoint_execution_allowed"] is False + assert future["sql_execution_allowed"] is False + assert future["database_write_allowed"] is False + assert future["database_apply_authorized"] is False + assert storage_closeout["verifier_receipt_persistence_guard_proof_closeout_only"] is True + assert storage_closeout["receipt_persistence_storage_boundary_proof_only"] is True + assert storage_closeout["receipt_persistence_storage_boundary_locked"] is True + assert storage_closeout["receipt_persistence_storage_write_allowed"] is False + assert storage_closeout["receipt_persistence_storage_written"] is False + assert storage_closeout["persists_verifier_receipt"] is False + assert storage_closeout["endpoint_executed"] is False + assert storage_closeout["sql_executed"] is False + assert storage_closeout["database_written"] is False + assert storage["storage_boundary_mode"] == "receipt_persistence_storage_boundary_proof_preview_only" + assert storage["receipt_persistence_storage_boundary_locked"] is True + assert storage["receipt_persistence_storage_write_allowed"] is False + assert storage["receipt_persistence_storage_written"] is False + assert storage["verifier_receipt_persistence_allowed"] is False + assert storage["verifier_receipt_persisted"] is False + assert storage["persists_verifier_receipt"] is False + assert storage["endpoint_execution_allowed"] is False + assert storage["sql_execution_allowed"] is False + assert storage["database_write_allowed"] is False + assert storage["database_apply_authorized"] is False + assert storage["stdout_included"] is False + assert storage["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof" + ] + is False + ) + assert contract["receipt_persistence_storage_boundary_locked"] is True + assert contract["receipt_persistence_storage_write_allowed"] is False + assert contract["receipt_persistence_storage_written"] is False + assert contract["persists_verifier_receipt"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["ready_for_receipt_persistence_storage_now"] is False + assert contract["executes_database_apply"] is False + assert contract["database_apply_authorized"] is False + assert payload["safety"]["persists_verifier_receipt"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run receipt persistence storage boundary proof closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-receipt-persistence-storage-boundary-proof-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof" + ] + storage_closeout = payload[ + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout" + ] + ledger = storage_closeout["storage_boundary_no_write_ledger_proof"] + contract = payload[ + "controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_contract" + ] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-receipt-persistence-guard-proof-closeout" + ) + assert payload["summary"]["controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_count"] == 1 + assert payload["summary"]["controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_field_count"] == 12 + assert payload["summary"]["controlled_dry_run_receipt_persistence_storage_boundary_proof_closeout_acceptance_gate_count"] == 10 + assert payload["summary"]["storage_boundary_no_write_ledger_proof_count"] == 1 + assert payload["summary"]["storage_boundary_no_write_ledger_proof_field_count"] == 12 + assert payload["summary"]["storage_boundary_write_locked_count"] == 1 + assert payload["summary"]["storage_boundary_write_allowed_count"] == 0 + assert payload["summary"]["storage_boundary_written_count"] == 0 + assert payload["summary"]["ledger_write_allowed_count"] == 0 + assert payload["summary"]["ledger_written_count"] == 0 + assert payload["summary"]["receipt_persistence_storage_write_allowed_count"] == 0 + assert payload["summary"]["receipt_persistence_storage_written_count"] == 0 + assert payload["summary"]["persists_verifier_receipt_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["executes_sql_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert future["storage_boundary_write_locked"] is True + assert future["storage_boundary_write_allowed"] is False + assert future["storage_boundary_written"] is False + assert future["ledger_write_allowed"] is False + assert future["ledger_written"] is False + assert future["receipt_persistence_storage_write_allowed"] is False + assert future["receipt_persistence_storage_written"] is False + assert future["persists_verifier_receipt"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["database_apply_authorized"] is False + assert storage_closeout["receipt_persistence_storage_boundary_proof_closeout_only"] is True + assert storage_closeout["storage_boundary_no_write_ledger_proof_only"] is True + assert storage_closeout["storage_boundary_write_locked"] is True + assert storage_closeout["storage_boundary_write_allowed"] is False + assert storage_closeout["storage_boundary_written"] is False + assert storage_closeout["ledger_write_allowed"] is False + assert storage_closeout["ledger_written"] is False + assert storage_closeout["receipt_persistence_storage_write_allowed"] is False + assert storage_closeout["receipt_persistence_storage_written"] is False + assert storage_closeout["persists_verifier_receipt"] is False + assert storage_closeout["endpoint_executed"] is False + assert storage_closeout["sql_executed"] is False + assert storage_closeout["database_written"] is False + assert ledger["ledger_mode"] == "storage_boundary_no_write_ledger_proof_preview_only" + assert ledger["storage_boundary_write_allowed"] is False + assert ledger["storage_boundary_written"] is False + assert ledger["ledger_write_allowed"] is False + assert ledger["ledger_written"] is False + assert ledger["receipt_persistence_storage_write_allowed"] is False + assert ledger["receipt_persistence_storage_written"] is False + assert ledger["persists_verifier_receipt"] is False + assert ledger["endpoint_execution_allowed"] is False + assert ledger["sql_execution_allowed"] is False + assert ledger["database_write_allowed"] is False + assert ledger["database_apply_authorized"] is False + assert ledger["stdout_included"] is False + assert ledger["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof" + ] + is False + ) + assert contract["storage_boundary_write_allowed"] is False + assert contract["ledger_write_allowed"] is False + assert contract["receipt_persistence_storage_write_allowed"] is False + assert contract["persists_verifier_receipt"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["executes_database_apply"] is False + assert contract["database_apply_authorized"] is False + assert payload["safety"]["persists_verifier_receipt"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run storage boundary no-write ledger proof closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-storage-boundary-no-write-ledger-proof-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_no_write_ledger_retention_proof" + ] + ledger_closeout = payload[ + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout" + ] + retention = ledger_closeout["no_write_ledger_retention_proof"] + contract = payload[ + "controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_contract" + ] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-receipt-persistence-storage-boundary-proof-closeout" + ) + assert payload["summary"]["controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_count"] == 1 + assert payload["summary"]["controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_field_count"] == 12 + assert payload["summary"]["controlled_dry_run_storage_boundary_no_write_ledger_proof_closeout_acceptance_gate_count"] == 10 + assert payload["summary"]["no_write_ledger_retention_proof_count"] == 1 + assert payload["summary"]["no_write_ledger_retention_proof_field_count"] == 12 + assert payload["summary"]["ledger_retention_write_locked_count"] == 1 + assert payload["summary"]["ledger_retention_write_allowed_count"] == 0 + assert payload["summary"]["ledger_retention_written_count"] == 0 + assert payload["summary"]["ledger_write_allowed_count"] == 0 + assert payload["summary"]["ledger_written_count"] == 0 + assert payload["summary"]["receipt_persistence_storage_write_allowed_count"] == 0 + assert payload["summary"]["receipt_persistence_storage_written_count"] == 0 + assert payload["summary"]["persists_verifier_receipt_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["executes_sql_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert future["ledger_retention_write_locked"] is True + assert future["ledger_retention_write_allowed"] is False + assert future["ledger_retention_written"] is False + assert future["ledger_write_allowed"] is False + assert future["ledger_written"] is False + assert future["persists_verifier_receipt"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["database_apply_authorized"] is False + assert ledger_closeout["storage_boundary_no_write_ledger_proof_closeout_only"] is True + assert ledger_closeout["no_write_ledger_retention_proof_only"] is True + assert ledger_closeout["ledger_retention_write_locked"] is True + assert ledger_closeout["ledger_retention_write_allowed"] is False + assert ledger_closeout["ledger_retention_written"] is False + assert ledger_closeout["ledger_write_allowed"] is False + assert ledger_closeout["ledger_written"] is False + assert ledger_closeout["persists_verifier_receipt"] is False + assert ledger_closeout["endpoint_executed"] is False + assert ledger_closeout["sql_executed"] is False + assert ledger_closeout["database_written"] is False + assert retention["retention_mode"] == "no_write_ledger_retention_proof_preview_only" + assert retention["ledger_retention_write_allowed"] is False + assert retention["ledger_retention_written"] is False + assert retention["ledger_write_allowed"] is False + assert retention["ledger_written"] is False + assert retention["receipt_persistence_storage_write_allowed"] is False + assert retention["receipt_persistence_storage_written"] is False + assert retention["persists_verifier_receipt"] is False + assert retention["endpoint_execution_allowed"] is False + assert retention["sql_execution_allowed"] is False + assert retention["database_write_allowed"] is False + assert retention["database_apply_authorized"] is False + assert retention["stdout_included"] is False + assert retention["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_no_write_ledger_retention_proof" + ] + is False + ) + assert contract["ledger_retention_write_allowed"] is False + assert contract["ledger_retention_written"] is False + assert contract["ledger_write_allowed"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["executes_database_apply"] is False + assert contract["database_apply_authorized"] is False + assert payload["safety"]["persists_verifier_receipt"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_no_write_ledger_retention_proof_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run no-write ledger retention proof closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-write-ledger-retention-proof-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_no_write_ledger_retention_proof_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof" + ] + archive_closeout = payload[ + "controlled_dry_run_no_write_ledger_retention_proof_closeout" + ] + archive = archive_closeout["retention_boundary_no_write_archive_proof"] + contract = payload[ + "controlled_dry_run_no_write_ledger_retention_proof_closeout_contract" + ] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_no_write_ledger_retention_proof_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-storage-boundary-no-write-ledger-proof-closeout" + ) + assert payload["summary"]["controlled_dry_run_no_write_ledger_retention_proof_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_no_write_ledger_retention_proof_closeout_count"] == 1 + assert payload["summary"]["controlled_dry_run_no_write_ledger_retention_proof_closeout_field_count"] == 12 + assert payload["summary"]["controlled_dry_run_no_write_ledger_retention_proof_closeout_acceptance_gate_count"] == 10 + assert payload["summary"]["retention_boundary_no_write_archive_proof_count"] == 1 + assert payload["summary"]["retention_boundary_no_write_archive_proof_field_count"] == 12 + assert payload["summary"]["retention_archive_write_locked_count"] == 1 + assert payload["summary"]["retention_archive_write_allowed_count"] == 0 + assert payload["summary"]["retention_archive_written_count"] == 0 + assert payload["summary"]["ledger_retention_write_allowed_count"] == 0 + assert payload["summary"]["ledger_retention_written_count"] == 0 + assert payload["summary"]["ledger_write_allowed_count"] == 0 + assert payload["summary"]["ledger_written_count"] == 0 + assert payload["summary"]["persists_verifier_receipt_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["executes_sql_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert future["retention_archive_write_locked"] is True + assert future["retention_archive_write_allowed"] is False + assert future["retention_archive_written"] is False + assert future["ledger_retention_write_allowed"] is False + assert future["ledger_retention_written"] is False + assert future["ledger_write_allowed"] is False + assert future["ledger_written"] is False + assert future["persists_verifier_receipt"] is False + assert future["endpoint_executed"] is False + assert future["sql_executed"] is False + assert future["database_written"] is False + assert future["database_apply_authorized"] is False + assert archive_closeout["no_write_ledger_retention_proof_closeout_only"] is True + assert archive_closeout["retention_boundary_no_write_archive_proof_only"] is True + assert archive_closeout["retention_archive_write_locked"] is True + assert archive_closeout["retention_archive_write_allowed"] is False + assert archive_closeout["retention_archive_written"] is False + assert archive_closeout["ledger_retention_write_allowed"] is False + assert archive_closeout["ledger_retention_written"] is False + assert archive_closeout["persists_verifier_receipt"] is False + assert archive_closeout["endpoint_executed"] is False + assert archive_closeout["sql_executed"] is False + assert archive_closeout["database_written"] is False + assert archive["archive_mode"] == "retention_boundary_no_write_archive_proof_preview_only" + assert archive["retention_archive_write_allowed"] is False + assert archive["retention_archive_written"] is False + assert archive["ledger_retention_write_allowed"] is False + assert archive["ledger_retention_written"] is False + assert archive["ledger_write_allowed"] is False + assert archive["ledger_written"] is False + assert archive["persists_verifier_receipt"] is False + assert archive["endpoint_execution_allowed"] is False + assert archive["sql_execution_allowed"] is False + assert archive["database_write_allowed"] is False + assert archive["database_apply_authorized"] is False + assert archive["stdout_included"] is False + assert archive["stderr_included"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_retention_boundary_no_write_archive_proof" + ] + is False + ) + assert contract["retention_archive_write_allowed"] is False + assert contract["ledger_retention_write_allowed"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["executes_database_apply"] is False + assert contract["database_apply_authorized"] is False + assert payload["safety"]["persists_verifier_receipt"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run retention boundary no-write archive proof closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-retention-boundary-no-write-archive-proof-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload["future_readiness"] + handoff = payload["sealed_handoff_proof"] + contract = payload["contract"] + assert payload["success"] is True + assert payload["response_mode"] == "compact" + assert payload["full_payload_hint"] == "append full=1 for the complete nested proof payload" + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-write-ledger-retention-proof-closeout" + ) + assert "source_controlled_dry_run_no_write_ledger_retention_proof_closeout" not in payload + assert ( + "future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof" + not in payload + ) + assert ( + "controlled_dry_run_retention_boundary_no_write_archive_proof_closeout" + not in payload + ) + assert payload["summary"]["controlled_dry_run_retention_boundary_no_write_archive_proof_closeout_check_count"] == 12 + assert payload["summary"]["archive_retention_sealed_handoff_proof_count"] == 1 + assert payload["summary"]["archive_retention_sealed_handoff_proof_field_count"] == 12 + assert payload["summary"]["sealed_handoff_write_locked_count"] == 1 + assert payload["summary"]["sealed_handoff_write_allowed_count"] == 0 + assert payload["summary"]["sealed_handoff_written_count"] == 0 + assert payload["summary"]["retention_archive_write_allowed_count"] == 0 + assert payload["summary"]["retention_archive_written_count"] == 0 + assert payload["summary"]["ledger_retention_write_allowed_count"] == 0 + assert payload["summary"]["ledger_retention_written_count"] == 0 + assert payload["summary"]["ledger_write_allowed_count"] == 0 + assert payload["summary"]["ledger_written_count"] == 0 + assert payload["summary"]["persists_verifier_receipt_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["executes_sql_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof" + ] + is False + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof_closeout" + ] + is False + ) + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert handoff["handoff_mode"] == "archive_retention_sealed_handoff_proof_preview_only" + assert len(handoff["sealed_handoff_manifest_hash"]) == 64 + assert handoff["sealed_handoff_write_locked"] is True + assert handoff["sealed_handoff_write_allowed"] is False + assert handoff["sealed_handoff_written"] is False + assert handoff["retention_archive_write_allowed"] is False + assert handoff["retention_archive_written"] is False + assert handoff["ledger_retention_write_allowed"] is False + assert handoff["ledger_retention_written"] is False + assert handoff["ledger_write_allowed"] is False + assert handoff["ledger_written"] is False + assert handoff["persists_verifier_receipt"] is False + assert handoff["endpoint_executed"] is False + assert handoff["sql_executed"] is False + assert handoff["database_written"] is False + assert handoff["database_apply_authorized"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_archive_retention_sealed_handoff_proof" + ] + is False + ) + assert contract["sealed_handoff_write_allowed"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["executes_database_apply"] is False + assert contract["database_apply_authorized"] is False + assert len(payload["checks"]) == 12 + assert payload["safety"]["persists_verifier_receipt"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_route_defaults_to_compact_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run archive retention sealed handoff proof closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-archive-retention-sealed-handoff-proof-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_archive_retention_sealed_handoff_proof_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload["future_readiness"] + transfer = payload["sealed_handoff_verifier_transfer_proof"] + contract = payload["contract"] + assert payload["success"] is True + assert payload["response_mode"] == "compact" + assert payload["full_payload_hint"] == "append full=1 for the complete nested proof payload" + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_archive_retention_sealed_handoff_proof_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-retention-boundary-no-write-archive-proof-closeout" + ) + assert ( + "source_controlled_dry_run_retention_boundary_no_write_archive_proof_closeout" + not in payload + ) + assert ( + "future_database_apply_controlled_dry_run_sealed_handoff_verifier_transfer_proof" + not in payload + ) + assert ( + "controlled_dry_run_archive_retention_sealed_handoff_proof_closeout" + not in payload + ) + assert payload["summary"]["controlled_dry_run_archive_retention_sealed_handoff_proof_closeout_check_count"] == 12 + assert payload["summary"]["sealed_handoff_verifier_transfer_proof_count"] == 1 + assert payload["summary"]["sealed_handoff_verifier_transfer_proof_field_count"] == 12 + assert payload["summary"]["verifier_transfer_write_locked_count"] == 1 + assert payload["summary"]["verifier_transfer_write_allowed_count"] == 0 + assert payload["summary"]["verifier_transfer_written_count"] == 0 + assert payload["summary"]["persists_verifier_receipt_count"] == 0 + assert payload["summary"]["verifier_invoked_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["executes_sql_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert ( + future[ + "ready_for_future_database_apply_controlled_dry_run_sealed_handoff_verifier_transfer_proof" + ] + is False + ) + assert ( + future[ + "can_enter_future_database_apply_controlled_dry_run_sealed_handoff_verifier_transfer_proof_closeout" + ] + is False + ) + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert transfer["verifier_transfer_mode"] == "sealed_handoff_verifier_transfer_proof_preview_only" + assert len(transfer["verifier_transfer_manifest_hash"]) == 64 + assert transfer["verifier_transfer_write_locked"] is True + assert transfer["verifier_transfer_write_allowed"] is False + assert transfer["verifier_transfer_written"] is False + assert transfer["verifier_invocation_allowed"] is False + assert transfer["verifier_invoked"] is False + assert transfer["persists_verifier_receipt"] is False + assert transfer["endpoint_executed"] is False + assert transfer["sql_executed"] is False + assert transfer["database_written"] is False + assert transfer["database_apply_authorized"] is False + assert ( + contract[ + "permits_future_database_apply_controlled_dry_run_sealed_handoff_verifier_transfer_proof" + ] + is False + ) + assert contract["verifier_transfer_write_allowed"] is False + assert contract["verifier_invocation_allowed"] is False + assert contract["ready_for_database_apply_now"] is False + assert contract["executes_database_apply"] is False + assert contract["database_apply_authorized"] is False + assert len(payload["checks"]) == 12 + assert payload["safety"]["persists_verifier_receipt"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_package_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run package should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-package?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_package + .__wrapped__() + ) + + payload = response.get_json() + receipt = payload[ + "future_database_apply_controlled_dry_run_execution_receipt" + ] + package = payload["controlled_dry_run_package"] + command_shape = package["dry_run_command_shape"] + receipt_preview = package["dry_run_execution_receipt_preview"] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_package" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-apply-final-preflight" + ) + assert payload["summary"]["controlled_dry_run_package_check_count"] == 12 + assert payload["summary"]["controlled_apply_final_preflight_check_count"] == 12 + assert payload["summary"]["authorization_evidence_execution_closeout_check_count"] == 12 + assert payload["summary"]["authorization_evidence_execution_preflight_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_package_count"] == 1 + assert payload["summary"]["controlled_dry_run_package_field_count"] == 12 + assert payload["summary"]["controlled_dry_run_acceptance_gate_count"] == 10 + assert payload["summary"]["dry_run_execution_receipt_preview_count"] == 1 + assert payload["summary"]["dry_run_execution_receipt_field_count"] == 8 + assert payload["summary"]["rollback_binding_count"] == 1 + assert payload["summary"]["post_apply_verifier_binding_count"] == 1 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert receipt["dry_run_execution_performed"] is False + assert receipt["ready_for_database_apply_now"] is False + assert receipt["database_apply_authorized"] is False + assert receipt["issues_database_apply_authorization"] is False + assert receipt["signs_database_apply_authorization"] is False + assert receipt["executes_authorization_evidence"] is False + assert receipt["executes_database_apply"] is False + assert receipt["executes_endpoint"] is False + assert receipt["executes_sql"] is False + assert receipt["writes_database"] is False + assert package["dry_run_only"] is True + assert package["check_mode_only"] is True + assert package["accepts_plaintext_secret"] is False + assert package["reads_secret_in_preview"] is False + assert package["signature_material_included"] is False + assert package["secret_material_included"] is False + assert package["signs_database_apply_authorization"] is False + assert package["executes_authorization_evidence"] is False + assert package["executes_database_apply"] is False + assert package["executes_endpoint_in_preview"] is False + assert package["executes_sql_in_preview"] is False + assert package["writes_database_in_preview"] is False + assert package["ready_for_database_apply_now"] is False + assert package["database_apply_authorized"] is False + assert command_shape["execution_allowed"] is False + assert command_shape["shell_command_included"] is False + assert command_shape["sql_included"] is False + assert command_shape["endpoint_execution_included"] is False + assert command_shape["database_write_included"] is False + assert receipt_preview["execution_performed"] is False + assert receipt_preview["stdout_included"] is False + assert receipt_preview["stderr_included"] is False + assert receipt_preview["database_apply_authorized"] is False + assert receipt_preview["executes_shell"] is False + assert receipt_preview["executes_endpoint"] is False + assert receipt_preview["executes_sql"] is False + assert receipt_preview["writes_database"] is False + assert receipt_preview["reads_secret"] is False + assert ( + payload["controlled_dry_run_package_contract"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["controlled_dry_run_package_contract"]["executes_database_apply"] + is False + ) + assert ( + payload["controlled_dry_run_package_contract"]["executes_endpoint"] + is False + ) + assert payload["controlled_dry_run_package_contract"]["executes_sql"] is False + assert ( + payload["controlled_dry_run_package_contract"]["database_apply_authorized"] + is False + ) + assert ( + payload["controlled_dry_run_package_contract"][ + "signs_database_apply_authorization" + ] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + assert payload["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_receipt_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run receipt closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-receipt-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_receipt_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_result_parser_verification" + ] + closeout = payload["controlled_dry_run_receipt_closeout"] + parser = closeout["dry_run_result_parser"] + validation = closeout["receipt_validation_report"] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_receipt_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-package" + ) + assert payload["summary"]["controlled_dry_run_receipt_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_package_check_count"] == 12 + assert payload["summary"]["controlled_apply_final_preflight_check_count"] == 12 + assert payload["summary"]["authorization_evidence_execution_closeout_check_count"] == 12 + assert payload["summary"]["authorization_evidence_execution_preflight_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_receipt_closeout_count"] == 1 + assert payload["summary"]["controlled_dry_run_receipt_closeout_field_count"] == 12 + assert payload["summary"]["controlled_dry_run_receipt_closeout_acceptance_gate_count"] == 10 + assert payload["summary"]["dry_run_result_parser_count"] == 1 + assert payload["summary"]["dry_run_result_parser_field_count"] == 10 + assert payload["summary"]["receipt_validation_report_count"] == 1 + assert payload["summary"]["receipt_validation_field_count"] == 8 + assert payload["summary"]["dry_run_execution_receipt_preview_count"] == 1 + assert payload["summary"]["dry_run_execution_receipt_field_count"] == 8 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["dry_run_execution_performed"] is False + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert future["issues_database_apply_authorization"] is False + assert future["signs_database_apply_authorization"] is False + assert future["executes_authorization_evidence"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert parser["execution_required"] is False + assert parser["stdout_allowed"] is False + assert parser["stderr_allowed"] is False + assert parser["database_apply_authorized"] is False + assert parser["parser_field_count"] == 10 + assert validation["execution_performed"] is False + assert validation["stdout_included"] is False + assert validation["stderr_included"] is False + assert validation["database_apply_authorized"] is False + assert validation["executes_shell"] is False + assert validation["executes_endpoint"] is False + assert validation["executes_sql"] is False + assert validation["writes_database"] is False + assert validation["reads_secret"] is False + assert closeout["receipt_preview_only"] is True + assert closeout["dry_run_only"] is True + assert closeout["check_mode_only"] is True + assert closeout["accepts_plaintext_secret"] is False + assert closeout["reads_secret_in_preview"] is False + assert closeout["signature_material_included"] is False + assert closeout["secret_material_included"] is False + assert closeout["signs_database_apply_authorization"] is False + assert closeout["executes_authorization_evidence"] is False + assert closeout["executes_database_apply"] is False + assert closeout["executes_endpoint_in_preview"] is False + assert closeout["executes_sql_in_preview"] is False + assert closeout["writes_database_in_preview"] is False + assert closeout["ready_for_database_apply_now"] is False + assert closeout["database_apply_authorized"] is False + assert ( + payload["controlled_dry_run_receipt_closeout_contract"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["controlled_dry_run_receipt_closeout_contract"][ + "executes_database_apply" + ] + is False + ) + assert ( + payload["controlled_dry_run_receipt_closeout_contract"]["executes_endpoint"] + is False + ) + assert ( + payload["controlled_dry_run_receipt_closeout_contract"]["executes_sql"] + is False + ) + assert ( + payload["controlled_dry_run_receipt_closeout_contract"][ + "database_apply_authorized" + ] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + assert payload["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_runner_readiness_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run runner readiness should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-readiness?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_runner_readiness + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_execution_plan_binding" + ] + runner = payload["controlled_dry_run_runner_readiness"] + plan = runner["execution_plan_binding"] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_runner_readiness" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-receipt-closeout" + ) + assert payload["summary"]["controlled_dry_run_runner_readiness_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_receipt_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_package_check_count"] == 12 + assert payload["summary"]["controlled_apply_final_preflight_check_count"] == 12 + assert payload["summary"]["authorization_evidence_execution_closeout_check_count"] == 12 + assert payload["summary"]["authorization_evidence_execution_preflight_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_runner_readiness_count"] == 1 + assert payload["summary"]["controlled_dry_run_runner_readiness_field_count"] == 12 + assert ( + payload["summary"][ + "controlled_dry_run_runner_readiness_acceptance_gate_count" + ] + == 10 + ) + assert payload["summary"]["execution_plan_binding_count"] == 1 + assert payload["summary"]["execution_plan_binding_field_count"] == 12 + assert payload["summary"]["dry_run_result_parser_count"] == 1 + assert payload["summary"]["receipt_validation_report_count"] == 1 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["dry_run_execution_performed"] is False + assert future["runner_execution_authorized"] is False + assert future["dry_run_execution_authorized"] is False + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert future["issues_database_apply_authorization"] is False + assert future["signs_database_apply_authorization"] is False + assert future["executes_authorization_evidence"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert runner["runner_readiness_only"] is True + assert runner["execution_plan_preview_only"] is True + assert runner["runner_execution_authorized"] is False + assert runner["dry_run_execution_authorized"] is False + assert runner["dry_run_only"] is True + assert runner["check_mode_only"] is True + assert runner["accepts_plaintext_secret"] is False + assert runner["reads_secret_in_preview"] is False + assert runner["signature_material_included"] is False + assert runner["secret_material_included"] is False + assert runner["signs_database_apply_authorization"] is False + assert runner["executes_authorization_evidence"] is False + assert runner["executes_database_apply"] is False + assert runner["executes_endpoint_in_preview"] is False + assert runner["executes_sql_in_preview"] is False + assert runner["writes_database_in_preview"] is False + assert runner["ready_for_database_apply_now"] is False + assert runner["database_apply_authorized"] is False + assert plan["execution_authorized"] is False + assert plan["runner_execution_authorized"] is False + assert plan["dry_run_execution_authorized"] is False + assert plan["shell_execution_included"] is False + assert plan["endpoint_execution_included"] is False + assert plan["sql_execution_included"] is False + assert plan["database_write_included"] is False + assert plan["stdout_capture_allowed"] is False + assert plan["stderr_capture_allowed"] is False + assert plan["database_apply_authorized"] is False + assert ( + payload["controlled_dry_run_runner_readiness_contract"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["controlled_dry_run_runner_readiness_contract"][ + "executes_database_apply" + ] + is False + ) + assert ( + payload["controlled_dry_run_runner_readiness_contract"]["executes_endpoint"] + is False + ) + assert ( + payload["controlled_dry_run_runner_readiness_contract"]["executes_sql"] + is False + ) + assert ( + payload["controlled_dry_run_runner_readiness_contract"][ + "database_apply_authorized" + ] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + assert payload["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_execution_plan_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run execution plan closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-plan-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_execution_plan_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_command_artifact_verification" + ] + plan_closeout = payload["controlled_dry_run_execution_plan_closeout"] + artifact = plan_closeout["non_executable_command_artifact"] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_execution_plan_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-readiness" + ) + assert ( + payload["summary"][ + "controlled_dry_run_execution_plan_closeout_check_count" + ] + == 12 + ) + assert payload["summary"]["controlled_dry_run_runner_readiness_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_receipt_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_package_check_count"] == 12 + assert payload["summary"]["controlled_apply_final_preflight_check_count"] == 12 + assert payload["summary"]["authorization_evidence_execution_closeout_check_count"] == 12 + assert payload["summary"]["authorization_evidence_execution_preflight_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_execution_plan_closeout_count"] == 1 + assert ( + payload["summary"][ + "controlled_dry_run_execution_plan_closeout_field_count" + ] + == 12 + ) + assert ( + payload["summary"][ + "controlled_dry_run_execution_plan_closeout_acceptance_gate_count" + ] + == 10 + ) + assert payload["summary"]["non_executable_command_artifact_count"] == 1 + assert payload["summary"]["non_executable_command_artifact_field_count"] == 10 + assert payload["summary"]["execution_plan_binding_count"] == 1 + assert payload["summary"]["execution_plan_binding_field_count"] == 12 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["runner_execution_authorized"] is False + assert future["dry_run_execution_authorized"] is False + assert future["execution_authorized"] is False + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert future["issues_database_apply_authorization"] is False + assert future["signs_database_apply_authorization"] is False + assert future["executes_authorization_evidence"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert plan_closeout["execution_plan_closeout_only"] is True + assert plan_closeout["non_executable_command_artifact_only"] is True + assert plan_closeout["runner_execution_authorized"] is False + assert plan_closeout["dry_run_execution_authorized"] is False + assert plan_closeout["execution_authorized"] is False + assert plan_closeout["dry_run_only"] is True + assert plan_closeout["check_mode_only"] is True + assert plan_closeout["accepts_plaintext_secret"] is False + assert plan_closeout["reads_secret_in_preview"] is False + assert plan_closeout["signature_material_included"] is False + assert plan_closeout["secret_material_included"] is False + assert plan_closeout["signs_database_apply_authorization"] is False + assert plan_closeout["executes_authorization_evidence"] is False + assert plan_closeout["executes_database_apply"] is False + assert plan_closeout["executes_endpoint_in_preview"] is False + assert plan_closeout["executes_sql_in_preview"] is False + assert plan_closeout["writes_database_in_preview"] is False + assert plan_closeout["ready_for_database_apply_now"] is False + assert plan_closeout["database_apply_authorized"] is False + assert artifact["command_text_included"] is False + assert artifact["argv_included"] is False + assert artifact.get("command_text") is None + assert artifact.get("argv") is None + assert artifact["shell_command_included"] is False + assert artifact["endpoint_execution_included"] is False + assert artifact["sql_execution_included"] is False + assert artifact["database_write_included"] is False + assert artifact["execution_authorized"] is False + assert artifact["database_apply_authorized"] is False + assert len(artifact["non_executable_command_artifact_sha256"]) == 64 + assert ( + payload["controlled_dry_run_execution_plan_closeout_contract"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["controlled_dry_run_execution_plan_closeout_contract"][ + "executes_database_apply" + ] + is False + ) + assert ( + payload["controlled_dry_run_execution_plan_closeout_contract"][ + "executes_endpoint" + ] + is False + ) + assert ( + payload["controlled_dry_run_execution_plan_closeout_contract"][ + "executes_sql" + ] + is False + ) + assert ( + payload["controlled_dry_run_execution_plan_closeout_contract"][ + "database_apply_authorized" + ] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + assert payload["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_command_artifact_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run command artifact closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-command-artifact-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_command_artifact_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_runner_execution_receipt_preflight" + ] + command_closeout = payload["controlled_dry_run_command_artifact_closeout"] + receipt_preflight = command_closeout["runner_execution_receipt_preflight"] + artifact = command_closeout["non_executable_command_artifact"] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_command_artifact_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-plan-closeout" + ) + assert ( + payload["summary"][ + "controlled_dry_run_command_artifact_closeout_check_count" + ] + == 12 + ) + assert payload["summary"]["controlled_dry_run_execution_plan_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_runner_readiness_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_receipt_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_package_check_count"] == 12 + assert payload["summary"]["controlled_apply_final_preflight_check_count"] == 12 + assert payload["summary"]["authorization_evidence_execution_closeout_check_count"] == 12 + assert payload["summary"]["authorization_evidence_execution_preflight_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_command_artifact_closeout_count"] == 1 + assert payload["summary"]["controlled_dry_run_command_artifact_closeout_field_count"] == 12 + assert ( + payload["summary"][ + "controlled_dry_run_command_artifact_closeout_acceptance_gate_count" + ] + == 10 + ) + assert payload["summary"]["runner_execution_receipt_preflight_count"] == 1 + assert payload["summary"]["runner_execution_receipt_preflight_field_count"] == 10 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["runner_execution_authorized"] is False + assert future["dry_run_execution_authorized"] is False + assert future["execution_authorized"] is False + assert future["stdout_capture_allowed"] is False + assert future["stderr_capture_allowed"] is False + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert future["issues_database_apply_authorization"] is False + assert future["signs_database_apply_authorization"] is False + assert future["executes_authorization_evidence"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert command_closeout["command_artifact_closeout_only"] is True + assert command_closeout["runner_execution_receipt_preflight_only"] is True + assert command_closeout["runner_execution_authorized"] is False + assert command_closeout["dry_run_execution_authorized"] is False + assert command_closeout["execution_authorized"] is False + assert command_closeout["dry_run_only"] is True + assert command_closeout["check_mode_only"] is True + assert command_closeout["accepts_plaintext_secret"] is False + assert command_closeout["reads_secret_in_preview"] is False + assert command_closeout["signature_material_included"] is False + assert command_closeout["secret_material_included"] is False + assert command_closeout["signs_database_apply_authorization"] is False + assert command_closeout["executes_authorization_evidence"] is False + assert command_closeout["executes_database_apply"] is False + assert command_closeout["executes_endpoint_in_preview"] is False + assert command_closeout["executes_sql_in_preview"] is False + assert command_closeout["writes_database_in_preview"] is False + assert command_closeout["ready_for_database_apply_now"] is False + assert command_closeout["database_apply_authorized"] is False + assert artifact["command_text_included"] is False + assert artifact["argv_included"] is False + assert artifact.get("command_text") is None + assert artifact.get("argv") is None + assert artifact["shell_command_included"] is False + assert artifact["endpoint_execution_included"] is False + assert artifact["sql_execution_included"] is False + assert artifact["database_write_included"] is False + assert receipt_preflight["preflight_status"] == "preflight_only_not_executed" + assert receipt_preflight["execution_required"] is False + assert receipt_preflight["execution_authorized"] is False + assert receipt_preflight["runner_execution_authorized"] is False + assert receipt_preflight["stdout_capture_allowed"] is False + assert receipt_preflight["stderr_capture_allowed"] is False + assert receipt_preflight["execution_performed"] is False + assert receipt_preflight["writes_database"] is False + assert ( + payload["controlled_dry_run_command_artifact_closeout_contract"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["controlled_dry_run_command_artifact_closeout_contract"][ + "executes_database_apply" + ] + is False + ) + assert ( + payload["controlled_dry_run_command_artifact_closeout_contract"][ + "executes_endpoint" + ] + is False + ) + assert ( + payload["controlled_dry_run_command_artifact_closeout_contract"][ + "executes_sql" + ] + is False + ) + assert ( + payload["controlled_dry_run_command_artifact_closeout_contract"][ + "database_apply_authorized" + ] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + assert payload["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_controlled_dry_run_runner_execution_receipt_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply controlled dry-run runner execution receipt closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-execution-receipt-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_controlled_dry_run_runner_execution_receipt_closeout + .__wrapped__() + ) + + payload = response.get_json() + future = payload[ + "future_database_apply_controlled_dry_run_post_receipt_parser_verification" + ] + receipt_closeout = payload[ + "controlled_dry_run_runner_execution_receipt_closeout" + ] + preview = receipt_closeout["receipt_closeout_preview"] + parser = receipt_closeout["post_receipt_parser_verification"] + preflight = receipt_closeout["runner_execution_receipt_preflight"] + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_controlled_dry_run_runner_execution_receipt_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-command-artifact-closeout" + ) + assert ( + payload["summary"][ + "controlled_dry_run_runner_execution_receipt_closeout_check_count" + ] + == 12 + ) + assert payload["summary"]["controlled_dry_run_command_artifact_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_execution_plan_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_runner_readiness_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_receipt_closeout_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_package_check_count"] == 12 + assert payload["summary"]["controlled_apply_final_preflight_check_count"] == 12 + assert payload["summary"]["authorization_evidence_execution_closeout_check_count"] == 12 + assert payload["summary"]["authorization_evidence_execution_preflight_check_count"] == 12 + assert payload["summary"]["controlled_dry_run_runner_execution_receipt_closeout_count"] == 1 + assert ( + payload["summary"][ + "controlled_dry_run_runner_execution_receipt_closeout_field_count" + ] + == 12 + ) + assert ( + payload["summary"][ + "controlled_dry_run_runner_execution_receipt_closeout_acceptance_gate_count" + ] + == 10 + ) + assert payload["summary"]["post_receipt_parser_verification_count"] == 1 + assert payload["summary"]["post_receipt_parser_verification_field_count"] == 10 + assert payload["summary"]["receipt_closeout_preview_count"] == 1 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert future["runner_execution_authorized"] is False + assert future["dry_run_execution_authorized"] is False + assert future["execution_authorized"] is False + assert future["stdout_capture_allowed"] is False + assert future["stderr_capture_allowed"] is False + assert future["ready_for_database_apply_now"] is False + assert future["database_apply_authorized"] is False + assert future["issues_database_apply_authorization"] is False + assert future["signs_database_apply_authorization"] is False + assert future["executes_authorization_evidence"] is False + assert future["executes_database_apply"] is False + assert future["executes_endpoint"] is False + assert future["executes_sql"] is False + assert future["writes_database"] is False + assert receipt_closeout["runner_execution_receipt_closeout_only"] is True + assert receipt_closeout["post_receipt_parser_verification_only"] is True + assert receipt_closeout["runner_execution_authorized"] is False + assert receipt_closeout["dry_run_execution_authorized"] is False + assert receipt_closeout["execution_authorized"] is False + assert receipt_closeout["dry_run_only"] is True + assert receipt_closeout["check_mode_only"] is True + assert receipt_closeout["accepts_plaintext_secret"] is False + assert receipt_closeout["reads_secret_in_preview"] is False + assert receipt_closeout["signature_material_included"] is False + assert receipt_closeout["secret_material_included"] is False + assert receipt_closeout["signs_database_apply_authorization"] is False + assert receipt_closeout["executes_authorization_evidence"] is False + assert receipt_closeout["executes_database_apply"] is False + assert receipt_closeout["executes_endpoint_in_preview"] is False + assert receipt_closeout["executes_sql_in_preview"] is False + assert receipt_closeout["writes_database_in_preview"] is False + assert preflight["preflight_status"] == "preflight_only_not_executed" + assert preflight["execution_required"] is False + assert preflight["execution_performed"] is False + assert preflight["stdout_capture_allowed"] is False + assert preflight["stderr_capture_allowed"] is False + assert preflight["writes_database"] is False + assert preview["receipt_status"] == "receipt_closeout_preview_not_executed" + assert preview["execution_required"] is False + assert preview["execution_performed"] is False + assert preview["stdout_included"] is False + assert preview["stderr_included"] is False + assert preview["writes_database"] is False + assert parser["expected_preflight_status"] == "preflight_only_not_executed" + assert parser["expected_receipt_status"] == "receipt_closeout_preview_not_executed" + assert parser["execution_required"] is False + assert parser["stdout_allowed"] is False + assert parser["stderr_allowed"] is False + assert parser["database_apply_authorized"] is False + assert ( + payload["controlled_dry_run_runner_execution_receipt_closeout_contract"][ + "ready_for_database_apply_now" + ] + is False + ) + assert ( + payload["controlled_dry_run_runner_execution_receipt_closeout_contract"][ + "executes_database_apply" + ] + is False + ) + assert ( + payload["controlled_dry_run_runner_execution_receipt_closeout_contract"][ + "executes_endpoint" + ] + is False + ) + assert ( + payload["controlled_dry_run_runner_execution_receipt_closeout_contract"][ + "executes_sql" + ] + is False + ) + assert ( + payload["controlled_dry_run_runner_execution_receipt_closeout_contract"][ + "database_apply_authorized" + ] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["executes_endpoint_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["reads_secret_in_preview"] is False + assert payload["safety"]["executes_endpoint"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + assert payload["safety"]["executes_database_apply"] is False + + +def test_auto_policy_db_apply_authorization_signing_decision_closeout_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply authorization signing decision closeout should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-decision-closeout?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_authorization_signing_decision_closeout + .__wrapped__() + ) + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signing_decision_closeout" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-decision-preflight" + ) + assert payload["summary"]["signing_decision_closeout_check_count"] == 12 + assert payload["summary"]["signing_decision_input_requirement_count"] == 10 + assert payload["summary"]["signing_decision_rejection_reason_count"] == 11 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + payload["future_authorization_signing_decision_closeout"]["ready_for_database_apply_now"] + is False + ) + assert ( + payload["future_authorization_signing_decision_closeout"][ + "issues_database_apply_authorization" + ] + is False + ) + assert ( + payload["future_authorization_signing_decision_closeout"][ + "signs_database_apply_authorization" + ] + is False + ) + assert payload["unsigned_signing_decision_package"]["ready_for_database_apply_now"] is False + assert payload["unsigned_signing_decision_package"]["signs_database_apply_authorization"] is False + assert payload["unsigned_signing_decision_package"]["secret_material_included"] is False + assert payload["unsigned_signing_decision_package"]["secret_material_required_in_preview"] is False + assert payload["signing_decision_closeout_contract"]["ready_for_database_apply_now"] is False + assert ( + payload["signing_decision_closeout_contract"]["signs_database_apply_authorization"] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["signs_database_apply_authorization"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False + + +def test_auto_policy_db_apply_authorization_signing_issuer_guard_route_defaults_to_no_fetch_and_uses_cached_payload(monkeypatch): + from flask import Flask + from routes import ai_routes as routes + + monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload()) + + def fail_engine(database_path): + raise AssertionError("cached auto-policy DB apply authorization signing issuer guard should not open a DB engine") + + monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine) + + app = Flask(__name__) + with app.test_request_context( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-issuer-guard?batch_size=1" + ): + response = ( + routes + .api_pchome_growth_auto_policy_db_apply_authorization_signing_issuer_guard + .__wrapped__() + ) + + payload = response.get_json() + assert payload["success"] is True + assert payload["policy"] == ( + "read_only_pchome_growth_auto_policy_db_apply_authorization_signing_issuer_guard" + ) + assert payload["source_endpoint"] == ( + "/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-decision-closeout" + ) + assert payload["summary"]["signing_issuer_guard_check_count"] == 12 + assert payload["summary"]["signing_decision_closeout_check_count"] == 12 + assert payload["summary"]["signing_decision_input_requirement_count"] == 10 + assert payload["summary"]["signing_decision_rejection_reason_count"] == 11 + assert payload["summary"]["signs_database_apply_authorization_count"] == 0 + assert ( + payload["future_authorization_signing_issuer_guard"]["ready_for_database_apply_now"] + is False + ) + assert ( + payload["future_authorization_signing_issuer_guard"][ + "issues_database_apply_authorization" + ] + is False + ) + assert ( + payload["future_authorization_signing_issuer_guard"][ + "signs_database_apply_authorization" + ] + is False + ) + assert payload["signable_request_boundary"]["ready_for_database_apply_now"] is False + assert payload["signable_request_boundary"]["signs_database_apply_authorization"] is False + assert payload["signable_request_boundary"]["secret_material_included"] is False + assert payload["signable_request_boundary"]["secret_material_required_in_preview"] is False + assert payload["signing_issuer_guard_contract"]["ready_for_database_apply_now"] is False + assert ( + payload["signing_issuer_guard_contract"]["signs_database_apply_authorization"] + is False + ) + assert payload["summary"]["reads_secret_count"] == 0 + assert payload["summary"]["executes_script_count"] == 0 + assert payload["summary"]["executes_migration_count"] == 0 + assert payload["summary"]["writes_database_count"] == 0 + assert payload["safety"]["signs_database_apply_authorization"] is False + assert payload["safety"]["executes_sql"] is False + assert payload["safety"]["writes_database"] is False diff --git a/tests/test_production_version_truth.py b/tests/test_production_version_truth.py new file mode 100644 index 0000000..cfeb62b --- /dev/null +++ b/tests/test_production_version_truth.py @@ -0,0 +1,68 @@ +import json + +from scripts.ops import check_production_version_truth as guard + + +def _report(production_version="V10.725", local_version="V10.725", head_version="V10.725"): + return { + "policy": "production_health_is_latest_version_truth", + "health_url": "https://mo.wooo.work/health", + "production": { + "status": "healthy", + "database": "postgresql", + "version": production_version, + }, + "local": { + "branch": "main", + "head": "f3e412cd211f5e4601204b256aeb95eae073b441", + "config_version": local_version, + "head_config_version": head_version, + }, + "origin_main": { + "head": "f3e412cd211f5e4601204b256aeb95eae073b441", + "matches_local_head": True, + }, + } + + +def test_parse_config_version(): + assert guard.parse_config_version('SYSTEM_VERSION = "V10.725"\n') == "V10.725" + + +def test_production_version_truth_passes_when_everything_matches(): + ok, errors = guard.evaluate(_report(), allow_local_version_drift=False) + + assert ok is True + assert errors == [] + + +def test_working_tree_version_drift_blocks_by_default(): + ok, errors = guard.evaluate(_report(local_version="V10.726"), allow_local_version_drift=False) + + assert ok is False + assert any("working-tree config.py version differs from production" in error for error in errors) + + +def test_explicit_release_prep_can_allow_working_tree_version_drift(): + ok, errors = guard.evaluate(_report(local_version="V10.726"), allow_local_version_drift=True) + + assert ok is True + assert errors == [] + + +def test_head_version_must_still_match_production(): + ok, errors = guard.evaluate(_report(head_version="V10.724"), allow_local_version_drift=True) + + assert ok is False + assert any("HEAD config.py version differs from production" in error for error in errors) + + +def test_json_output_contains_production_truth_policy(monkeypatch, capsys): + monkeypatch.setattr(guard, "build_report", lambda health_url, timeout: _report()) + + exit_code = guard.main(["--json"]) + payload = json.loads(capsys.readouterr().out) + + assert exit_code == 0 + assert payload["policy"] == "production_health_is_latest_version_truth" + assert payload["production"]["version"] == "V10.725"