feat: add ops center and node onboarding flow
This commit is contained in:
556
domain-api/deploy/multi-region/export_go_live_bundle.sh
Executable file
556
domain-api/deploy/multi-region/export_go_live_bundle.sh
Executable file
@@ -0,0 +1,556 @@
|
||||
#!/usr/bin/env bash
|
||||
set -uo 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}"
|
||||
|
||||
MAINLAND_BASE_URL_DEFAULT="$(ops_center_resolve_mainland_api_base_url)"
|
||||
OVERSEAS_BASE_URL_DEFAULT="$(ops_center_resolve_overseas_api_base_url)"
|
||||
REPORT_ROOT_DEFAULT="${OPS_CENTER_REPORT_ROOT}/go-live-bundles"
|
||||
|
||||
REPORT_DIR_INPUT="${1:-}"
|
||||
MAINLAND_BASE_URL_INPUT="${2:-}"
|
||||
OVERSEAS_BASE_URL_INPUT="${3:-}"
|
||||
|
||||
timestamp_now() {
|
||||
date '+%Y%m%d_%H%M%S'
|
||||
}
|
||||
|
||||
normalize_optional_base_url() {
|
||||
local raw_value="${1:-}"
|
||||
local fallback_value="${2:-}"
|
||||
if [[ -z "${raw_value}" ]]; then
|
||||
printf '%s' "${fallback_value}"
|
||||
return
|
||||
fi
|
||||
case "${raw_value}" in
|
||||
-|none|null|disabled)
|
||||
printf ''
|
||||
;;
|
||||
*)
|
||||
printf '%s' "${raw_value}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
if [[ -n "${REPORT_DIR_INPUT}" && "${REPORT_DIR_INPUT}" != http://* && "${REPORT_DIR_INPUT}" != https://* ]]; then
|
||||
REPORT_DIR="${REPORT_DIR_INPUT}"
|
||||
MAINLAND_BASE_URL="${MAINLAND_BASE_URL_INPUT:-${MAINLAND_BASE_URL_DEFAULT}}"
|
||||
OVERSEAS_BASE_URL="$(normalize_optional_base_url "${OVERSEAS_BASE_URL_INPUT:-}" "${OVERSEAS_BASE_URL_DEFAULT}")"
|
||||
else
|
||||
REPORT_DIR="${REPORT_ROOT_DEFAULT}/go-live-bundle-$(timestamp_now)"
|
||||
MAINLAND_BASE_URL="${REPORT_DIR_INPUT:-${MAINLAND_BASE_URL_DEFAULT}}"
|
||||
OVERSEAS_BASE_URL="$(normalize_optional_base_url "${MAINLAND_BASE_URL_INPUT:-}" "${OVERSEAS_BASE_URL_DEFAULT}")"
|
||||
fi
|
||||
|
||||
mkdir -p "${REPORT_DIR}"
|
||||
|
||||
CURL_CONNECT_TIMEOUT="${OPS_CENTER_CURL_CONNECT_TIMEOUT:-3}"
|
||||
CURL_MAX_TIME="${OPS_CENTER_CURL_MAX_TIME:-15}"
|
||||
STEP_COMMAND_TIMEOUT_SECONDS="${OPS_CENTER_REPORT_COMMAND_TIMEOUT_SECONDS:-45}"
|
||||
DOCTOR_DECISION_TIMEOUT_SECONDS="${OPS_CENTER_REPORT_DOCTOR_DECISION_TIMEOUT_SECONDS:-120}"
|
||||
DOCTOR_EXPORT_TIMEOUT_SECONDS="${OPS_CENTER_REPORT_DOCTOR_EXPORT_TIMEOUT_SECONDS:-180}"
|
||||
DOCTOR_INNER_REPORT_TIMEOUT_SECONDS="${OPS_CENTER_DOCTOR_INNER_REPORT_TIMEOUT_SECONDS:-60}"
|
||||
|
||||
ENV_AUDIT_TXT_PATH="${REPORT_DIR}/00_env_audit.txt"
|
||||
SUMMARY_TXT_PATH="${REPORT_DIR}/01_go_live_check_summary.txt"
|
||||
GO_LIVE_JSON_PATH="${REPORT_DIR}/02_go_live_summary.json"
|
||||
STACK_JSON_PATH="${REPORT_DIR}/03_stack_diagnosis.json"
|
||||
DRIVER_FEED_JSON_PATH="${REPORT_DIR}/04_driver_feed.json"
|
||||
CODEX_BRIEF_JSON_PATH="${REPORT_DIR}/05_codex_brief.json"
|
||||
RELEASE_LAUNCHPAD_JSON_PATH="${REPORT_DIR}/06_release_launchpad.json"
|
||||
DOCTOR_DECISION_JSON_PATH="${REPORT_DIR}/07_doctor_decision.json"
|
||||
DOCTOR_EXPORT_STDOUT_PATH="${REPORT_DIR}/08_doctor_export_stdout.txt"
|
||||
MANIFEST_PATH="${REPORT_DIR}/manifest.json"
|
||||
|
||||
declare -a STEP_RESULTS=()
|
||||
|
||||
pretty_print_json_file() {
|
||||
local source_path="${1:-}"
|
||||
python3 - "${source_path}" <<'PY'
|
||||
import json
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
path = pathlib.Path(sys.argv[1])
|
||||
raw_text = path.read_text(encoding="utf-8") if path.exists() else ""
|
||||
try:
|
||||
payload = json.loads(raw_text)
|
||||
except Exception:
|
||||
print(raw_text)
|
||||
else:
|
||||
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
||||
PY
|
||||
}
|
||||
|
||||
record_step_result() {
|
||||
local key="${1:-}"
|
||||
local path="${2:-}"
|
||||
local ok="${3:-false}"
|
||||
local http_status="${4:-}"
|
||||
local exit_code="${5:-0}"
|
||||
STEP_RESULTS+=("${key}|${path}|${ok}|${http_status}|${exit_code}")
|
||||
}
|
||||
|
||||
fetch_json_to_file() {
|
||||
local key="${1:-}"
|
||||
local url="${2:-}"
|
||||
local output_path="${3:-}"
|
||||
local tmp_path
|
||||
tmp_path="$(mktemp)"
|
||||
local raw_text http_status
|
||||
raw_text="$(curl -sS \
|
||||
--connect-timeout "${CURL_CONNECT_TIMEOUT}" \
|
||||
--max-time "${CURL_MAX_TIME}" \
|
||||
"${url}" \
|
||||
-w $'\nHTTP_STATUS=%{http_code}' || true)"
|
||||
http_status="${raw_text##*$'\n'HTTP_STATUS=}"
|
||||
printf '%s' "${raw_text%$'\n'HTTP_STATUS=*}" > "${tmp_path}"
|
||||
pretty_print_json_file "${tmp_path}" > "${output_path}"
|
||||
rm -f "${tmp_path}"
|
||||
if [[ "${http_status}" == 2* ]]; then
|
||||
record_step_result "${key}" "${output_path}" "true" "${http_status}" "0"
|
||||
return 0
|
||||
fi
|
||||
record_step_result "${key}" "${output_path}" "false" "${http_status}" "1"
|
||||
return 1
|
||||
}
|
||||
|
||||
capture_command_to_file() {
|
||||
local key="${1:-}"
|
||||
local output_path="${2:-}"
|
||||
shift 2 || true
|
||||
capture_command_to_file_with_timeout "${STEP_COMMAND_TIMEOUT_SECONDS}" "${key}" "${output_path}" "$@"
|
||||
}
|
||||
|
||||
capture_command_to_file_with_timeout() {
|
||||
local timeout_seconds="${1:-45}"
|
||||
local key="${2:-}"
|
||||
local output_path="${3:-}"
|
||||
shift 3 || true
|
||||
local -a wrapped_command=("$@")
|
||||
local had_errexit="false"
|
||||
case "$-" in
|
||||
*e*) had_errexit="true" ;;
|
||||
esac
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
wrapped_command=(timeout "${timeout_seconds}" "$@")
|
||||
fi
|
||||
set +e
|
||||
"${wrapped_command[@]}" > "${output_path}" 2>&1
|
||||
local exit_code=$?
|
||||
if [[ "${had_errexit}" == "true" ]]; then
|
||||
set -e
|
||||
else
|
||||
set +e
|
||||
fi
|
||||
if [[ "${exit_code}" -eq 0 ]]; then
|
||||
record_step_result "${key}" "${output_path}" "true" "" "${exit_code}"
|
||||
return 0
|
||||
fi
|
||||
record_step_result "${key}" "${output_path}" "false" "" "${exit_code}"
|
||||
return "${exit_code}"
|
||||
}
|
||||
|
||||
materialize_go_live_summary_fallback() {
|
||||
local summary_path="${1:-}"
|
||||
local output_path="${2:-}"
|
||||
python3 - "${summary_path}" "${output_path}" <<'PY'
|
||||
import json
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
summary_path = pathlib.Path(sys.argv[1])
|
||||
output_path = pathlib.Path(sys.argv[2])
|
||||
raw_text = summary_path.read_text(encoding="utf-8") if summary_path.exists() else ""
|
||||
start = raw_text.find("{")
|
||||
end = raw_text.rfind("}")
|
||||
if start < 0 or end < start:
|
||||
sys.exit(1)
|
||||
json_text = raw_text[start:end + 1]
|
||||
payload = json.loads(json_text)
|
||||
if not isinstance(payload, dict):
|
||||
sys.exit(1)
|
||||
output_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
PY
|
||||
}
|
||||
|
||||
override_step_result() {
|
||||
local key="${1:-}"
|
||||
local path="${2:-}"
|
||||
local ok="${3:-false}"
|
||||
local http_status="${4:-}"
|
||||
local exit_code="${5:-0}"
|
||||
local -a updated_results=()
|
||||
local raw_item existing_key
|
||||
for raw_item in "${STEP_RESULTS[@]}"; do
|
||||
existing_key="${raw_item%%|*}"
|
||||
if [[ "${existing_key}" == "${key}" ]]; then
|
||||
continue
|
||||
fi
|
||||
updated_results+=("${raw_item}")
|
||||
done
|
||||
STEP_RESULTS=("${updated_results[@]}")
|
||||
record_step_result "${key}" "${path}" "${ok}" "${http_status}" "${exit_code}"
|
||||
}
|
||||
|
||||
set +e
|
||||
capture_command_to_file \
|
||||
"env_audit" \
|
||||
"${ENV_AUDIT_TXT_PATH}" \
|
||||
bash "${SCRIPT_DIR}/drive_ops_center.sh" env-audit "${MAINLAND_BASE_URL}"
|
||||
|
||||
capture_command_to_file \
|
||||
"go_live_check_summary" \
|
||||
"${SUMMARY_TXT_PATH}" \
|
||||
bash "${SCRIPT_DIR}/drive_ops_center.sh" go-live-check "${MAINLAND_BASE_URL}" "${OVERSEAS_BASE_URL}" summary
|
||||
|
||||
fetch_json_to_file \
|
||||
"go_live_summary" \
|
||||
"${MAINLAND_BASE_URL}/api/v1/ops/go-live-summary?base_url=${MAINLAND_BASE_URL}" \
|
||||
"${GO_LIVE_JSON_PATH}"
|
||||
|
||||
if [[ ! -s "${GO_LIVE_JSON_PATH}" || "$(sed -n '1p' "${GO_LIVE_JSON_PATH}" 2>/dev/null)" != "{"* ]]; then
|
||||
if materialize_go_live_summary_fallback "${SUMMARY_TXT_PATH}" "${GO_LIVE_JSON_PATH}" 2>/dev/null; then
|
||||
override_step_result "go_live_summary" "${GO_LIVE_JSON_PATH}" "true" "fallback" "0"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$(python3 - "${GO_LIVE_JSON_PATH}" <<'PY'
|
||||
import json
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
path = pathlib.Path(sys.argv[1])
|
||||
try:
|
||||
payload = json.loads(path.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
print("invalid")
|
||||
raise SystemExit(0)
|
||||
|
||||
if isinstance(payload, dict) and payload.get("detail") == "Not Found":
|
||||
print("not_found")
|
||||
else:
|
||||
print("ok")
|
||||
PY
|
||||
)" == "not_found" ]]; then
|
||||
if materialize_go_live_summary_fallback "${SUMMARY_TXT_PATH}" "${GO_LIVE_JSON_PATH}" 2>/dev/null; then
|
||||
override_step_result "go_live_summary" "${GO_LIVE_JSON_PATH}" "true" "fallback" "0"
|
||||
fi
|
||||
fi
|
||||
|
||||
fetch_json_to_file \
|
||||
"stack_diagnosis" \
|
||||
"${MAINLAND_BASE_URL}/api/v1/ops/stack-diagnosis?base_url=${MAINLAND_BASE_URL}" \
|
||||
"${STACK_JSON_PATH}"
|
||||
|
||||
fetch_json_to_file \
|
||||
"driver_feed" \
|
||||
"${MAINLAND_BASE_URL}/api/v1/ops/driver-feed" \
|
||||
"${DRIVER_FEED_JSON_PATH}"
|
||||
|
||||
fetch_json_to_file \
|
||||
"codex_brief" \
|
||||
"${MAINLAND_BASE_URL}/api/v1/ops/codex-brief" \
|
||||
"${CODEX_BRIEF_JSON_PATH}"
|
||||
|
||||
fetch_json_to_file \
|
||||
"release_launchpad" \
|
||||
"${MAINLAND_BASE_URL}/api/v1/ops/releases/launchpad" \
|
||||
"${RELEASE_LAUNCHPAD_JSON_PATH}"
|
||||
|
||||
capture_command_to_file_with_timeout \
|
||||
"${DOCTOR_EXPORT_TIMEOUT_SECONDS}" \
|
||||
"doctor_export" \
|
||||
"${DOCTOR_EXPORT_STDOUT_PATH}" \
|
||||
env "OPS_CENTER_DOCTOR_REPORT_TIMEOUT_SECONDS=${DOCTOR_INNER_REPORT_TIMEOUT_SECONDS}" \
|
||||
bash "${SCRIPT_DIR}/drive_ops_center.sh" doctor-export "${REPORT_DIR}/doctor-export" "${MAINLAND_BASE_URL}" "${OVERSEAS_BASE_URL}"
|
||||
|
||||
capture_command_to_file_with_timeout \
|
||||
"${DOCTOR_DECISION_TIMEOUT_SECONDS}" \
|
||||
"doctor_decision" \
|
||||
"${DOCTOR_DECISION_JSON_PATH}" \
|
||||
bash "${SCRIPT_DIR}/drive_ops_center.sh" doctor-decision "${REPORT_DIR}/doctor-export"
|
||||
set -e
|
||||
|
||||
python3 - <<'PY' \
|
||||
"${MANIFEST_PATH}" \
|
||||
"${REPORT_DIR}" \
|
||||
"${MAINLAND_BASE_URL}" \
|
||||
"${OVERSEAS_BASE_URL}" \
|
||||
"${ENV_AUDIT_TXT_PATH}" \
|
||||
"${SUMMARY_TXT_PATH}" \
|
||||
"${GO_LIVE_JSON_PATH}" \
|
||||
"${STACK_JSON_PATH}" \
|
||||
"${DRIVER_FEED_JSON_PATH}" \
|
||||
"${CODEX_BRIEF_JSON_PATH}" \
|
||||
"${RELEASE_LAUNCHPAD_JSON_PATH}" \
|
||||
"${DOCTOR_DECISION_JSON_PATH}" \
|
||||
"${DOCTOR_EXPORT_STDOUT_PATH}" \
|
||||
"${STEP_RESULTS[@]}"
|
||||
import json
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
manifest_path = pathlib.Path(sys.argv[1])
|
||||
report_dir = pathlib.Path(sys.argv[2])
|
||||
mainland_base_url = sys.argv[3]
|
||||
overseas_base_url = sys.argv[4]
|
||||
|
||||
file_keys = [
|
||||
"env_audit",
|
||||
"go_live_check_summary",
|
||||
"go_live_summary",
|
||||
"stack_diagnosis",
|
||||
"driver_feed",
|
||||
"codex_brief",
|
||||
"release_launchpad",
|
||||
"doctor_decision",
|
||||
"doctor_export",
|
||||
]
|
||||
file_paths = {
|
||||
"env_audit": pathlib.Path(sys.argv[5]),
|
||||
"go_live_check_summary": pathlib.Path(sys.argv[6]),
|
||||
"go_live_summary": pathlib.Path(sys.argv[7]),
|
||||
"stack_diagnosis": pathlib.Path(sys.argv[8]),
|
||||
"driver_feed": pathlib.Path(sys.argv[9]),
|
||||
"codex_brief": pathlib.Path(sys.argv[10]),
|
||||
"release_launchpad": pathlib.Path(sys.argv[11]),
|
||||
"doctor_decision": pathlib.Path(sys.argv[12]),
|
||||
"doctor_export": pathlib.Path(sys.argv[13]),
|
||||
}
|
||||
|
||||
step_results = {}
|
||||
for raw_item in sys.argv[14:]:
|
||||
parts = raw_item.split("|", 4)
|
||||
if len(parts) != 5:
|
||||
continue
|
||||
key, path_value, ok_value, http_status, exit_code = parts
|
||||
step_results[key] = {
|
||||
"path": path_value,
|
||||
"ok": ok_value == "true",
|
||||
"http_status": http_status,
|
||||
"exit_code": int(exit_code or 0),
|
||||
}
|
||||
|
||||
|
||||
def load_json_file(path: pathlib.Path) -> dict:
|
||||
if not path.is_file():
|
||||
return {}
|
||||
try:
|
||||
payload = json.loads(path.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
return {}
|
||||
return payload if isinstance(payload, dict) else {}
|
||||
|
||||
|
||||
def load_env_audit_summary(path: pathlib.Path) -> dict:
|
||||
if not path.is_file():
|
||||
return {}
|
||||
raw_text = path.read_text(encoding="utf-8", errors="replace")
|
||||
marker = "[8/8] condensed env audit summary"
|
||||
candidate_text = raw_text.split(marker, 1)[1].strip() if marker in raw_text else raw_text.strip()
|
||||
if not candidate_text:
|
||||
return {}
|
||||
try:
|
||||
payload = json.loads(candidate_text)
|
||||
except Exception:
|
||||
return {}
|
||||
return payload if isinstance(payload, dict) else {}
|
||||
|
||||
failures = []
|
||||
artifacts = []
|
||||
for key in file_keys:
|
||||
artifact_path = file_paths[key]
|
||||
result = dict(step_results.get(key) or {})
|
||||
if not result:
|
||||
result = {
|
||||
"path": str(artifact_path),
|
||||
"ok": artifact_path.exists(),
|
||||
"http_status": "",
|
||||
"exit_code": 0 if artifact_path.exists() else 1,
|
||||
}
|
||||
entry = {
|
||||
"key": key,
|
||||
"path": str(artifact_path),
|
||||
"exists": artifact_path.exists(),
|
||||
"ok": bool(result.get("ok", False)),
|
||||
"http_status": str(result.get("http_status") or ""),
|
||||
"exit_code": int(result.get("exit_code", 0) or 0),
|
||||
}
|
||||
artifacts.append(entry)
|
||||
if not entry["ok"]:
|
||||
failures.append(key)
|
||||
|
||||
go_live_summary_payload = load_json_file(file_paths["go_live_summary"])
|
||||
stack_diagnosis_payload = load_json_file(file_paths["stack_diagnosis"])
|
||||
driver_feed_payload = load_json_file(file_paths["driver_feed"])
|
||||
codex_brief_payload = load_json_file(file_paths["codex_brief"])
|
||||
env_audit_summary = load_env_audit_summary(file_paths["env_audit"])
|
||||
|
||||
stack_diagnosis = dict(stack_diagnosis_payload.get("diagnosis") or {})
|
||||
driver_feed_summary = dict(driver_feed_payload.get("summary") or {})
|
||||
codex_brief_summary = dict(codex_brief_payload.get("summary") or {})
|
||||
|
||||
|
||||
def first_nonempty(*values: object) -> str:
|
||||
for value in values:
|
||||
normalized = str(value or "").strip()
|
||||
if normalized:
|
||||
return normalized
|
||||
return ""
|
||||
|
||||
|
||||
def nonempty_distinct(values: dict[str, str]) -> list[str]:
|
||||
return sorted({str(value or "").strip() for value in values.values() if str(value or "").strip()})
|
||||
|
||||
|
||||
def distinct_ints(values: dict[str, int], available_sources: set[str]) -> list[int]:
|
||||
return sorted({int(values[source]) for source in values if source in available_sources})
|
||||
|
||||
|
||||
launchpad_target_node_codes = {
|
||||
"go_live_summary": str(go_live_summary_payload.get("launchpad_recommended_target_node_code") or "").strip(),
|
||||
"stack_diagnosis": str(stack_diagnosis.get("launchpad_recommended_target_node_code") or "").strip(),
|
||||
"driver_feed": str(driver_feed_summary.get("launchpad_recommended_target_node_code") or "").strip(),
|
||||
"codex_brief": str(codex_brief_summary.get("launchpad_recommended_target_node_code") or "").strip(),
|
||||
}
|
||||
launchpad_recovery_labels = {
|
||||
"go_live_summary": str(go_live_summary_payload.get("launchpad_recommended_recovery_label") or "").strip(),
|
||||
"stack_diagnosis": str(stack_diagnosis.get("launchpad_recommended_recovery_label") or "").strip(),
|
||||
"driver_feed": str(driver_feed_summary.get("launchpad_recommended_recovery_label") or "").strip(),
|
||||
"codex_brief": str(codex_brief_summary.get("launchpad_recommended_recovery_label") or "").strip(),
|
||||
}
|
||||
launchpad_recovery_summaries = {
|
||||
"go_live_summary": str(go_live_summary_payload.get("launchpad_recommended_recovery_summary") or "").strip(),
|
||||
"stack_diagnosis": str(stack_diagnosis.get("launchpad_recommended_recovery_summary") or "").strip(),
|
||||
"driver_feed": str(driver_feed_summary.get("launchpad_recommended_recovery_summary") or "").strip(),
|
||||
"codex_brief": str(codex_brief_summary.get("launchpad_recommended_recovery_summary") or "").strip(),
|
||||
}
|
||||
launchpad_bootstrap_pending_nodes = {
|
||||
"go_live_summary": int(go_live_summary_payload.get("launchpad_onboarding_bootstrap_pending_nodes", 0) or 0),
|
||||
"stack_diagnosis": int(stack_diagnosis.get("launchpad_onboarding_bootstrap_pending_nodes", 0) or 0),
|
||||
"driver_feed": int(driver_feed_summary.get("launchpad_onboarding_bootstrap_pending_nodes", 0) or 0),
|
||||
"codex_brief": int(codex_brief_summary.get("launchpad_onboarding_bootstrap_pending_nodes", 0) or 0),
|
||||
}
|
||||
launchpad_acceptance_ready_nodes = {
|
||||
"go_live_summary": int(go_live_summary_payload.get("launchpad_onboarding_acceptance_ready_nodes", 0) or 0),
|
||||
"stack_diagnosis": int(stack_diagnosis.get("launchpad_onboarding_acceptance_ready_nodes", 0) or 0),
|
||||
"driver_feed": int(driver_feed_summary.get("launchpad_onboarding_acceptance_ready_nodes", 0) or 0),
|
||||
"codex_brief": int(codex_brief_summary.get("launchpad_onboarding_acceptance_ready_nodes", 0) or 0),
|
||||
}
|
||||
available_alignment_sources = {
|
||||
source
|
||||
for source, payload_value in {
|
||||
"go_live_summary": go_live_summary_payload,
|
||||
"stack_diagnosis": stack_diagnosis_payload,
|
||||
"driver_feed": driver_feed_payload,
|
||||
"codex_brief": codex_brief_payload,
|
||||
}.items()
|
||||
if isinstance(payload_value, dict) and payload_value
|
||||
}
|
||||
launchpad_alignment = {
|
||||
"consistent": True,
|
||||
"target_node_code_consistent": True,
|
||||
"recovery_label_consistent": True,
|
||||
"recovery_summary_consistent": True,
|
||||
"bootstrap_pending_consistent": True,
|
||||
"acceptance_ready_consistent": True,
|
||||
"available_sources": sorted(available_alignment_sources),
|
||||
"target_node_codes": launchpad_target_node_codes,
|
||||
"recovery_labels": launchpad_recovery_labels,
|
||||
"recovery_summaries": launchpad_recovery_summaries,
|
||||
"bootstrap_pending_nodes": launchpad_bootstrap_pending_nodes,
|
||||
"acceptance_ready_nodes": launchpad_acceptance_ready_nodes,
|
||||
}
|
||||
launchpad_alignment["target_node_code_consistent"] = len(nonempty_distinct({
|
||||
source: value for source, value in launchpad_target_node_codes.items() if source in available_alignment_sources
|
||||
})) <= 1
|
||||
launchpad_alignment["recovery_label_consistent"] = len(nonempty_distinct({
|
||||
source: value for source, value in launchpad_recovery_labels.items() if source in available_alignment_sources
|
||||
})) <= 1
|
||||
launchpad_alignment["recovery_summary_consistent"] = len(nonempty_distinct({
|
||||
source: value for source, value in launchpad_recovery_summaries.items() if source in available_alignment_sources
|
||||
})) <= 1
|
||||
launchpad_alignment["bootstrap_pending_consistent"] = len(
|
||||
distinct_ints(launchpad_bootstrap_pending_nodes, available_alignment_sources)
|
||||
) <= 1
|
||||
launchpad_alignment["acceptance_ready_consistent"] = len(
|
||||
distinct_ints(launchpad_acceptance_ready_nodes, available_alignment_sources)
|
||||
) <= 1
|
||||
launchpad_alignment["consistent"] = all([
|
||||
bool(launchpad_alignment["target_node_code_consistent"]),
|
||||
bool(launchpad_alignment["recovery_label_consistent"]),
|
||||
bool(launchpad_alignment["recovery_summary_consistent"]),
|
||||
bool(launchpad_alignment["bootstrap_pending_consistent"]),
|
||||
bool(launchpad_alignment["acceptance_ready_consistent"]),
|
||||
])
|
||||
|
||||
env_audit_status = str(env_audit_summary.get("status") or "").strip()
|
||||
env_audit_runtime_may_need_restart = bool(((env_audit_summary.get("runtime") or {}).get("runtime_may_need_restart", False)))
|
||||
review_status_hint = "ready"
|
||||
review_headline = "核心上线证据已经齐备,可进入最终人工复核或正式发布门禁。"
|
||||
if failures or env_audit_status == "blocked":
|
||||
review_status_hint = "blocked"
|
||||
review_headline = "当前导出包仍有关键阻断项,不能直接作为正式上线终稿。"
|
||||
elif env_audit_status == "attention" or not launchpad_alignment["consistent"]:
|
||||
review_status_hint = "attention"
|
||||
if not launchpad_alignment["consistent"]:
|
||||
review_headline = "核心报告虽已导出,但 launchpad 摘要在不同 surface 之间存在漂移,建议先重新复核。"
|
||||
else:
|
||||
review_headline = "核心报告已齐,但环境审计仍提示存在待收口事项。"
|
||||
|
||||
payload = {
|
||||
"report_dir": str(report_dir),
|
||||
"mainland_base_url": mainland_base_url,
|
||||
"overseas_base_url": overseas_base_url,
|
||||
"artifacts": artifacts,
|
||||
"failures": failures,
|
||||
"summary": {
|
||||
"ok": len(failures) == 0,
|
||||
"artifact_total": len(artifacts),
|
||||
"failure_total": len(failures),
|
||||
"review_status_hint": review_status_hint,
|
||||
"review_headline": review_headline,
|
||||
"env_audit_status": env_audit_status,
|
||||
"env_audit_runtime_may_need_restart": env_audit_runtime_may_need_restart,
|
||||
"launchpad_recommended_target_node_code": first_nonempty(
|
||||
go_live_summary_payload.get("launchpad_recommended_target_node_code"),
|
||||
stack_diagnosis.get("launchpad_recommended_target_node_code"),
|
||||
driver_feed_summary.get("launchpad_recommended_target_node_code"),
|
||||
codex_brief_summary.get("launchpad_recommended_target_node_code"),
|
||||
),
|
||||
"launchpad_recommended_recovery_label": first_nonempty(
|
||||
go_live_summary_payload.get("launchpad_recommended_recovery_label"),
|
||||
stack_diagnosis.get("launchpad_recommended_recovery_label"),
|
||||
driver_feed_summary.get("launchpad_recommended_recovery_label"),
|
||||
codex_brief_summary.get("launchpad_recommended_recovery_label"),
|
||||
),
|
||||
"launchpad_recommended_recovery_summary": first_nonempty(
|
||||
go_live_summary_payload.get("launchpad_recommended_recovery_summary"),
|
||||
stack_diagnosis.get("launchpad_recommended_recovery_summary"),
|
||||
driver_feed_summary.get("launchpad_recommended_recovery_summary"),
|
||||
codex_brief_summary.get("launchpad_recommended_recovery_summary"),
|
||||
),
|
||||
"launchpad_onboarding_bootstrap_pending_nodes": max(launchpad_bootstrap_pending_nodes.values() or [0]),
|
||||
"launchpad_onboarding_acceptance_ready_nodes": max(launchpad_acceptance_ready_nodes.values() or [0]),
|
||||
"launchpad_alignment": launchpad_alignment,
|
||||
"recommended_reading_order": [
|
||||
"00_env_audit.txt",
|
||||
"01_go_live_check_summary.txt",
|
||||
"02_go_live_summary.json",
|
||||
"03_stack_diagnosis.json",
|
||||
"04_driver_feed.json",
|
||||
"05_codex_brief.json",
|
||||
"06_release_launchpad.json",
|
||||
"07_doctor_decision.json",
|
||||
"08_doctor_export_stdout.txt",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
manifest_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
||||
PY
|
||||
Reference in New Issue
Block a user