feat: add ops center and node onboarding flow

This commit is contained in:
Your Name
2026-04-18 23:52:51 +08:00
parent 246838ae4c
commit b9c29481b5
142 changed files with 89727 additions and 186 deletions

View File

@@ -0,0 +1,317 @@
#!/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 <manifest.json|report_dir>" >&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