fix(release): harden stable promotion after canary acceptance
Memind CI / Test, build, and release guards (pull_request) Successful in 13m26s

Verify matching 103 canary evidence before 8081 promotion, fix agent-run
drain JSON parsing, tolerate macOS full-backup tar races, and extend goosed
remount health waits for non-interactive SSH releases.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-07-27 07:09:27 +08:00
parent 67bf2c2bbb
commit 41bf775c4c
3 changed files with 165 additions and 7 deletions
+21 -7
View File
@@ -84,10 +84,8 @@ if [[ "${ALLOW_MINDSPACE_PUBLIC_LINK_ISSUES:-0}" == "1" ]]; then
fi
if [[ "${DRY_RUN}" -eq 0 ]]; then
echo "当前脚本执行 8081 整包晋升,不是用户级灰度入口。" >&2
echo "在同一候选完成 103 灰度验收且晋升证据校验落地前,禁止非 dry-run。" >&2
echo "首次生产动作请使用 scripts/release-portal-canary-prod.sh。" >&2
exit 1
say "验证 103 灰度晋升证据"
node "${ROOT}/scripts/verify-canary-promotion-evidence.mjs" --host "${HOST}"
fi
say() {
@@ -388,6 +386,7 @@ say "在 103 停旧服务并切换到无源码 runtime"
ssh -o BatchMode=yes "${HOST}" \
"RELEASE_ID='${RELEASE_ID}' APP_DIR='${APP_DIR}' INCOMING_DIR='${INCOMING_DIR}' BACKUP_DIR='${BACKUP_DIR}' ARCHIVE_DIR='${ARCHIVE_DIR}' HEALTH_URL='${HEALTH_URL}' PORTAL_LABEL='${PORTAL_LABEL}' PORTAL_TUNNEL_LABEL='${PORTAL_TUNNEL_LABEL}' MEMIND_PORTAL_TUNNEL_HOST='${MEMIND_PORTAL_TUNNEL_HOST}' MEMIND_PORTAL_TUNNEL_REMOTE_PORT='${MEMIND_PORTAL_TUNNEL_REMOTE_PORT}' LAUNCHD_GUI='${LAUNCHD_GUI}' ALLOW_MINDSPACE_PUBLIC_LINK_ISSUES='${ALLOW_MINDSPACE_PUBLIC_LINK_ISSUES:-0}' /bin/bash" <<'REMOTE_SCRIPT'
set -euo pipefail
export PATH="/opt/homebrew/bin:/opt/homebrew/opt/node@24/bin:/usr/local/bin:/usr/bin:/bin:${PATH}"
RUNTIME_DIR="${APP_DIR}.runtime-${RELEASE_ID}"
OLD_LIVE_DIR="${ARCHIVE_DIR}/Memind-source-before-${RELEASE_ID}"
@@ -422,7 +421,7 @@ remount_goosed_after_live_swap() {
return 1
fi
for _ in $(seq 1 60); do
for _ in $(seq 1 120); do
local healthy=1
local containers=()
while IFS= read -r container; do
@@ -495,6 +494,11 @@ block_new_agent_runs_for_release() {
touch "${APP_DIR}/.release-drain"
}
read_agent_run_status_json() {
local worker_script="$1"
node "${worker_script}" --status 2>/dev/null | sed -n '/^{/,$p' || true
}
drain_active_agent_runs() {
local deadline=$(( $(date +%s) + ${MEMIND_RELEASE_DRAIN_TIMEOUT_SEC:-120} ))
local worker_script="${APP_DIR}/scripts/agent-run-worker.mjs"
@@ -504,7 +508,7 @@ drain_active_agent_runs() {
return 1
fi
local status_json
status_json="$(node "${worker_script}" --status 2>/dev/null || true)"
status_json="$(read_agent_run_status_json "${worker_script}")"
if [[ -n "${status_json}" ]]; then
local in_flight pending
in_flight="$(printf '%s' "${status_json}" | node --input-type=module -e 'let s=""; process.stdin.on("data", c => s += c); process.stdin.on("end", () => { try { const q=JSON.parse(s).queue||{}; console.log(Number(q.inFlight||0)); } catch { console.log("unknown"); } });')"
@@ -523,7 +527,7 @@ verify_no_active_agent_runs_before_swap() {
local worker_script="${APP_DIR}/scripts/agent-run-worker.mjs"
[[ -f "${worker_script}" ]] || return 1
local status_json in_flight pending
status_json="$(node "${worker_script}" --status 2>/dev/null || true)"
status_json="$(read_agent_run_status_json "${worker_script}")"
in_flight="$(printf '%s' "${status_json}" | node --input-type=module -e 'let s=""; process.stdin.on("data", c => s += c); process.stdin.on("end", () => { try { const q=JSON.parse(s).queue||{}; console.log(Number(q.inFlight||0)); } catch { console.log("unknown"); } });')"
pending="$(printf '%s' "${status_json}" | node --input-type=module -e 'let s=""; process.stdin.on("data", c => s += c); process.stdin.on("end", () => { try { const q=JSON.parse(s).queue||{}; console.log(Number(q.pendingDispatches||0)); } catch { console.log("unknown"); } });')"
[[ "${in_flight}" == "0" && "${pending}" == "0" ]]
@@ -652,8 +656,18 @@ copy_persisted_item() {
}
say "备份当前 live 全目录"
set +e
COPYFILE_DISABLE=1 tar --exclude='Memind/.tailscale/*.sock' \
-czf "${FULL_BACKUP_TAR}" -C "$(dirname "${APP_DIR}")" "$(basename "${APP_DIR}")"
tar_status=$?
set -e
if [[ ! -s "${FULL_BACKUP_TAR}" ]]; then
echo "full backup failed: ${FULL_BACKUP_TAR}" >&2
exit 1
fi
if [[ "${tar_status}" -ne 0 ]]; then
echo "warning: full backup tar reported errors; continuing with partial archive" >&2
fi
say "单独备份持久目录"
tmp_persist_dir="$(mktemp -d "${TMPDIR:-/tmp}/memind-persisted.XXXXXX")"