#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck disable=SC1091 source "${SCRIPT_DIR}/lib_ops_center.sh" ops_center_load_config "${SCRIPT_DIR}" TARGET_OR_BASE_URL="${1:-}" SECOND_ARG="${2:-}" REPORT_DIR="" MANIFEST_PATH="" GENERATED_BUNDLE="false" MAINLAND_BASE_URL="" OVERSEAS_BASE_URL="" if [[ -n "${TARGET_OR_BASE_URL}" && ( -f "${TARGET_OR_BASE_URL}" || -d "${TARGET_OR_BASE_URL}" ) ]]; then if [[ -d "${TARGET_OR_BASE_URL}" ]]; then REPORT_DIR="${TARGET_OR_BASE_URL%/}" MANIFEST_PATH="${REPORT_DIR}/manifest.json" else MANIFEST_PATH="${TARGET_OR_BASE_URL}" REPORT_DIR="$(cd "$(dirname "${MANIFEST_PATH}")" && pwd)" fi else MAINLAND_BASE_URL="${TARGET_OR_BASE_URL:-$(ops_center_resolve_mainland_api_base_url)}" OVERSEAS_BASE_URL="$(normalize_optional_base_url_arg "${SECOND_ARG:-}" "$(ops_center_resolve_overseas_api_base_url)")" REPORT_DIR="${OPS_CENTER_REPORT_ROOT}/go-live-bundles/go-live-signoff-$(timestamp_now)" ops_center_ensure_report_dir "${REPORT_DIR}" bash "${SCRIPT_DIR}/export_go_live_bundle.sh" "${REPORT_DIR}" "${MAINLAND_BASE_URL}" "${OVERSEAS_BASE_URL}" >/dev/null MANIFEST_PATH="${REPORT_DIR}/manifest.json" GENERATED_BUNDLE="true" fi if [[ ! -f "${MANIFEST_PATH}" ]]; then echo "manifest not found: ${MANIFEST_PATH}" >&2 exit 1 fi REVIEW_JSON="$(bash "${SCRIPT_DIR}/review_go_live_bundle.sh" "${MANIFEST_PATH}")" DOCTOR_JSON="$(bash "${SCRIPT_DIR}/drive_ops_center.sh" doctor-decision "${MANIFEST_PATH}")" python3 - <<'PY' \ "${REVIEW_JSON}" \ "${DOCTOR_JSON}" \ "${GENERATED_BUNDLE}" \ "${REPORT_DIR}" \ "${MANIFEST_PATH}" \ "${MAINLAND_BASE_URL}" \ "${OVERSEAS_BASE_URL}" import json import sys review = json.loads(sys.argv[1]) doctor = json.loads(sys.argv[2]) generated_bundle = str(sys.argv[3]).strip().lower() == "true" report_dir = str(sys.argv[4] or "").strip() manifest_path = str(sys.argv[5] or "").strip() mainland_base_url = str(sys.argv[6] or "").strip() overseas_base_url = str(sys.argv[7] or "").strip() go_live_summary = dict(review.get("go_live_summary") or {}) stack_diagnosis = dict(review.get("stack_diagnosis") or {}) driver_feed = dict(review.get("driver_feed") or {}) codex_brief = dict(review.get("codex_brief") or {}) launchpad_alignment = dict(review.get("launchpad_alignment") or {}) doctor_decision = dict(doctor.get("decision") or {}) doctor_summary = dict(doctor.get("summary") or {}) review_status = str(review.get("status") or "").strip() doctor_status = str(doctor_decision.get("status") or "").strip() go_live_status = str(go_live_summary.get("go_live_status") or "").strip() publish_status = str(go_live_summary.get("publish_status") or "").strip() stack_status = str(stack_diagnosis.get("stack_status") or "").strip() driver_launch_status = str(driver_feed.get("launch_status") or "").strip() codex_launch_status = str(codex_brief.get("launch_status") or "").strip() def dedupe_commands(values): result = [] seen = set() for item in values: text = str(item or "").strip() if not text or text in seen: continue seen.add(text) result.append(text) return result blocked_reasons = dedupe_commands( [] + ([f"review:{review_status}"] if review_status == "blocked" else []) + ([f"doctor:{doctor_status}"] if doctor_status == "blocked" else []) + ([f"go_live:{go_live_status}"] if go_live_status == "blocked" else []) + ([f"publish:{publish_status}"] if publish_status == "blocked" else []) + ([f"stack:{stack_status}"] if stack_status == "blocked" else []) + ([f"driver_launch:{driver_launch_status}"] if driver_launch_status == "blocked" else []) + ([f"codex_launch:{codex_launch_status}"] if codex_launch_status == "blocked" else []) ) attention_reasons = dedupe_commands( [] + ([f"review:{review_status}"] if review_status == "attention" else []) + ([f"doctor:{doctor_status}"] if doctor_status == "attention" else []) + ([f"go_live:{go_live_status}"] if go_live_status == "attention" else []) + ([f"publish:{publish_status}"] if publish_status == "attention" else []) + ([f"stack:{stack_status}"] if stack_status == "attention" else []) + ([f"driver_launch:{driver_launch_status}"] if driver_launch_status == "attention" else []) + ([f"codex_launch:{codex_launch_status}"] if codex_launch_status == "attention" else []) + (["launchpad_alignment:inconsistent"] if not bool(launchpad_alignment.get("consistent", False)) else []) ) if blocked_reasons: signoff_status = "blocked" headline = ( str(review.get("headline") or "").strip() or str(doctor_decision.get("headline") or "").strip() or "当前发布前验证仍存在阻断项,不应直接签字上线。" ) elif attention_reasons: signoff_status = "attention" headline = ( str(doctor_decision.get("headline") or "").strip() or str(review.get("headline") or "").strip() or "当前现场已进入收口区间,但仍建议先完成 attention 项再正式签字。" ) else: signoff_status = "ready" headline = "当前证据包、doctor 结论与发布门禁一致,可进入最终人工签字或正式发布。" recommended_commands = dedupe_commands( [] + list(doctor_decision.get("recommended_commands") or []) + [ f"bash domain-api/deploy/multi-region/drive_ops_center.sh go-live-review {manifest_path}", f"bash domain-api/deploy/multi-region/drive_ops_center.sh doctor-decision {manifest_path}", ] ) payload = { "signoff_status": signoff_status, "headline": headline, "generated_bundle": generated_bundle, "report_dir": report_dir or str(doctor.get("report_dir") or review.get("report_dir") or ""), "manifest_path": manifest_path or str(doctor.get("manifest_path") or ""), "base_urls": { "mainland": mainland_base_url, "overseas": overseas_base_url, }, "release_gate": { "go_live_status": go_live_status, "publish_status": publish_status, "stack_status": stack_status, "driver_launch_status": driver_launch_status, "codex_launch_status": codex_launch_status, "bundle_review_status": review_status, "doctor_status": doctor_status, }, "blocked_reasons": blocked_reasons, "attention_reasons": attention_reasons, "decision": { "operator_title": str(go_live_summary.get("operator_title") or "").strip(), "next_step_action_code": str(go_live_summary.get("next_step_action_code") or "").strip(), "doctor_reason_code": str(doctor_decision.get("reason_code") or "").strip(), "doctor_preferred_surface": str(doctor_decision.get("preferred_surface") or "").strip(), "launchpad_recommended_target_node_code": str(go_live_summary.get("launchpad_recommended_target_node_code") or "").strip(), "launchpad_recommended_recovery_label": str(go_live_summary.get("launchpad_recommended_recovery_label") or "").strip(), "launchpad_recommended_recovery_summary": str(go_live_summary.get("launchpad_recommended_recovery_summary") or "").strip(), }, "launchpad_alignment": launchpad_alignment, "recommended_commands": recommended_commands, "bundle_review": review, "doctor_decision": { "summary": doctor_summary, "decision": doctor_decision, }, } print(json.dumps(payload, ensure_ascii=False, indent=2)) PY