#!/usr/bin/env bash set -euo pipefail TARGET_PATH="${1:-}" if [[ -z "${TARGET_PATH}" ]]; then echo "usage: bash domain-api/deploy/multi-region/review_go_live_bundle.sh " >&2 exit 1 fi if [[ -d "${TARGET_PATH}" ]]; then MANIFEST_PATH="${TARGET_PATH%/}/manifest.json" else MANIFEST_PATH="${TARGET_PATH}" fi if [[ ! -f "${MANIFEST_PATH}" ]]; then echo "manifest not found: ${MANIFEST_PATH}" >&2 exit 1 fi python3 - "${MANIFEST_PATH}" <<'PY' import json import pathlib import sys manifest_path = pathlib.Path(sys.argv[1]) payload = json.loads(manifest_path.read_text(encoding="utf-8")) artifacts = payload.get("artifacts") or [] failures = payload.get("failures") or [] summary = payload.get("summary") or {} critical_keys = {"go_live_summary", "stack_diagnosis", "driver_feed", "codex_brief", "release_launchpad"} artifact_by_key = {str(item.get("key") or ""): item for item in artifacts if isinstance(item, dict)} critical_failures = [key for key in critical_keys if not bool((artifact_by_key.get(key) or {}).get("ok", False))] noncritical_failures = [key for key in failures if key not in critical_keys] env_audit_artifact = artifact_by_key.get("env_audit") or {} env_audit_status = "" env_audit_headline = "" env_audit_missing_items = [] env_audit_recommended_actions = [] env_audit_blocking = False env_audit_attention = False env_audit_runtime_may_need_restart = False go_live_summary_payload = {} stack_diagnosis_payload = {} driver_feed_payload = {} codex_brief_payload = {} def load_json_artifact(key: str) -> dict: artifact = artifact_by_key.get(key) or {} artifact_path = pathlib.Path(str(artifact.get("path") or "")) if not artifact_path.is_file(): return {} try: loaded = json.loads(artifact_path.read_text(encoding="utf-8")) except Exception: return {} return loaded if isinstance(loaded, dict) else {} env_audit_path = pathlib.Path(str(env_audit_artifact.get("path") or "")) if env_audit_path.is_file(): raw_text = env_audit_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 candidate_text: try: env_payload = json.loads(candidate_text) except Exception: env_payload = {} env_audit_status = str(env_payload.get("status") or "") env_audit_headline = str(env_payload.get("headline") or "") env_audit_missing_items = list(env_payload.get("missing_items") or []) env_audit_recommended_actions = [str(item).strip() for item in list(env_payload.get("recommended_actions") or []) if str(item).strip()] env_audit_runtime_may_need_restart = bool(((env_payload.get("runtime") or {}).get("runtime_may_need_restart", False))) env_audit_blocking = env_audit_status == "blocked" env_audit_attention = env_audit_status == "attention" go_live_summary_payload = load_json_artifact("go_live_summary") stack_diagnosis_payload = load_json_artifact("stack_diagnosis") driver_feed_payload = load_json_artifact("driver_feed") codex_brief_payload = load_json_artifact("codex_brief") go_live_launchpad_target_node_code = str(go_live_summary_payload.get("launchpad_recommended_target_node_code") or "").strip() go_live_launchpad_recovery_label = str(go_live_summary_payload.get("launchpad_recommended_recovery_label") or "").strip() go_live_launchpad_recovery_summary = str(go_live_summary_payload.get("launchpad_recommended_recovery_summary") or "").strip() go_live_launchpad_bootstrap_pending_nodes = int(go_live_summary_payload.get("launchpad_onboarding_bootstrap_pending_nodes", 0) or 0) go_live_launchpad_acceptance_ready_nodes = int(go_live_summary_payload.get("launchpad_onboarding_acceptance_ready_nodes", 0) or 0) stack_diagnosis = stack_diagnosis_payload.get("diagnosis") or {} stack_launchpad_target_node_code = str(stack_diagnosis.get("launchpad_recommended_target_node_code") or "").strip() stack_launchpad_recovery_label = str(stack_diagnosis.get("launchpad_recommended_recovery_label") or "").strip() stack_launchpad_recovery_summary = str(stack_diagnosis.get("launchpad_recommended_recovery_summary") or "").strip() stack_launchpad_bootstrap_pending_nodes = int(stack_diagnosis.get("launchpad_onboarding_bootstrap_pending_nodes", 0) or 0) stack_launchpad_acceptance_ready_nodes = int(stack_diagnosis.get("launchpad_onboarding_acceptance_ready_nodes", 0) or 0) driver_feed_summary = driver_feed_payload.get("summary") or {} codex_brief_summary = codex_brief_payload.get("summary") or {} 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": go_live_launchpad_target_node_code, "stack_diagnosis": stack_launchpad_target_node_code, "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": go_live_launchpad_recovery_label, "stack_diagnosis": stack_launchpad_recovery_label, "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": go_live_launchpad_recovery_summary, "stack_diagnosis": stack_launchpad_recovery_summary, "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": go_live_launchpad_bootstrap_pending_nodes, "stack_diagnosis": stack_launchpad_bootstrap_pending_nodes, "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": go_live_launchpad_acceptance_ready_nodes, "stack_diagnosis": stack_launchpad_acceptance_ready_nodes, "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_target_consistent = len(nonempty_distinct({ source: value for source, value in launchpad_target_node_codes.items() if source in available_alignment_sources })) <= 1 launchpad_recovery_label_consistent = len(nonempty_distinct({ source: value for source, value in launchpad_recovery_labels.items() if source in available_alignment_sources })) <= 1 launchpad_recovery_summary_consistent = len(nonempty_distinct({ source: value for source, value in launchpad_recovery_summaries.items() if source in available_alignment_sources })) <= 1 launchpad_bootstrap_pending_consistent = len(distinct_ints(launchpad_bootstrap_pending_nodes, available_alignment_sources)) <= 1 launchpad_acceptance_ready_consistent = len(distinct_ints(launchpad_acceptance_ready_nodes, available_alignment_sources)) <= 1 launchpad_alignment_consistent = all([ launchpad_target_consistent, launchpad_recovery_label_consistent, launchpad_recovery_summary_consistent, launchpad_bootstrap_pending_consistent, launchpad_acceptance_ready_consistent, ]) node_agent_env_missing = any( str(item).strip() == "missing_env:/etc/default/domaincheck-node-agent" for item in env_audit_missing_items ) if critical_failures or env_audit_blocking: status = "blocked" if critical_failures and env_audit_blocking: headline = "关键报告存在缺失,且环境审计已给出阻断结论,当前不适合上线。" elif critical_failures: headline = "上线证据包仍缺关键报告,暂不建议直接宣称收口完成。" else: headline = "环境审计已给出阻断结论,需先修复现场环境后再进入发布门禁。" elif noncritical_failures or env_audit_attention or not launchpad_alignment_consistent: status = "attention" if not launchpad_alignment_consistent: headline = "核心报告已齐,但 launchpad 摘要在不同 surface 之间存在漂移,建议先复核再发版。" elif noncritical_failures and env_audit_attention: headline = "核心报告已齐,但补充报告和环境审计都提示仍有缺口,建议先复核再发版。" elif noncritical_failures: headline = "核心报告已齐,但仍有补充报告超时或失败,建议先复核再发版。" else: headline = "核心报告已齐,但环境审计仍提示基础缺口,建议补齐后再发版。" else: status = "ready" headline = "核心上线证据已经齐备,可进入最终人工复核或正式发布门禁。" result = { "report_dir": str(payload.get("report_dir") or manifest_path.parent), "status": status, "headline": headline, "artifact_total": int(summary.get("artifact_total", len(artifacts)) or len(artifacts)), "failure_total": int(summary.get("failure_total", len(failures)) or len(failures)), "critical_failures": critical_failures, "noncritical_failures": noncritical_failures, "env_audit": { "ok": bool(env_audit_artifact.get("ok", False)), "status": env_audit_status, "headline": env_audit_headline, "missing_items": env_audit_missing_items, "runtime_may_need_restart": env_audit_runtime_may_need_restart, "recommended_actions": env_audit_recommended_actions, }, "go_live_summary": { "go_live_status": str(go_live_summary_payload.get("go_live_status") or "").strip(), "publish_status": str(go_live_summary_payload.get("publish_status") or "").strip(), "operator_title": str(go_live_summary_payload.get("operator_title") or "").strip(), "next_step_action_code": str(go_live_summary_payload.get("next_step_action_code") or "").strip(), "launchpad_recommended_target_node_code": go_live_launchpad_target_node_code, "launchpad_recommended_recovery_label": go_live_launchpad_recovery_label, "launchpad_recommended_recovery_summary": go_live_launchpad_recovery_summary, "launchpad_onboarding_bootstrap_pending_nodes": go_live_launchpad_bootstrap_pending_nodes, "launchpad_onboarding_acceptance_ready_nodes": go_live_launchpad_acceptance_ready_nodes, }, "stack_diagnosis": { "stack_status": str((stack_diagnosis_payload.get("diagnosis") or {}).get("stack_status") or "").strip(), "operator_title": str(((stack_diagnosis_payload.get("diagnosis") or {}).get("operator_decision") or {}).get("title") or "").strip(), "next_step_action_code": str(((stack_diagnosis_payload.get("diagnosis") or {}).get("next_step") or {}).get("action_code") or "").strip(), "launchpad_recommended_target_node_code": stack_launchpad_target_node_code, "launchpad_recommended_recovery_label": stack_launchpad_recovery_label, "launchpad_recommended_recovery_summary": stack_launchpad_recovery_summary, "launchpad_onboarding_bootstrap_pending_nodes": stack_launchpad_bootstrap_pending_nodes, "launchpad_onboarding_acceptance_ready_nodes": stack_launchpad_acceptance_ready_nodes, }, "driver_feed": { "headline": str(driver_feed_payload.get("headline") or "").strip(), "launch_status": str(((driver_feed_payload.get("automation_coverage") or {}).get("launch_status") or "")).strip(), "launch_ready": bool(((driver_feed_payload.get("automation_coverage") or {}).get("launch_ready", False))), "launchpad_recommended_target_node_code": str( ((driver_feed_payload.get("summary") or {}).get("launchpad_recommended_target_node_code") or "") ).strip(), "launchpad_recommended_recovery_label": str( ((driver_feed_payload.get("summary") or {}).get("launchpad_recommended_recovery_label") or "") ).strip(), }, "codex_brief": { "headline": str(codex_brief_payload.get("headline") or "").strip(), "focus_entry_key": str(((codex_brief_payload.get("focus") or {}).get("entry_key") or "")).strip(), "launch_status": str(((codex_brief_payload.get("automation_coverage") or {}).get("launch_status") or "")).strip(), "launch_ready": bool(((codex_brief_payload.get("automation_coverage") or {}).get("launch_ready", False))), "launchpad_recommended_target_node_code": str( ((codex_brief_payload.get("summary") or {}).get("launchpad_recommended_target_node_code") or "") ).strip(), "launchpad_recommended_recovery_label": str( ((codex_brief_payload.get("summary") or {}).get("launchpad_recommended_recovery_label") or "") ).strip(), }, "launchpad_alignment": { "consistent": launchpad_alignment_consistent, "target_node_code_consistent": launchpad_target_consistent, "recovery_label_consistent": launchpad_recovery_label_consistent, "recovery_summary_consistent": launchpad_recovery_summary_consistent, "bootstrap_pending_consistent": launchpad_bootstrap_pending_consistent, "acceptance_ready_consistent": launchpad_acceptance_ready_consistent, "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, }, "recommended_reading_order": list(summary.get("recommended_reading_order") or []), "recommended_next_steps": [ "先看 00_env_audit.txt,确认当前机器环境是否存在阻断或注意项", "先看 02_go_live_summary.json 和 03_stack_diagnosis.json", "再看 04_driver_feed.json 和 05_codex_brief.json", "若涉及发布门禁,再看 06_release_launchpad.json", "若 manifest 里仍有失败项,再看对应 artifact 原文件", ] + ( [ "当前缺口指向 Node Agent 未接管:先执行 agent-gap-export,拿到首个缺口节点的接管包", "随后执行 node-bootstrap-plan,为缺口节点生成可直接落地的接管脚本", "如果仓库代码已更新但 bootstrap-plan 输出仍偏旧,先重启控制面 API 再重新导出接管计划", ] if node_agent_env_missing else [] ) + ( [ "env-audit 已识别运行中 API 可能仍是旧代码:先执行 drive_ops_center.sh runtime-refresh-recover,按固定链路完成 API 重启与复检", "若仍需人工逐条确认,再执行 recommended_actions 里的 API 重启与复检命令" ] if env_audit_runtime_may_need_restart else [] ) + ( [ f"当前 launchpad 缺口已经收敛到节点 {go_live_launchpad_target_node_code},建议优先执行:{go_live_launchpad_recovery_label or '节点恢复'}", "先对照 02_go_live_summary.json、04_driver_feed.json、05_codex_brief.json 中的 launchpad 字段,确认三处摘要是否一致", (go_live_launchpad_recovery_summary or "如果 recovery_summary 仍为空,再回到 06_release_launchpad.json 查看 gap rows 详情"), ] if go_live_launchpad_target_node_code else [] ) + ( [ "当前 bundle 内的 launchpad 摘要在 go_live_summary / stack_diagnosis / driver_feed / codex_brief 之间存在不一致,先不要直接交付。", "优先复核 02_go_live_summary.json 与 03_stack_diagnosis.json,再检查 04_driver_feed.json 与 05_codex_brief.json 是否使用了同一版控制面数据。", "若只有局部摘要偏旧,先执行 drive_ops_center.sh runtime-refresh-recover,并重新导出 go-live bundle。", ] if not launchpad_alignment_consistent else [] ), } print(json.dumps(result, ensure_ascii=False, indent=2)) PY