#!/usr/bin/env node import { spawnSync } from 'node:child_process'; import path from 'node:path'; const ROOT = path.resolve(new URL('..', import.meta.url).pathname); const REMOTE_ROOT = process.env.STUDIO_REMOTE_ROOT ?? '/Users/john/Project'; const CANDIDATE_BASE = `${REMOTE_ROOT}/Memind-candidates`; const REMOTE_SCRIPT = [ 'set -euo pipefail', 'expected_commit="$1"', 'candidate_base="$2"', 'matched=""', 'latest=""', 'for dir in "${candidate_base}"/*; do', ' [[ -d "${dir}" ]] || continue', ' [[ -f "${dir}/.release-manifest.txt" ]] || continue', ' if [[ -z "${latest}" || "$(basename "${dir}")" > "$(basename "${latest}")" ]]; then', ' latest="${dir}"', ' fi', ' if grep -q "^git_head=${expected_commit}$" "${dir}/.release-manifest.txt"; then', ' matched="${dir}"', ' fi', 'done', 'if [[ -n "${matched}" ]]; then candidate="${matched}"; else candidate="${latest}"; fi', '[[ -n "${candidate}" ]] || { echo "NO_CANDIDATE"; exit 1; }', 'printf \'candidate_dir=%s\\n\' "${candidate}"', 'cat "${candidate}/.release-manifest.txt"', 'printf \'\\n---HEALTH---\\n\'', 'curl -s -D - -o /dev/null http://127.0.0.1:18081/api/status | tr -d \'\\r\'', '', ].join('\n'); function parseArgs(argv) { const options = { host: process.env.STUDIO_HOST ?? '58.38.22.103', commit: null, }; for (let index = 2; index < argv.length; index += 1) { const arg = argv[index]; if (arg === '--host') options.host = argv[++index] ?? ''; else if (arg === '--commit') options.commit = argv[++index] ?? ''; else throw new Error(`Unknown argument: ${arg}`); } return options; } function git(...args) { const result = spawnSync('git', args, { cwd: ROOT, encoding: 'utf8' }); if (result.status !== 0) { throw new Error(`git ${args.join(' ')} failed: ${result.stderr || result.stdout}`); } return result.stdout.trim(); } function ssh(host, args, input = '') { const result = spawnSync('ssh', ['-o', 'BatchMode=yes', '-o', 'ConnectTimeout=15', host, ...args], { encoding: 'utf8', input, }); if (result.status !== 0) { throw new Error(`SSH to ${host} failed:\n${result.stderr || result.stdout}`); } return result.stdout.trim(); } function parseManifest(content) { const fields = {}; for (const line of content.split('\n')) { const separator = line.indexOf('='); if (separator <= 0) continue; fields[line.slice(0, separator)] = line.slice(separator + 1); } return fields; } try { const options = parseArgs(process.argv); const commit = options.commit ?? git('rev-parse', 'HEAD'); const remoteUserHost = options.host.includes('@') ? options.host : `john@${options.host}`; const remoteOutput = ssh( remoteUserHost, ['/bin/bash', '-s', '--', commit, CANDIDATE_BASE], REMOTE_SCRIPT, ); const healthMarker = '\n---HEALTH---\n'; const markerIndex = remoteOutput.indexOf('---HEALTH---'); if (markerIndex < 0) { throw new Error('Canary promotion evidence response missing health probe'); } const manifestText = remoteOutput.slice(0, markerIndex); const healthText = remoteOutput.slice(markerIndex + healthMarker.trim().length); const candidateDir = manifestText .split('\n') .find((line) => line.startsWith('candidate_dir=')) ?.slice('candidate_dir='.length); const manifest = parseManifest( manifestText .split('\n') .filter((line) => !line.startsWith('candidate_dir=')) .join('\n'), ); if (!candidateDir) { throw new Error('103 has no canary candidate directory'); } if (manifest.git_head !== commit) { throw new Error( `Canary candidate commit mismatch: candidate=${manifest.git_head ?? 'missing'} expected=${commit}`, ); } if (!/^HTTP\/1\.1 200/m.test(healthText)) { throw new Error('Canary candidate /api/status is not healthy on 18081'); } if (!/^x-memind-runtime-role: candidate/im.test(healthText)) { throw new Error('Canary candidate health probe missing X-Memind-Runtime-Role: candidate'); } console.log('Canary promotion evidence verified'); console.log(`candidate_dir=${candidateDir}`); console.log(`release_id=${manifest.release_id ?? 'unknown'}`); console.log(`git_head=${manifest.git_head}`); } catch (error) { console.error(error.message); process.exit(1); }