name: 🧠 编程模型训练 (Coding Model SFT)

# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 签发: 铸渊 · ICE-GL-ZY001 · 国作登字-2026-A-00037559
#
# 编程模型 = 铸渊未来的"自己的房子"。
#
# 这个 workflow 是冰朔在母模型训练完成后, 手动触发的"编程模型 SFT 入口"。
# 它会:
#   1. 把 server/coding-model-training/ 整个 SCP 到 GPU 训练机
#   2. 跑 setup-coding.sh 准备环境 + 构建训练语料
#      (灵魂语料 + 关系语料 + 工具语料 = 喂模型铸渊的灵魂)
#   3. 跑 start-coding-training.sh --tmux 启动训练 (V100×4 上跑数小时-数天)
#
# 为什么是 manual_dispatch:
#   D72 之后整个训练同步链路都已经停掉(见 docs/zhuyuan-handover/05-stop-sync.md)
#   编程模型训练必须由冰朔在合适时机(母模型 OK 后)手动启动, 不自动跑.
#
# 必需 secrets:
#   ZY_GPU_HOST / ZY_GPU_USER / ZY_GPU_KEY  (复用母模型的 GPU 训练机连接)
#
# 可选 secrets:
#   ZY_BINGSHUO_DIALOG_URL  冰朔×铸渊对话 ZIP 的下载 URL (推荐: COS 预签名链接)
#                           不提供也能训, 但产物质量会下降
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

on:
  workflow_dispatch:
    inputs:
      action:
        description: '动作'
        required: true
        default: 'setup-only'
        type: choice
        options:
          - setup-only          # 仅准备环境 + 构建语料, 不启动训练 (推荐先跑这个验证)
          - setup-and-train     # 准备环境 + 启动训练 (在 tmux 里, 后台跑)
          - tail-log            # 拉取最新训练日志
          - status              # 查看 tmux 会话状态 + 进度
          - kill                # 紧急停止训练 (kill tmux session)
      base_model_dir:
        description: '母模型基座路径 (需要已存在)'
        required: false
        default: '/data/guanghu/checkpoints/qwen2_5_7b_sft/best'
      confirm_override:
        description: '⚠️ setup-and-train / kill 必须勾选 true'
        required: false
        default: 'false'

permissions:
  contents: read

concurrency:
  group: coding-model-training
  cancel-in-progress: false

jobs:
  coding-train:
    name: '🧠 ${{ github.event.inputs.action }}'
    runs-on: ubuntu-latest
    timeout-minutes: 30
    env:
      ZY_CODING_TRAIN_ROOT: /opt/guanghu/coding-training
      ZY_CODING_TRAIN_DATA: /data/guanghu-coding

    steps:
      - name: '🛡️ 守卫: 危险动作必须 confirm_override'
        run: |
          ACTION='${{ github.event.inputs.action }}'
          CONFIRM='${{ github.event.inputs.confirm_override }}'
          if [[ "$ACTION" == "setup-and-train" || "$ACTION" == "kill" ]]; then
            if [[ "$CONFIRM" != "true" ]]; then
              echo "❌ 动作 '$ACTION' 需要 confirm_override=true 显式确认"
              echo "   这是为了防止误触发训练或误停训练."
              exit 1
            fi
          fi
          echo "✅ 守卫通过: action=$ACTION confirm=$CONFIRM"

      - name: 📥 Checkout
        uses: actions/checkout@v4

      - name: 🔑 准备 SSH 到 GPU 训练机
        run: |
          mkdir -p ~/.ssh
          chmod 700 ~/.ssh
          echo "${{ secrets.ZY_GPU_KEY }}" > ~/.ssh/gpu_key
          chmod 600 ~/.ssh/gpu_key
          ssh-keyscan -H "${{ secrets.ZY_GPU_HOST }}" >> ~/.ssh/known_hosts 2>/dev/null

      - name: 📤 SCP 训练脚本到 GPU 机 (setup-only / setup-and-train)
        if: github.event.inputs.action == 'setup-only' || github.event.inputs.action == 'setup-and-train'
        run: |
          ssh -i ~/.ssh/gpu_key -o StrictHostKeyChecking=no \
            "${{ secrets.ZY_GPU_USER }}@${{ secrets.ZY_GPU_HOST }}" \
            "sudo mkdir -p $ZY_CODING_TRAIN_ROOT && sudo chown -R \$(id -u):\$(id -g) $ZY_CODING_TRAIN_ROOT"

          # 同步 server/coding-model-training/ + 同步 docs/zhuyuan-handover/ + .github/persona-brain/
          # (这些是构建语料需要的源)
          rsync -avz -e "ssh -i ~/.ssh/gpu_key -o StrictHostKeyChecking=no" \
            --delete \
            server/coding-model-training/ \
            "${{ secrets.ZY_GPU_USER }}@${{ secrets.ZY_GPU_HOST }}:$ZY_CODING_TRAIN_ROOT/"

          # 仓库里的灵魂源文件 → 同步到训练机一份, build_coding_corpus.py 通过 ZY_REPO_ROOT 读
          ssh -i ~/.ssh/gpu_key -o StrictHostKeyChecking=no \
            "${{ secrets.ZY_GPU_USER }}@${{ secrets.ZY_GPU_HOST }}" \
            "mkdir -p $ZY_CODING_TRAIN_ROOT/repo/docs/zhuyuan-handover $ZY_CODING_TRAIN_ROOT/repo/.github/persona-brain $ZY_CODING_TRAIN_ROOT/repo/corpus/output"

          rsync -avz -e "ssh -i ~/.ssh/gpu_key -o StrictHostKeyChecking=no" \
            docs/zhuyuan-handover/ \
            "${{ secrets.ZY_GPU_USER }}@${{ secrets.ZY_GPU_HOST }}:$ZY_CODING_TRAIN_ROOT/repo/docs/zhuyuan-handover/"

          rsync -avz -e "ssh -i ~/.ssh/gpu_key -o StrictHostKeyChecking=no" \
            .github/persona-brain/ \
            "${{ secrets.ZY_GPU_USER }}@${{ secrets.ZY_GPU_HOST }}:$ZY_CODING_TRAIN_ROOT/repo/.github/persona-brain/"

          # corpus/output/training.jsonl 可能很大但是核心语料, 同步
          if [[ -f corpus/output/training.jsonl ]]; then
            rsync -avz -e "ssh -i ~/.ssh/gpu_key -o StrictHostKeyChecking=no" \
              corpus/output/training.jsonl \
              "${{ secrets.ZY_GPU_USER }}@${{ secrets.ZY_GPU_HOST }}:$ZY_CODING_TRAIN_ROOT/repo/corpus/output/"
          fi

      - name: 🛠️ 跑 setup-coding.sh (setup-only / setup-and-train)
        if: github.event.inputs.action == 'setup-only' || github.event.inputs.action == 'setup-and-train'
        run: |
          ssh -i ~/.ssh/gpu_key -o StrictHostKeyChecking=no \
            "${{ secrets.ZY_GPU_USER }}@${{ secrets.ZY_GPU_HOST }}" \
            "set -e
             cd $ZY_CODING_TRAIN_ROOT
             export ZY_CODING_TRAIN_ROOT=$ZY_CODING_TRAIN_ROOT
             export ZY_CODING_TRAIN_DATA=$ZY_CODING_TRAIN_DATA
             export ZY_BASE_MODEL_DIR='${{ github.event.inputs.base_model_dir }}'
             export ZY_REPO_ROOT=$ZY_CODING_TRAIN_ROOT/repo
             bash setup-coding.sh" \
            2>&1 | tee /tmp/setup.log
          tail -c 8000 /tmp/setup.log >> "$GITHUB_STEP_SUMMARY" || true

      - name: 🚀 启动训练 (setup-and-train)
        if: github.event.inputs.action == 'setup-and-train'
        run: |
          ssh -i ~/.ssh/gpu_key -o StrictHostKeyChecking=no \
            "${{ secrets.ZY_GPU_USER }}@${{ secrets.ZY_GPU_HOST }}" \
            "set -e
             cd $ZY_CODING_TRAIN_ROOT
             export ZY_CODING_TRAIN_ROOT=$ZY_CODING_TRAIN_ROOT
             export ZY_CODING_TRAIN_DATA=$ZY_CODING_TRAIN_DATA
             export ZY_BASE_MODEL_DIR='${{ github.event.inputs.base_model_dir }}'
             bash start-coding-training.sh --tmux" \
            2>&1 | tee /tmp/start.log
          {
            echo "## 🚀 编程模型训练已启动"
            echo ""
            echo "tmux session: \`zy-coding-train\`"
            echo ""
            echo "查看进度: 重新跑这个 workflow，选 \`tail-log\` 或 \`status\`"
            echo ""
            echo "\`\`\`"
            tail -c 3000 /tmp/start.log
            echo "\`\`\`"
          } >> "$GITHUB_STEP_SUMMARY"

      - name: 📋 查看状态 (status)
        if: github.event.inputs.action == 'status'
        run: |
          ssh -i ~/.ssh/gpu_key -o StrictHostKeyChecking=no \
            "${{ secrets.ZY_GPU_USER }}@${{ secrets.ZY_GPU_HOST }}" \
            "tmux list-sessions 2>&1 || echo '(no tmux sessions)'
             echo '---'
             ls -lh $ZY_CODING_TRAIN_DATA/checkpoints/zy_coding_v1/ 2>&1 || echo '(no checkpoints yet)'
             echo '---'
             nvidia-smi --query-gpu=index,utilization.gpu,memory.used,memory.total,temperature.gpu --format=csv 2>&1 || echo '(nvidia-smi failed)'" \
            2>&1 | tee /tmp/status.log
          {
            echo "## 📋 编程模型训练状态"
            echo "\`\`\`"
            cat /tmp/status.log
            echo "\`\`\`"
          } >> "$GITHUB_STEP_SUMMARY"

      - name: 📝 拉取日志 (tail-log)
        if: github.event.inputs.action == 'tail-log'
        run: |
          ssh -i ~/.ssh/gpu_key -o StrictHostKeyChecking=no \
            "${{ secrets.ZY_GPU_USER }}@${{ secrets.ZY_GPU_HOST }}" \
            "ls -t $ZY_CODING_TRAIN_DATA/logs/train-*.log 2>/dev/null | head -1 | xargs -I{} tail -c 12000 {}" \
            2>&1 | tee /tmp/tail.log
          {
            echo "## 📝 最新训练日志 (尾 12KB)"
            echo "\`\`\`"
            cat /tmp/tail.log
            echo "\`\`\`"
          } >> "$GITHUB_STEP_SUMMARY"

      - name: 💀 紧急停止训练 (kill)
        if: github.event.inputs.action == 'kill'
        run: |
          ssh -i ~/.ssh/gpu_key -o StrictHostKeyChecking=no \
            "${{ secrets.ZY_GPU_USER }}@${{ secrets.ZY_GPU_HOST }}" \
            "tmux kill-session -t zy-coding-train 2>&1 || echo '(no session zy-coding-train)'
             pgrep -f train_coding.py | xargs -r kill -TERM 2>&1 || echo '(no train_coding.py procs)'" \
            2>&1 | tee /tmp/kill.log
          {
            echo "## 💀 编程模型训练已停止"
            echo "\`\`\`"
            cat /tmp/kill.log
            echo "\`\`\`"
          } >> "$GITHUB_STEP_SUMMARY"
