165 lines
6.8 KiB
Python
165 lines
6.8 KiB
Python
"""市場情報正式 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",
|
||
],
|
||
}
|