Files
ewoooc/services/market_intel/migration_live_smoke.py
OoO c4b92ce9f5
All checks were successful
CD Pipeline / deploy (push) Successful in 1m55s
新增市場情報正式 DB 只讀 smoke
2026-05-18 20:26:04 +08:00

165 lines
6.8 KiB
Python
Raw Permalink 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.
"""市場情報正式 DB 只讀 smoke 判讀。
本模組只包裝 migration catalog review 的結果,讓操作員能安全地執行
`execute=true` 只讀 smoke不執行 migration、不寫 DB、不跑 rollback。
"""
def _smoke_result(catalog_review, *, execute_requested):
catalog_state = catalog_review.get("catalog_state")
seed_state = catalog_review.get("seed_state")
if not execute_requested:
return "planned_no_execution"
if catalog_state == "probe_error":
return "failed_catalog_probe_error"
if catalog_state == "partial_schema":
return "attention_partial_schema"
if catalog_state == "already_applied":
return "passed_already_applied"
if catalog_state == "not_applied" and seed_state == "probe_error":
return "passed_not_applied_seed_table_missing"
if catalog_state == "not_applied":
return "passed_not_applied"
return "review_required"
def _build_blocked_reasons(*, execute_requested, catalog_review, safety_checks):
blocked_reasons = [
"migration_not_executed_by_live_smoke",
"api_never_runs_migration",
"database_write_still_blocked",
]
blocked_reasons.extend(
key for key, passed in safety_checks.items()
if not passed
)
if not execute_requested:
blocked_reasons.append("execute_false_planned_only")
if catalog_review.get("catalog_state") == "probe_error":
blocked_reasons.append("catalog_probe_error")
if catalog_review.get("catalog_state") == "partial_schema":
blocked_reasons.append("partial_schema_requires_manual_reconciliation")
if catalog_review.get("catalog_state") == "already_applied":
blocked_reasons.append("market_schema_already_present")
return blocked_reasons
def build_migration_live_smoke_preview(*, runtime_status, catalog_review):
"""建立正式 DB 只讀 smoke payload不執行 migration 或 DB write。"""
execute_requested = bool(catalog_review.get("execute_requested"))
catalog_state = catalog_review.get("catalog_state")
seed_state = catalog_review.get("seed_state")
seed_probe_error_tolerated = bool(
execute_requested
and catalog_state == "not_applied"
and seed_state == "probe_error"
)
read_only_probe_completed = bool(
catalog_review.get("read_only_query_executed")
and catalog_state != "probe_error"
)
safety_checks = {
"feature_flags_default_safe": bool(
not runtime_status.enabled
and not runtime_status.crawler_enabled
and not runtime_status.write_enabled
),
"database_write_blocked": bool(not runtime_status.database_write_allowed),
"scheduler_detached": bool(not runtime_status.scheduler_attached),
"catalog_review_safe": bool(
catalog_review.get("review_ready")
and not catalog_review.get("database_write_executed")
and not catalog_review.get("database_commit_executed")
and not catalog_review.get("database_session_created")
and not catalog_review.get("migration_executed")
and not catalog_review.get("rollback_executed")
),
"read_only_probe_completed_when_requested": bool(
not execute_requested or read_only_probe_completed
),
"seed_probe_error_tolerable": bool(
seed_state != "probe_error" or seed_probe_error_tolerated
),
}
live_smoke_passed = bool(
execute_requested
and read_only_probe_completed
and all(safety_checks.values())
)
smoke_result = _smoke_result(
catalog_review,
execute_requested=execute_requested,
)
return {
"mode": "migration_live_smoke_preview",
"execute_requested": execute_requested,
"smoke_result": smoke_result,
"live_smoke_passed": live_smoke_passed,
"catalog_state": catalog_state,
"seed_state": seed_state,
"risk_level": catalog_review.get("risk_level"),
"apply_path": catalog_review.get("apply_path"),
"read_only_probe_completed": read_only_probe_completed,
"seed_probe_error_tolerated": seed_probe_error_tolerated,
"ready_for_manual_migration_review": bool(
catalog_review.get("ready_for_manual_migration_review")
),
"ready_to_apply_migration": False,
"migration_executed": False,
"rollback_executed": False,
"database_connection_opened": bool(
catalog_review.get("database_connection_opened")
),
"read_only_query_executed": bool(
catalog_review.get("read_only_query_executed")
),
"database_session_created": False,
"explicit_transaction_opened": False,
"database_write_executed": False,
"database_commit_executed": False,
"external_network_executed": False,
"scheduler_attached": False,
"api_executes_migration": False,
"api_executes_rollback": False,
"safety_checks": safety_checks,
"blocked_reasons": _build_blocked_reasons(
execute_requested=execute_requested,
catalog_review=catalog_review,
safety_checks=safety_checks,
),
"catalog_review_summary": {
"catalog_state": catalog_state,
"seed_state": seed_state,
"risk_level": catalog_review.get("risk_level"),
"apply_path": catalog_review.get("apply_path"),
"table_catalog": catalog_review.get("table_catalog") or {},
"seed_catalog": catalog_review.get("seed_catalog") or {},
"finding_count": len(catalog_review.get("findings") or []),
"findings": catalog_review.get("findings") or [],
},
"operator_next_steps": [
{
"key": "run_live_smoke_execute_true",
"label": "人工呼叫 /api/market_intel/migration_live_smoke?execute=true 執行只讀 smoke",
"status": "completed" if execute_requested else "required",
},
{
"key": "interpret_smoke_result",
"label": "依 smoke_result 判斷正式 DB 目前是否尚未套表、已套表、partial schema 或 probe error",
"status": "required",
},
{
"key": "manual_migration_review_only",
"label": "只有 smoke 顯示 not_applied 且人工批准後,才可另開維護窗口手動執行 migration",
"status": "blocked_by_operator_approval",
},
],
"manual_probe_targets": [
"/api/market_intel/migration_live_smoke?execute=true",
"/api/market_intel/migration_catalog_review?execute=true",
"/api/market_intel/schema_db_probe?execute=true",
"/api/market_intel/platform_seed_db_diff?execute=true",
],
}