Files
ewoooc/services/market_intel/deployment_readiness.py
OoO 6fc064cd9a
All checks were successful
CD Pipeline / deploy (push) Successful in 1m12s
新增市場情報 AI summary persistence preflight
2026-05-19 23:10:32 +08:00

788 lines
49 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""市場情報 app-only release gate 組裝器。
本模組只組裝 preview payload不執行 git、部署、SSH、migration 或 DB write。
"""
from services.market_intel.candidate_queue_writer_cli import build_candidate_queue_writer_cli_plan
from services.market_intel.candidate_queue_writer_preflight import build_candidate_queue_writer_preflight
from services.market_intel.candidate_queue_writer_postwrite_smoke import build_candidate_queue_writer_postwrite_smoke
from services.market_intel.candidate_queue_writer_operator_drill import build_candidate_queue_writer_operator_drill
from services.market_intel.candidate_queue_writer_run_package import build_candidate_queue_writer_run_package
from services.market_intel.candidate_queue_writer_run_readiness import build_candidate_queue_writer_run_readiness
from services.market_intel.candidate_queue_writer_run_receipt import build_candidate_queue_writer_run_receipt
from services.market_intel.candidate_queue_writer_run_closeout import build_candidate_queue_writer_run_closeout
from services.market_intel.candidate_queue_review_handoff import build_candidate_queue_review_handoff
from services.market_intel.candidate_queue_review_inventory import build_candidate_queue_review_inventory
from services.market_intel.candidate_queue_review_decision import build_candidate_queue_review_decision
from services.market_intel.candidate_queue_review_decision_approval import build_candidate_queue_review_decision_approval
from services.market_intel.candidate_queue_review_decision_transaction import build_candidate_queue_review_decision_transaction
from services.market_intel.candidate_queue_review_decision_writer_cli import build_candidate_queue_review_decision_writer_cli_plan
from services.market_intel.candidate_queue_review_decision_writer_preflight import build_candidate_queue_review_decision_writer_preflight
from services.market_intel.candidate_queue_review_decision_writer_postwrite_smoke import build_candidate_queue_review_decision_writer_postwrite_smoke
from services.market_intel.candidate_queue_review_decision_writer_operator_drill import build_candidate_queue_review_decision_writer_operator_drill
from services.market_intel.candidate_queue_review_decision_writer_run_package import build_candidate_queue_review_decision_writer_run_package
from services.market_intel.candidate_queue_review_decision_writer_run_readiness import build_candidate_queue_review_decision_writer_run_readiness
from services.market_intel.candidate_queue_review_decision_writer_run_receipt import build_candidate_queue_review_decision_writer_run_receipt
from services.market_intel.candidate_queue_review_decision_writer_run_closeout import build_candidate_queue_review_decision_writer_run_closeout
from services.market_intel.candidate_queue_review_decision_post_closeout_inventory import build_candidate_queue_review_decision_post_closeout_inventory
from services.market_intel.candidate_queue_review_completion_archive import build_candidate_queue_review_completion_archive
from services.market_intel.candidate_queue_review_archive_summary import build_candidate_queue_review_archive_summary
from services.market_intel.candidate_queue_review_ai_summary_preflight import build_candidate_queue_review_ai_summary_preflight
from services.market_intel.candidate_queue_review_ai_summary_output_receipt import build_candidate_queue_review_ai_summary_output_receipt
from services.market_intel.candidate_queue_review_ai_summary_persistence_preflight import build_candidate_queue_review_ai_summary_persistence_preflight
from services.market_intel.candidate_queue_review_ai_summary_run_package import build_candidate_queue_review_ai_summary_run_package
BLOCKED_RUN_REVIEW_KEYS = (
"ready_for_api_database_write",
"ready_for_scheduler_attach",
"ready_for_ai_summary_generation",
"ready_for_llm_call",
"ready_for_telegram_dispatch",
"api_executes_cli",
"api_executes_llm",
"api_reads_approval_token",
"api_writes_file",
"api_writes_database",
"api_updates_review_state",
"approval_record_written",
"decision_record_written",
"run_package_file_written",
"summary_file_written",
"summary_record_written",
"summary_manifest_written",
"summary_receipt_file_written",
"summary_persistence_preflight_file_written",
"summary_persistence_record_written",
"metadata_patch_written",
"ai_summary_generated",
"llm_call_executed",
"ollama_call_executed",
"gemini_call_executed",
"telegram_dispatched",
"review_state_update_executed",
"database_connection_opened",
"database_session_created",
"explicit_transaction_opened",
"transaction_opened",
"transaction_committed",
"database_write_executed",
"database_commit_executed",
"database_rollback_executed",
"scheduler_attached",
"writes_executed",
"would_write_database",
)
PRODUCTION_SMOKE_TARGETS = ("/health", "/market_intel", "/api/market_intel/status", "/api/market_intel/deployment_readiness", "/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/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/migration_apply_drill", "/api/market_intel/migration_catalog_review", "/api/market_intel/migration_live_smoke", "/api/market_intel/live_db_inventory", "/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/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_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", "/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_decision_writer_status")
def _run_review_preview_safe(payload, mode):
return bool(payload["mode"] == mode and all(not payload.get(key) for key in BLOCKED_RUN_REVIEW_KEYS))
def build_deployment_readiness_preview(
*,
service,
market_intel_tables,
schema_smoke_builder,
):
"""建立市場情報推版準備狀態;不執行 git、部署或遠端操作。"""
status = service.get_runtime_status()
schema_smoke = schema_smoke_builder(market_intel_tables)
writer_plan = service.build_platform_seed_writer_plan()
mcp_deploy_preflight = service.build_mcp_deploy_preflight()
mcp_activation_runbook = service.build_mcp_activation_runbook()
mcp_fetch_gate = service.build_mcp_fetch_gate()
scheduler_plan = service.build_scheduler_plan()
manual_sample_plan = service.build_manual_sample_plan()
manual_sample_acceptance = service.build_manual_sample_acceptance()
manual_sample_review = service.build_manual_sample_review()
manual_sample_review_evaluation = service.build_manual_sample_review_evaluation(sample_result={})
manual_sample_candidate_handoff = service.build_manual_sample_candidate_handoff(sample_result={})
manual_sample_candidate_queue_draft = service.build_manual_sample_candidate_queue_draft(sample_result={})
manual_sample_candidate_queue_approval = service.build_manual_sample_candidate_queue_approval(sample_result={})
manual_sample_candidate_queue_transaction = service.build_manual_sample_candidate_queue_transaction(sample_result={})
candidate_queue_writer_preflight = build_candidate_queue_writer_preflight(
transaction_preview=manual_sample_candidate_queue_transaction,
execute_requested=False,
)
candidate_queue_writer_cli_status = build_candidate_queue_writer_cli_plan(
transaction_preview=manual_sample_candidate_queue_transaction,
writer_preflight=candidate_queue_writer_preflight,
)
candidate_queue_writer_postwrite_smoke = build_candidate_queue_writer_postwrite_smoke(
transaction_preview=manual_sample_candidate_queue_transaction,
execute_requested=False,
)
candidate_queue_writer_operator_drill = (
build_candidate_queue_writer_operator_drill(
transaction_preview=manual_sample_candidate_queue_transaction,
writer_preflight=candidate_queue_writer_preflight,
writer_status=candidate_queue_writer_cli_status,
postwrite_smoke=candidate_queue_writer_postwrite_smoke,
)
)
candidate_queue_writer_run_package = build_candidate_queue_writer_run_package(
transaction_preview=manual_sample_candidate_queue_transaction,
writer_preflight=candidate_queue_writer_preflight,
writer_status=candidate_queue_writer_cli_status,
postwrite_smoke=candidate_queue_writer_postwrite_smoke,
operator_drill=candidate_queue_writer_operator_drill,
)
candidate_queue_writer_run_readiness = build_candidate_queue_writer_run_readiness(
transaction_preview=manual_sample_candidate_queue_transaction,
writer_preflight=candidate_queue_writer_preflight,
writer_status=candidate_queue_writer_cli_status,
postwrite_smoke=candidate_queue_writer_postwrite_smoke,
operator_drill=candidate_queue_writer_operator_drill,
run_package=candidate_queue_writer_run_package,
)
candidate_queue_writer_run_receipt = build_candidate_queue_writer_run_receipt(
transaction_preview=manual_sample_candidate_queue_transaction,
run_readiness=candidate_queue_writer_run_readiness,
)
candidate_queue_writer_run_closeout = build_candidate_queue_writer_run_closeout(
transaction_preview=manual_sample_candidate_queue_transaction,
run_receipt=candidate_queue_writer_run_receipt,
)
candidate_queue_review_handoff = build_candidate_queue_review_handoff(
transaction_preview=manual_sample_candidate_queue_transaction,
run_closeout=candidate_queue_writer_run_closeout,
)
match_review_plan = service.build_match_review_plan()
opportunity_plan = service.build_opportunity_plan()
opportunity_scoring_plan = service.build_opportunity_scoring_plan()
opportunity_evidence_plan = service.build_opportunity_evidence_plan()
opportunity_alert_plan = service.build_opportunity_alert_plan()
migration_apply_drill = service.build_migration_apply_drill()
migration_catalog_review = service.build_migration_catalog_review()
migration_live_smoke = service.build_migration_live_smoke()
live_db_inventory = service.build_live_db_inventory()
candidate_queue_review_inventory = build_candidate_queue_review_inventory(
review_handoff=candidate_queue_review_handoff,
postwrite_smoke=candidate_queue_writer_postwrite_smoke,
live_db_inventory=live_db_inventory,
)
candidate_queue_review_decision = build_candidate_queue_review_decision(
review_inventory=candidate_queue_review_inventory,
)
candidate_queue_review_decision_approval = build_candidate_queue_review_decision_approval(review_decision=candidate_queue_review_decision)
candidate_queue_review_decision_transaction = build_candidate_queue_review_decision_transaction(decision_approval=candidate_queue_review_decision_approval)
candidate_queue_review_decision_writer_status = build_candidate_queue_review_decision_writer_cli_plan(transaction_preview=candidate_queue_review_decision_transaction)
candidate_queue_review_decision_writer_preflight = build_candidate_queue_review_decision_writer_preflight(
writer_status=candidate_queue_review_decision_writer_status,
transaction_preview=candidate_queue_review_decision_transaction,
)
candidate_queue_review_decision_writer_postwrite_smoke = build_candidate_queue_review_decision_writer_postwrite_smoke(
transaction_preview=candidate_queue_review_decision_transaction,
execute_requested=False,
)
candidate_queue_review_decision_writer_operator_drill = build_candidate_queue_review_decision_writer_operator_drill(
transaction_preview=candidate_queue_review_decision_transaction,
writer_preflight=candidate_queue_review_decision_writer_preflight,
writer_status=candidate_queue_review_decision_writer_status,
postwrite_smoke=candidate_queue_review_decision_writer_postwrite_smoke,
)
candidate_queue_review_decision_writer_run_package = build_candidate_queue_review_decision_writer_run_package(
transaction_preview=candidate_queue_review_decision_transaction,
writer_preflight=candidate_queue_review_decision_writer_preflight,
writer_status=candidate_queue_review_decision_writer_status,
postwrite_smoke=candidate_queue_review_decision_writer_postwrite_smoke,
operator_drill=candidate_queue_review_decision_writer_operator_drill,
)
candidate_queue_review_decision_writer_run_readiness = build_candidate_queue_review_decision_writer_run_readiness(
transaction_preview=candidate_queue_review_decision_transaction,
writer_preflight=candidate_queue_review_decision_writer_preflight,
writer_status=candidate_queue_review_decision_writer_status,
postwrite_smoke=candidate_queue_review_decision_writer_postwrite_smoke,
operator_drill=candidate_queue_review_decision_writer_operator_drill,
run_package=candidate_queue_review_decision_writer_run_package,
)
candidate_queue_review_decision_writer_run_receipt = build_candidate_queue_review_decision_writer_run_receipt(
transaction_preview=candidate_queue_review_decision_transaction,
run_readiness=candidate_queue_review_decision_writer_run_readiness,
)
candidate_queue_review_decision_writer_run_closeout = build_candidate_queue_review_decision_writer_run_closeout(
transaction_preview=candidate_queue_review_decision_transaction,
run_receipt=candidate_queue_review_decision_writer_run_receipt,
)
candidate_queue_review_decision_post_closeout_inventory = build_candidate_queue_review_decision_post_closeout_inventory(
transaction_preview=candidate_queue_review_decision_transaction,
run_closeout=candidate_queue_review_decision_writer_run_closeout,
postwrite_smoke=candidate_queue_review_decision_writer_postwrite_smoke,
live_db_inventory=live_db_inventory,
)
candidate_queue_review_completion_archive = build_candidate_queue_review_completion_archive(
transaction_preview=candidate_queue_review_decision_transaction,
run_receipt=candidate_queue_review_decision_writer_run_receipt,
run_closeout=candidate_queue_review_decision_writer_run_closeout,
post_closeout_inventory=candidate_queue_review_decision_post_closeout_inventory,
)
candidate_queue_review_archive_summary = build_candidate_queue_review_archive_summary(
review_completion_archive=candidate_queue_review_completion_archive,
)
candidate_queue_review_ai_summary_preflight = build_candidate_queue_review_ai_summary_preflight(
archive_summary=candidate_queue_review_archive_summary,
)
candidate_queue_review_ai_summary_run_package = build_candidate_queue_review_ai_summary_run_package(
archive_summary=candidate_queue_review_archive_summary,
ai_summary_preflight=candidate_queue_review_ai_summary_preflight,
)
candidate_queue_review_ai_summary_output_receipt = build_candidate_queue_review_ai_summary_output_receipt(
ai_summary_run_package=candidate_queue_review_ai_summary_run_package,
)
candidate_queue_review_ai_summary_persistence_preflight = build_candidate_queue_review_ai_summary_persistence_preflight(
ai_summary_output_receipt=candidate_queue_review_ai_summary_output_receipt,
)
checks = {
"schema_smoke_passed": bool(schema_smoke["passed"]),
"feature_flags_default_safe": bool(
not status.enabled
and not status.crawler_enabled
and not status.write_enabled
),
"database_write_blocked": bool(not status.database_write_allowed),
"scheduler_detached": bool(not status.scheduler_attached),
"manual_fetch_disabled": bool(not service.manual_fetch_allowed()),
"writer_plan_dry_run_only": bool(
writer_plan["mode"] == "dry_run"
and not writer_plan["writes_executed"]
and not writer_plan["would_write_database"]
),
"registered_adapters_present": bool(len(service.get_adapter_summaries()) >= 4),
"schema_db_probe_planned_safe": bool(
not service.build_schema_db_probe()["read_only_query_executed"]
),
"platform_seed_db_diff_planned_safe": bool(
not service.build_platform_seed_db_diff()["read_only_query_executed"]
),
"legacy_source_bridge_planned_safe": bool(
not service.build_legacy_source_bridge()["read_only_query_executed"]
),
"mcp_readiness_planned_safe": bool(
service.build_mcp_readiness()["mode"] == "mcp_readiness_planned"
),
"mcp_tool_contract_ready": bool(
service.build_mcp_tool_contract()["contract_ready"]
),
"mcp_deploy_preflight_preview_safe": bool(
mcp_deploy_preflight["mode"] == "mcp_external_deploy_preflight_preview"
and not mcp_deploy_preflight["deployment_actions_executed"]
),
"mcp_activation_runbook_preview_safe": bool(
mcp_activation_runbook["mode"] == "mcp_activation_runbook_preview"
and not mcp_activation_runbook["deployment_actions_executed"]
),
"mcp_fetch_gate_preview_safe": bool(
mcp_fetch_gate["mode"] == "mcp_fetch_gate_planned"
and not mcp_fetch_gate["network_request_allowed"]
and not mcp_fetch_gate["external_network_executed"]
),
"scheduler_plan_preview_safe": bool(
scheduler_plan["mode"] == "scheduler_attach_plan_preview"
and not scheduler_plan["scheduler_registration_executed"]
and not scheduler_plan["crawler_job_started"]
and not scheduler_plan["database_write_executed"]
),
"manual_sample_plan_preview_safe": bool(
manual_sample_plan["mode"] == "manual_sample_fetch_plan_preview"
and not manual_sample_plan["sample_fetch_executed"]
and not manual_sample_plan["external_network_executed"]
and not manual_sample_plan["database_write_executed"]
and not manual_sample_plan["database_commit_executed"]
and not manual_sample_plan["scheduler_attached"]
),
"manual_sample_acceptance_preview_safe": bool(
manual_sample_acceptance["mode"] == "manual_sample_acceptance_preview"
and not manual_sample_acceptance["sample_result_loaded"]
and not manual_sample_acceptance["candidate_import_allowed"]
and not manual_sample_acceptance["external_network_executed"]
and not manual_sample_acceptance["database_write_executed"]
and not manual_sample_acceptance["database_commit_executed"]
and not manual_sample_acceptance["scheduler_attached"]
),
"manual_sample_review_preview_safe": bool(
manual_sample_review["mode"] == "manual_sample_review_preview"
and not manual_sample_review["sample_result_loaded"]
and not manual_sample_review["candidate_import_allowed"]
and not manual_sample_review["external_network_executed"]
and not manual_sample_review["database_write_executed"]
and not manual_sample_review["database_commit_executed"]
and not manual_sample_review["scheduler_attached"]
),
"manual_sample_review_evaluation_post_safe": bool(
manual_sample_review_evaluation["mode"]
== "manual_sample_review_evaluation_preview"
and manual_sample_review_evaluation["payload_received"]
and not manual_sample_review_evaluation["payload_persisted"]
and not manual_sample_review_evaluation["sample_result_persisted"]
and not manual_sample_review_evaluation["candidate_import_allowed"]
and not manual_sample_review_evaluation["external_network_executed"]
and not manual_sample_review_evaluation["database_write_executed"]
and not manual_sample_review_evaluation["database_commit_executed"]
and not manual_sample_review_evaluation["scheduler_attached"]
),
"manual_sample_candidate_handoff_post_safe": bool(
manual_sample_candidate_handoff["mode"]
== "manual_sample_candidate_handoff_preview"
and manual_sample_candidate_handoff["payload_received"]
and not manual_sample_candidate_handoff["payload_persisted"]
and not manual_sample_candidate_handoff["candidate_handoff_persisted"]
and not manual_sample_candidate_handoff["candidate_import_allowed"]
and not manual_sample_candidate_handoff["external_network_executed"]
and not manual_sample_candidate_handoff["database_write_executed"]
and not manual_sample_candidate_handoff["database_commit_executed"]
and not manual_sample_candidate_handoff["scheduler_attached"]
),
"manual_sample_candidate_queue_draft_post_safe": bool(
manual_sample_candidate_queue_draft["mode"]
== "manual_sample_candidate_queue_draft_preview"
and manual_sample_candidate_queue_draft["payload_received"]
and not manual_sample_candidate_queue_draft["payload_persisted"]
and not manual_sample_candidate_queue_draft["review_queue_created"]
and not manual_sample_candidate_queue_draft["review_queue_persisted"]
and not manual_sample_candidate_queue_draft["candidate_import_allowed"]
and not manual_sample_candidate_queue_draft["external_network_executed"]
and not manual_sample_candidate_queue_draft["database_write_executed"]
and not manual_sample_candidate_queue_draft["database_commit_executed"]
and not manual_sample_candidate_queue_draft["scheduler_attached"]
),
"manual_sample_candidate_queue_approval_post_safe": bool(
manual_sample_candidate_queue_approval["mode"]
== "manual_sample_candidate_queue_approval_preview"
and manual_sample_candidate_queue_approval["payload_received"]
and not manual_sample_candidate_queue_approval["payload_persisted"]
and not manual_sample_candidate_queue_approval["approval_request_created"]
and not manual_sample_candidate_queue_approval["approval_record_written"]
and not manual_sample_candidate_queue_approval["review_queue_write_allowed"]
and not manual_sample_candidate_queue_approval["review_queue_created"]
and not manual_sample_candidate_queue_approval["review_queue_persisted"]
and not manual_sample_candidate_queue_approval["candidate_import_allowed"]
and not manual_sample_candidate_queue_approval["external_network_executed"]
and not manual_sample_candidate_queue_approval["database_write_executed"]
and not manual_sample_candidate_queue_approval["database_commit_executed"]
and not manual_sample_candidate_queue_approval["scheduler_attached"]
),
"manual_sample_candidate_queue_transaction_post_safe": bool(
manual_sample_candidate_queue_transaction["mode"]
== "manual_sample_candidate_queue_transaction_preview"
and manual_sample_candidate_queue_transaction["payload_received"]
and not manual_sample_candidate_queue_transaction["payload_persisted"]
and not manual_sample_candidate_queue_transaction["transaction_ready"]
and not manual_sample_candidate_queue_transaction["transaction_opened"]
and not manual_sample_candidate_queue_transaction["transaction_committed"]
and not manual_sample_candidate_queue_transaction["approval_record_written"]
and not manual_sample_candidate_queue_transaction["review_queue_created"]
and not manual_sample_candidate_queue_transaction["review_queue_persisted"]
and not manual_sample_candidate_queue_transaction["candidate_import_allowed"]
and not manual_sample_candidate_queue_transaction["external_network_executed"]
and not manual_sample_candidate_queue_transaction["database_write_executed"]
and not manual_sample_candidate_queue_transaction["database_commit_executed"]
and not manual_sample_candidate_queue_transaction["scheduler_attached"]
),
"candidate_queue_writer_cli_status_safe": bool(
candidate_queue_writer_cli_status["mode"]
== "candidate_queue_writer_cli_blocked"
and not candidate_queue_writer_cli_status["ready_for_real_write"]
and not candidate_queue_writer_cli_status["writes_executed"]
and not candidate_queue_writer_cli_status["would_write_database"]
and not candidate_queue_writer_cli_status["database_connection_opened"]
and not candidate_queue_writer_cli_status["explicit_transaction_opened"]
and not candidate_queue_writer_cli_status["database_write_executed"]
and not candidate_queue_writer_cli_status["database_commit_executed"]
and not candidate_queue_writer_cli_status["scheduler_attached"]
),
"candidate_queue_writer_preflight_planned_safe": bool(
candidate_queue_writer_preflight["mode"]
== "candidate_queue_writer_preflight_planned"
and not candidate_queue_writer_preflight["read_only_query_executed"]
and not candidate_queue_writer_preflight["database_connection_opened"]
and not candidate_queue_writer_preflight["database_write_executed"]
and not candidate_queue_writer_preflight["database_commit_executed"]
and not candidate_queue_writer_preflight["scheduler_attached"]
),
"candidate_queue_writer_postwrite_smoke_planned_safe": bool(
candidate_queue_writer_postwrite_smoke["mode"]
== "candidate_queue_writer_postwrite_smoke_planned"
and not candidate_queue_writer_postwrite_smoke["read_only_query_executed"]
and not candidate_queue_writer_postwrite_smoke["database_connection_opened"]
and not candidate_queue_writer_postwrite_smoke["database_write_executed"]
and not candidate_queue_writer_postwrite_smoke["database_commit_executed"]
and not candidate_queue_writer_postwrite_smoke["scheduler_attached"]
),
"candidate_queue_writer_operator_drill_preview_safe": bool(
candidate_queue_writer_operator_drill["mode"]
== "candidate_queue_writer_operator_drill_preview"
and not candidate_queue_writer_operator_drill["api_executes_cli"]
and not candidate_queue_writer_operator_drill["api_reads_approval_token"]
and not candidate_queue_writer_operator_drill["database_connection_opened"]
and not candidate_queue_writer_operator_drill["database_write_executed"]
and not candidate_queue_writer_operator_drill["database_commit_executed"]
and not candidate_queue_writer_operator_drill["scheduler_attached"]
),
"candidate_queue_writer_run_package_preview_safe": bool(
candidate_queue_writer_run_package["mode"]
== "candidate_queue_writer_run_package_preview"
and not candidate_queue_writer_run_package["package_artifact_created"]
and not candidate_queue_writer_run_package["api_writes_file"]
and not candidate_queue_writer_run_package["api_executes_cli"]
and not candidate_queue_writer_run_package["api_reads_approval_token"]
and not candidate_queue_writer_run_package["database_connection_opened"]
and not candidate_queue_writer_run_package["database_write_executed"]
and not candidate_queue_writer_run_package["database_commit_executed"]
and not candidate_queue_writer_run_package["scheduler_attached"]
),
"candidate_queue_writer_run_readiness_preview_safe": bool(
candidate_queue_writer_run_readiness["mode"]
== "candidate_queue_writer_run_readiness_preview"
and not candidate_queue_writer_run_readiness["ready_for_api_database_write"]
and not candidate_queue_writer_run_readiness["api_executes_cli"]
and not candidate_queue_writer_run_readiness["api_reads_approval_token"]
and not candidate_queue_writer_run_readiness["api_writes_file"]
and not candidate_queue_writer_run_readiness["database_connection_opened"]
and not candidate_queue_writer_run_readiness["database_write_executed"]
and not candidate_queue_writer_run_readiness["database_commit_executed"]
and not candidate_queue_writer_run_readiness["scheduler_attached"]
),
"candidate_queue_writer_run_receipt_preview_safe": _run_review_preview_safe(
candidate_queue_writer_run_receipt,
"candidate_queue_writer_run_receipt_preview",
),
"candidate_queue_writer_run_closeout_preview_safe": _run_review_preview_safe(
candidate_queue_writer_run_closeout,
"candidate_queue_writer_run_closeout_preview",
),
"candidate_queue_review_handoff_preview_safe": _run_review_preview_safe(
candidate_queue_review_handoff,
"candidate_queue_review_handoff_preview",
),
"candidate_queue_review_inventory_preview_safe": _run_review_preview_safe(
candidate_queue_review_inventory,
"candidate_queue_review_inventory_preview",
),
"candidate_queue_review_decision_preview_safe": _run_review_preview_safe(
candidate_queue_review_decision,
"candidate_queue_review_decision_preview",
),
"candidate_queue_review_decision_approval_preview_safe": _run_review_preview_safe(
candidate_queue_review_decision_approval,
"candidate_queue_review_decision_approval_preview",
),
"candidate_queue_review_decision_transaction_preview_safe": _run_review_preview_safe(
candidate_queue_review_decision_transaction,
"candidate_queue_review_decision_transaction_preview",
),
"candidate_queue_review_decision_writer_preflight_safe": _run_review_preview_safe(
candidate_queue_review_decision_writer_preflight,
"candidate_queue_review_decision_writer_preflight_preview",
),
"candidate_queue_review_decision_writer_postwrite_smoke_planned_safe": _run_review_preview_safe(
candidate_queue_review_decision_writer_postwrite_smoke,
"candidate_queue_review_decision_writer_postwrite_smoke_planned",
),
"candidate_queue_review_decision_writer_operator_drill_preview_safe": _run_review_preview_safe(
candidate_queue_review_decision_writer_operator_drill,
"candidate_queue_review_decision_writer_operator_drill_preview",
),
"candidate_queue_review_decision_writer_run_package_preview_safe": _run_review_preview_safe(
candidate_queue_review_decision_writer_run_package,
"candidate_queue_review_decision_writer_run_package_preview",
),
"candidate_queue_review_decision_writer_run_readiness_preview_safe": _run_review_preview_safe(
candidate_queue_review_decision_writer_run_readiness,
"candidate_queue_review_decision_writer_run_readiness_preview",
),
"candidate_queue_review_decision_writer_run_receipt_preview_safe": _run_review_preview_safe(
candidate_queue_review_decision_writer_run_receipt,
"candidate_queue_review_decision_writer_run_receipt_preview",
),
"candidate_queue_review_decision_writer_run_closeout_preview_safe": _run_review_preview_safe(
candidate_queue_review_decision_writer_run_closeout,
"candidate_queue_review_decision_writer_run_closeout_preview",
),
"candidate_queue_review_decision_post_closeout_inventory_preview_safe": _run_review_preview_safe(
candidate_queue_review_decision_post_closeout_inventory,
"candidate_queue_review_decision_post_closeout_inventory_preview",
),
"candidate_queue_review_completion_archive_preview_safe": _run_review_preview_safe(
candidate_queue_review_completion_archive,
"candidate_queue_review_completion_archive_preview",
),
"candidate_queue_review_archive_summary_preview_safe": _run_review_preview_safe(
candidate_queue_review_archive_summary,
"candidate_queue_review_archive_summary_preview",
),
"candidate_queue_review_ai_summary_preflight_preview_safe": _run_review_preview_safe(
candidate_queue_review_ai_summary_preflight,
"candidate_queue_review_ai_summary_preflight_preview",
),
"candidate_queue_review_ai_summary_run_package_preview_safe": _run_review_preview_safe(
candidate_queue_review_ai_summary_run_package,
"candidate_queue_review_ai_summary_run_package_preview",
),
"candidate_queue_review_ai_summary_output_receipt_preview_safe": _run_review_preview_safe(
candidate_queue_review_ai_summary_output_receipt,
"candidate_queue_review_ai_summary_output_receipt_preview",
),
"candidate_queue_review_ai_summary_persistence_preflight_preview_safe": _run_review_preview_safe(
candidate_queue_review_ai_summary_persistence_preflight,
"candidate_queue_review_ai_summary_persistence_preflight_preview",
),
"candidate_queue_review_decision_writer_cli_status_safe": _run_review_preview_safe(
candidate_queue_review_decision_writer_status,
"candidate_queue_review_decision_writer_cli_blocked",
),
"match_review_plan_preview_safe": bool(
match_review_plan["mode"] == "match_review_plan_preview"
and not match_review_plan["review_queue_created"]
and not match_review_plan["auto_confirm_executed"]
and not match_review_plan["database_write_executed"]
),
"opportunity_plan_preview_safe": bool(
opportunity_plan["mode"] == "opportunity_plan_preview"
and not opportunity_plan["opportunity_queue_created"]
and not opportunity_plan["threat_alert_dispatched"]
and not opportunity_plan["database_write_executed"]
),
"opportunity_scoring_plan_preview_safe": bool(
opportunity_scoring_plan["mode"]
== "opportunity_scoring_plan_preview"
and not opportunity_scoring_plan["scoring_job_created"]
and not opportunity_scoring_plan["score_calculation_executed"]
and not opportunity_scoring_plan["database_write_executed"]
),
"opportunity_evidence_plan_preview_safe": bool(
opportunity_evidence_plan["mode"]
== "opportunity_evidence_plan_preview"
and not opportunity_evidence_plan["evidence_query_executed"]
and not opportunity_evidence_plan["evidence_bundle_created"]
and not opportunity_evidence_plan["database_write_executed"]
),
"opportunity_alert_plan_preview_safe": bool(
opportunity_alert_plan["mode"] == "opportunity_alert_plan_preview"
and not opportunity_alert_plan["alert_candidate_created"]
and not opportunity_alert_plan["review_queue_contract_written"]
and not opportunity_alert_plan["review_queue_table_created"]
and not opportunity_alert_plan["review_action_executed"]
and not opportunity_alert_plan["approval_record_written"]
and not opportunity_alert_plan["telegram_dispatched"]
and not opportunity_alert_plan["database_write_executed"]
and not opportunity_alert_plan["llm_call_executed"]
),
"migration_apply_drill_preview_safe": bool(
migration_apply_drill["mode"] == "migration_apply_drill_preview"
and not migration_apply_drill["migration_executed"]
and not migration_apply_drill["rollback_executed"]
and not migration_apply_drill["database_write_executed"]
and not migration_apply_drill["database_commit_executed"]
and not migration_apply_drill["api_executes_migration"]
and not migration_apply_drill["api_executes_rollback"]
),
"migration_catalog_review_preview_safe": bool(
migration_catalog_review["mode"] == "migration_catalog_review_preview"
and not migration_catalog_review["migration_executed"]
and not migration_catalog_review["rollback_executed"]
and not migration_catalog_review["database_write_executed"]
and not migration_catalog_review["database_commit_executed"]
and not migration_catalog_review["api_executes_migration"]
and not migration_catalog_review["api_executes_rollback"]
),
"migration_live_smoke_preview_safe": bool(
migration_live_smoke["mode"] == "migration_live_smoke_preview"
and not migration_live_smoke["migration_executed"]
and not migration_live_smoke["rollback_executed"]
and not migration_live_smoke["database_write_executed"]
and not migration_live_smoke["database_commit_executed"]
and not migration_live_smoke["api_executes_migration"]
and not migration_live_smoke["api_executes_rollback"]
),
"live_db_inventory_preview_safe": bool(
live_db_inventory["mode"] == "live_db_inventory_planned"
and not live_db_inventory["read_only_query_executed"]
and not live_db_inventory["database_write_executed"]
and not live_db_inventory["database_commit_executed"]
and not live_db_inventory["migration_executed"]
and not live_db_inventory["external_network_executed"]
and not live_db_inventory["scheduler_attached"]
),
}
ready_for_production_deploy = all(checks.values())
blocked_reasons = [
reason for reason, blocked in (
("readiness_checks_not_all_passed", not ready_for_production_deploy),
("production_deploy_not_executed_by_api", True),
("git_commit_not_created_by_api", True),
("git_push_not_executed_by_api", True),
("backup_must_be_verified_by_operator", True),
("production_smoke_must_be_verified_by_operator", True),
)
if blocked
]
required_manual_steps = [
{
"key": "review_worktree_scope",
"label": "審核 worktree只納入市場情報相關變更排除 unrelated dirty files",
"status": "required",
},
{
"key": "run_backup_system",
"label": "重大更新前執行 python backup_system.py",
"status": "required",
},
{
"key": "commit_market_intel_changes_only",
"label": "只 commit 市場情報模組、ADR/TODO 與必要測試",
"status": "operator_optional",
},
{
"key": "push_reviewed_branch_or_main",
"label": "推送已審核分支或 main再進入部署 SOP",
"status": "operator_optional",
},
{
"key": "run_deployment_sop",
"label": "依 deployment SOP app-only 部署,不碰 momo-db",
"status": "required",
},
{
"key": "verify_health_endpoint",
"label": "部署後先驗證 /health不使用首頁作為探測",
"status": "required",
},
{
"key": "verify_market_intel_page_after_deploy",
"label": "驗證 /market_intel 與市場情報 API 仍維持 blocked dry-run",
"status": "required",
},
]
fallback_plan = [
{
"key": "feature_flag_kill_switch",
"label": "MARKET_INTEL_ENABLED、MARKET_INTEL_CRAWLER_ENABLED、MARKET_INTEL_WRITE_ENABLED 保持全關,可立即停用新功能面",
"trigger": "任何 UI/API 異常或非預期連外行為",
},
{
"key": "app_only_rollback",
"label": "回退到上一個已知正常版本後,只 recreate momo-app避免影響 momo-db 資料生命週期",
"trigger": "部署後 /health 或 /market_intel smoke 失敗",
},
{
"key": "scheduler_detached",
"label": "市場情報 scheduler 尚未掛載;異常時不需停爬蟲排程,因為本階段沒有排程入口",
"trigger": "排程或外部流量疑慮",
},
{
"key": "database_write_blocked",
"label": "API/UI writer 仍不寫 DB真寫入只允許 CLI 在完整 gate 通過後執行",
"trigger": "queue writer、seed writer 或 schema smoke 異常",
},
]
safe_deploy_boundaries = [
{
"key": "no_remove_orphans",
"label": "禁止使用 docker compose --remove-orphans",
},
{
"key": "no_momo_db_lifecycle_change",
"label": "禁止 stop/rm/recreate momo-db 或變更資料生命週期",
},
{
"key": "health_probe_only",
"label": "HTTP health / blackbox / CD 探測只打 /health",
},
{
"key": "flags_default_off",
"label": "市場情報三個 feature flags 預設維持 OFF",
},
]
return {
"phase": service.phase,
"mode": "app_only_release_gate",
"production_deployed": False,
"git_committed": False,
"git_pushed": False,
"ready_for_production_deploy": ready_for_production_deploy,
"deployment_actions_executed": False,
"execution_boundary": {
"api_executes_git": False,
"api_executes_backup": False,
"api_executes_scp": False,
"api_executes_ssh": False,
"api_recreates_container": False,
"api_runs_migration": False,
"api_writes_database": False,
},
"checks": checks,
"blocked_reasons": blocked_reasons,
"requires_backup_before_major_update": True,
"backup_command": "python backup_system.py",
"required_manual_steps": required_manual_steps,
"fallback_plan": fallback_plan,
"safe_deploy_boundaries": safe_deploy_boundaries,
"production_smoke_targets": list(PRODUCTION_SMOKE_TARGETS),
"status": status.to_dict(),
"schema_smoke": schema_smoke,
"writer_plan_summary": {
"operation_count": writer_plan["operation_count"],
"writes_executed": writer_plan["writes_executed"],
"would_write_database": writer_plan["would_write_database"],
},
"write_approval_runbook": service.build_write_approval_runbook(),
"migration_blueprint": service.build_migration_blueprint(),
"migration_apply_drill": migration_apply_drill,
"migration_catalog_review": migration_catalog_review,
"migration_live_smoke": migration_live_smoke,
"live_db_inventory": live_db_inventory,
"seed_writer_cli_status": service.build_seed_writer_cli_status(),
"schema_db_probe": service.build_schema_db_probe(),
"platform_seed_db_diff": service.build_platform_seed_db_diff(),
"legacy_source_bridge": service.build_legacy_source_bridge(),
"mcp_readiness": service.build_mcp_readiness(),
"mcp_tool_contract": service.build_mcp_tool_contract(),
"mcp_deploy_preflight": mcp_deploy_preflight,
"mcp_activation_runbook": mcp_activation_runbook,
"mcp_fetch_gate": mcp_fetch_gate,
"scheduler_plan": scheduler_plan,
"manual_sample_plan": manual_sample_plan,
"manual_sample_acceptance": manual_sample_acceptance,
"manual_sample_review": manual_sample_review,
"manual_sample_review_evaluation": manual_sample_review_evaluation,
"manual_sample_candidate_handoff": manual_sample_candidate_handoff,
"manual_sample_candidate_queue_draft": manual_sample_candidate_queue_draft,
"manual_sample_candidate_queue_approval": manual_sample_candidate_queue_approval,
"manual_sample_candidate_queue_transaction": manual_sample_candidate_queue_transaction,
"candidate_queue_writer_preflight": candidate_queue_writer_preflight,
"candidate_queue_writer_cli_status": candidate_queue_writer_cli_status,
"candidate_queue_writer_postwrite_smoke": candidate_queue_writer_postwrite_smoke,
"candidate_queue_writer_operator_drill": candidate_queue_writer_operator_drill,
"candidate_queue_writer_run_package": candidate_queue_writer_run_package,
"candidate_queue_writer_run_readiness": candidate_queue_writer_run_readiness,
"candidate_queue_writer_run_receipt": candidate_queue_writer_run_receipt,
"candidate_queue_writer_run_closeout": candidate_queue_writer_run_closeout,
"candidate_queue_review_handoff": candidate_queue_review_handoff,
"candidate_queue_review_inventory": candidate_queue_review_inventory,
"candidate_queue_review_decision": candidate_queue_review_decision,
"candidate_queue_review_decision_approval": candidate_queue_review_decision_approval,
"candidate_queue_review_decision_transaction": candidate_queue_review_decision_transaction,
"candidate_queue_review_decision_writer_preflight": candidate_queue_review_decision_writer_preflight,
"candidate_queue_review_decision_writer_postwrite_smoke": candidate_queue_review_decision_writer_postwrite_smoke,
"candidate_queue_review_decision_writer_operator_drill": candidate_queue_review_decision_writer_operator_drill,
"candidate_queue_review_decision_writer_run_package": candidate_queue_review_decision_writer_run_package,
"candidate_queue_review_decision_writer_run_readiness": candidate_queue_review_decision_writer_run_readiness,
"candidate_queue_review_decision_writer_run_receipt": candidate_queue_review_decision_writer_run_receipt,
"candidate_queue_review_decision_writer_run_closeout": candidate_queue_review_decision_writer_run_closeout,
"candidate_queue_review_decision_post_closeout_inventory": candidate_queue_review_decision_post_closeout_inventory,
"candidate_queue_review_completion_archive": candidate_queue_review_completion_archive,
"candidate_queue_review_archive_summary": candidate_queue_review_archive_summary,
"candidate_queue_review_ai_summary_preflight": candidate_queue_review_ai_summary_preflight,
"candidate_queue_review_ai_summary_run_package": candidate_queue_review_ai_summary_run_package,
"candidate_queue_review_ai_summary_output_receipt": candidate_queue_review_ai_summary_output_receipt,
"candidate_queue_review_ai_summary_persistence_preflight": candidate_queue_review_ai_summary_persistence_preflight,
"candidate_queue_review_decision_writer_status": candidate_queue_review_decision_writer_status,
"match_review_plan": match_review_plan,
"opportunity_plan": opportunity_plan,
"opportunity_scoring_plan": opportunity_scoring_plan,
"opportunity_evidence_plan": opportunity_evidence_plan,
"opportunity_alert_plan": opportunity_alert_plan,
}