"""市場情報正式寫入前的人工批准 runbook。 本模組只產生 gate 與操作順序,不建立 DB session、不執行 migration、不寫入資料。 """ def _status_value(status, key, default=False): return bool(getattr(status, key, default)) def build_write_approval_runbook( *, phase, status, schema_smoke, seed_plan, write_guard, writer_plan, ): """建立正式 seed write 前的 read-only runbook。""" gates = [ { "key": "schema_smoke_passed", "label": "ORM metadata smoke 已通過,八張 market_* table 與 market_platforms 欄位完整", "passed": bool(schema_smoke.get("passed")), }, { "key": "backup_completed", "label": "已在正式推版前執行 python backup_system.py", "passed": False, }, { "key": "migration_file_reviewed", "label": "market_* schema migration 已人工審核,且不 drop/alter 既有業績資料表", "passed": False, }, { "key": "feature_flags_enabled_for_write_window", "label": "寫入窗口才可同時啟用 MARKET_INTEL_ENABLED 與 MARKET_INTEL_WRITE_ENABLED", "passed": bool(_status_value(status, "enabled") and _status_value(status, "write_enabled")), }, { "key": "database_write_allowed", "label": "runtime database_write_allowed 為 true", "passed": bool(_status_value(status, "database_write_allowed")), }, { "key": "manual_operator_approval", "label": "操作者已明確批准一次性 market_platforms seed upsert", "passed": False, }, { "key": "rollback_plan_reviewed", "label": "已確認回復策略:關閉 flags、app-only rollback、必要時清理 seed rows", "passed": False, }, { "key": "production_smoke_targets_defined", "label": "已定義 /health 與市場情報 API smoke targets", "passed": True, }, ] blocked_reasons = [gate["key"] for gate in gates if not gate["passed"]] return { "phase": phase, "mode": "approval_runbook_read_only", "ready_for_real_write": False, "writes_executed": False, "would_write_database": False, "database_session_created": False, "database_commit_executed": False, "external_network_executed": False, "scheduler_attached": False, "approval_required": True, "approval_token_present": False, "blocked_reasons": blocked_reasons, "approval_gates": gates, "seed_count": int(seed_plan.get("seed_count") or 0), "writer_operation_count": int(writer_plan.get("operation_count") or 0), "schema_smoke": schema_smoke, "write_guard_summary": { "ready_to_write": bool(write_guard.get("ready_to_write")), "would_write_database": bool(write_guard.get("would_write_database")), "blocked_reasons": write_guard.get("blocked_reasons", []), }, "operator_sequence": [ { "key": "scope_review", "label": "確認 git diff 只包含 market_intel、ADR/TODO、版本與測試", }, { "key": "backup", "label": "執行 python backup_system.py,保存正式環境回復點", }, { "key": "migration_apply_window", "label": "在維護窗口套用 market_* migration,不觸碰 momo-db 容器生命週期", }, { "key": "seed_preview_compare", "label": "比對 platform_seed_writer_plan 的 4 筆 upsert preview", }, { "key": "one_time_seed_write", "label": "另開明確批准後才允許一次性 seed writer 真寫入", }, { "key": "post_write_smoke", "label": "驗證 /health、/market_intel、schema_smoke 與 deployment_readiness", }, ], "rollback_plan": [ { "key": "disable_flags", "label": "立刻關閉 MARKET_INTEL_ENABLED、MARKET_INTEL_CRAWLER_ENABLED、MARKET_INTEL_WRITE_ENABLED", }, { "key": "app_only_rollback", "label": "回退 momo-app 程式版本,只 recreate momo-app,不碰 momo-db", }, { "key": "seed_rows_cleanup", "label": "若唯一異常來自 platform seed,可在人工審核後刪除或停用 market_platforms seed rows", }, ], "hard_safety_boundaries": [ "no_external_crawling_during_seed_write", "no_scheduler_attach_during_seed_write", "no_momo_db_container_lifecycle_change", "no_remove_orphans", "health_probe_uses_health_endpoint_only", ], }