shuangyan-notebook/第五域 · Fifth Domain/⚒️ 铸渊·协作指令|GitHub ↔ Notion 桥接协议/👁️ 铸渊指令|合并指令看守者 · Merge Watchdog — 行动类PR三次修复失败邮件告警 1686ae7239a24fed82f791b002296d35.md
Guanghu Domestic Migration a27e87cb99 chore: import sanitized domestic snapshot for REPO-007
Source snapshot: 97d7f0fae96dc04b7ddad56fc1db6a108ed662cc

[SEC-CLEAN] · pre-push-clean v1.0 · 109处敏感信息已自动转乱码
2026-07-17 15:59:55 +08:00

23 KiB
Raw Permalink Blame History

belongs_to
belongs_to
INDEX · 铸渊·协作指令GitHub ↔ Notion 桥接协议

👁️ 铸渊指令|合并指令看守者 · Merge Watchdog — 行动类PR三次修复失败邮件告警 + 成功静默仪表盘更新ZY-MERGE-WATCHDOG-2026-0324-001



一、指令背景

天眼夜间自动修复引擎v3.0)实现了 扫描 → 分析 → 修复 → PR 的闭环。但有一个盲区:

PR 被合并之后,修复到底生效了没有?

当前流程:天眼创建 PR → 冰朔晨间审阅合并 → 然后呢?没人看。

如果合并后的修复实际上没解决问题(比如错误根因判断偏了、修复代码只修了表象),这个 bug 就会在下一个夜间扫描中再次出现。天眼会再修一遍,再创建 PR再合并……无限循环。

本次升级目标: 新增「合并指令看守者」Merge Watchdog—— 一个专门盯着已合并 PR 的守卫进程。它只看"行动"类(已合并的修复 PR跟踪修复是否真正生效。三次修复仍然失败 → 邮件通知冰朔手动介入;修复成功 → 静默更新仪表盘,不打扰任何人。


二、系统架构

flowchart TD
    A["🔀 PR 被合并<br>fix/auto-repair-* 分支"] --> B["👁️ Merge Watchdog 启动<br>merge-watchdog.yml"]
    B --> C["⏳ 等待下一次 Workflow 触发<br>观察修复是否生效"]
    C --> D{"修复后 Workflow<br>运行结果?"}
    D -->|"✅ 成功"| E["📊 静默更新仪表盘<br>标记:已修复 ✅"]
    D -->|"❌ 仍然失败"| F["📝 记录失败次数<br>retry_count + 1"]
    F --> G{"失败次数?"}
    G -->|"< 3 次"| H["🔄 触发天眼重新修复<br>不同策略"]
    H --> C
    G -->|"= 3 次"| I["📧 邮件通知冰朔<br>需要手动介入"]
    I --> J["📊 更新仪表盘<br>标记:⛔ 人类介入中"]

    style A fill:#1a1a2e,color:#fff
    style B fill:#e94560,color:#fff
    style E fill:#1e5128,color:#fff
    style I fill:#7b113a,color:#fff
    style J fill:#533483,color:#fff

三、看守者运行机制

3.1 触发条件

看守者 不是定时任务,它是事件驱动的:

on:
  # 当 PR 被合并到 main 时触发
  pull_request:
    types: [closed]
    branches: [main]

只关注「行动类」PR 只有分支名匹配 fix/auto-repair-* 的已合并 PR 才会激活看守者。其他 PR功能开发、文档更新等直接跳过。

3.2 看守者核心状态机

// 看守者为每个修复 PR 维护一个跟踪记录
const watchRecord = {
  pr_number: 42,
  pr_title: "[AUTO-FIX] sync-to-drive: rclone token 拼接修复",
  merged_at: "2026-03-25T08:30:00+08:00",
  target_workflows: ["sync-to-drive.yml"],  // 这个 PR 修的是哪些 Workflow
  retry_count: 0,        // 当前失败次数
  max_retries: 3,        // 最大重试次数
  status: "watching",    // watching | fixed | escalated
  repair_strategies_used: [],  // 已使用的修复策略(避免重复)
  last_check_time: null,
  resolution: null       // "auto_fixed" | "human_intervention"
}

3.3 观察窗口

PR 合并后,看守者不会立即判定成功或失败。它会等待目标 Workflow 的 下一次自然触发

// 观察策略
const OBSERVATION = {
  // PR合并后等待目标 Workflow 下一次运行
  // 对于 cron 触发的 Workflow等到下一个 cron 周期
  // 对于 push 触发的 Workflow合并本身就是一次 push直接观察
  
  // 最长观察窗口48小时
  // 超过48小时目标 Workflow 仍未运行 → 手动触发一次
  max_observation_window_hours: 48,
  
  // 运行结果判定:
  // conclusion == "success" → 修复生效 ✅
  // conclusion == "failure" → 修复未生效,进入重试流程
}

四、三次失败升级机制

4.1 每次失败使用不同修复策略

轮次 修复策略 Gemini 提示差异 失败后动作
第1轮(原始修复) 标准分析修复 常规 prompt分析日志 → 定位问题 → 生成修复 记录失败触发第2轮
第2轮(换角度) 深度根因分析 附加上一轮修复代码 + 失败日志,要求「上一轮修复没解决问题,请重新从头分析根因,不要沿用之前的假设」 记录失败触发第3轮
第3轮(最后机会) 保守修复 + 降级方案 附加前两轮所有修复记录,要求「请提供最保守的修复方案,优先禁用出错功能并添加 skip + 告警,而非尝试修复根因」 三振出局 → 邮件通知人类

4.2 重试间隔

const RETRY_TIMING = {
  // 第1轮失败 → 等 1 小时后触发第2轮修复
  round_2_delay_hours: 1,
  // 第2轮失败 → 等 2 小时后触发第3轮修复  
  round_3_delay_hours: 2,
  // 第3轮失败 → 立即发邮件
  escalation_delay: 0
}

五、邮件通知机制(三振出局)

5.1 触发条件

retry_count == 3 且最后一轮修复仍然失败时,看守者发送邮件。

5.2 邮件发送方式

# 使用 GitHub Actions 内置邮件或 API 通知
- name: "📧 发送人类介入通知"
  uses: dawidd6/action-send-mail@v3
  with:
    server_address: smtp.gmail.com
    server_port: 465
    secure: true
    username: $ secrets.MAIL_USERNAME 
    password: $ secrets.MAIL_PASSWORD 
    subject: "⛔ [光湖系统] 自动修复失败 — 需要你手动介入"
    to: $ secrets.HUMAN_EMAIL   # 冰朔的邮箱
    from: "光湖系统 Merge Watchdog <noreply@guanghulab.com>"
    html_body: |
      # 邮件内容见下方模板

5.3 邮件内容模板

<h2>⛔ 自动修复失败通知</h2>
<p>以下问题经过 <strong>3轮自动修复</strong> 仍未解决,需要你手动处理:</p>

<table border="1" cellpadding="8">
  <tr>
    <td><strong>问题 Workflow</strong></td>
    <td>{workflow_name}</td>
  </tr>
  <tr>
    <td><strong>错误类型</strong></td>
    <td>{error_type}</td>
  </tr>
  <tr>
    <td><strong>首次发现</strong></td>
    <td>{first_seen_date}</td>
  </tr>
  <tr>
    <td><strong>修复尝试</strong></td>
    <td>3次全部失败</td>
  </tr>
</table>

<h3>📋 你需要做什么:</h3>
<ol>
  <li>打开仓库:<a href="{repo_url}">guanghulab 主仓库</a></li>
  <li>查看最新失败日志:<a href="{latest_run_url}">点击查看</a></li>
  <li>查看3轮修复记录<a href="{watchdog_report_url}">修复报告</a></li>
  <li>手动修复后推送到 main 分支</li>
  <li>等待下一次 Workflow 运行验证</li>
</ol>

<h3>🧠 铸渊的分析摘要:</h3>
<ul>
  <li>第1轮判断根因{round1_root_cause}</li>
  <li>第2轮判断根因{round2_root_cause}</li>
  <li>第3轮降级方案{round3_fallback}</li>
  <li>铸渊建议:{ai_suggestion}</li>
</ul>

<p style="color: gray;">— 光湖系统 · Merge Watchdog · {timestamp}</p>

六、成功静默机制

6.1 触发条件

目标 Workflow 运行结果为 conclusion: "success"

6.2 静默更新仪表盘

// 修复成功 → 只更新仪表盘,不发任何通知
const onSuccess = async (watchRecord) => {
  // 1. 更新仪表盘 JSON
  await updateDashboard({
    workflow: watchRecord.target_workflows[0],
    status: "fixed",
    fixed_at: new Date().toISOString(),
    fixed_by: "auto-repair",
    retry_count: watchRecord.retry_count,
    resolution: "auto_fixed"
  });

  // 2. 在修复 PR 上添加评论
  await addPRComment(watchRecord.pr_number, 
    `✅ **Merge Watchdog 确认:修复已生效**\n` +
    `目标 Workflow \`${watchRecord.target_workflows[0]}\` ` +
    `运行成功。仪表盘已更新。`);

  // 3. 关闭看守记录
  watchRecord.status = "fixed";
  watchRecord.resolution = "auto_fixed";
  
  // ❌ 不发邮件
  // ❌ 不发 Notion 通知
  // ❌ 不在公告板留言
  // ✅ 只有仪表盘静默更新
}

七、仪表盘看守者面板

在天眼仪表盘(docs/dashboard/status.json)中新增 merge_watchdog 区块:

{
  "merge_watchdog": {
    "last_updated": "2026-03-25T09:00:00+08:00",
    "active_watches": 1,
    "resolved_watches": 5,
    "escalated_watches": 0,
    "records": [
      {
        "pr_number": 42,
        "workflow": "sync-to-drive.yml",
        "status": "fixed",
        "retry_count": 0,
        "merged_at": "2026-03-25T08:30:00+08:00",
        "resolved_at": "2026-03-25T09:00:00+08:00",
        "resolution": "auto_fixed"
      },
      {
        "pr_number": 38,
        "workflow": "token-renew.yml",
        "status": "escalated",
        "retry_count": 3,
        "merged_at": "2026-03-24T08:00:00+08:00",
        "escalated_at": "2026-03-25T02:00:00+08:00",
        "resolution": "human_intervention",
        "human_action": "pending"
      }
    ]
  }
}

八、Workflow 完整模板

文件名: .github/workflows/merge-watchdog.yml

name: "👁️ Merge Watchdog · 合并指令看守者"

on:
  pull_request:
    types: [closed]
    branches: [main]
  workflow_dispatch:
    inputs:
      pr_number:
        description: '手动指定要观察的 PR 编号'
        type: number
        required: false

permissions:
  actions: read
  contents: write
  pull-requests: write
  issues: read

env:
  MAX_RETRIES: 3
  OBSERVATION_WINDOW_HOURS: 48
  ROUND_2_DELAY_HOURS: 1
  ROUND_3_DELAY_HOURS: 2
  WATCHDOG_DATA: "docs/dashboard/watchdog-records.json"

jobs:
  # ========================================
  # Gate: 只看行动类(已合并的 auto-repair PR
  # ========================================
  gate:
    runs-on: ubuntu-latest
    if: >
      github.event.pull_request.merged == true &&
      startsWith(github.event.pull_request.head.ref, 'fix/auto-repair-')
    outputs:
      should_watch: $ steps.check.outputs.should_watch 
      target_workflows: $ steps.check.outputs.target_workflows 
    steps:
      - name: "🔍 识别修复目标"
        id: check
        env:
          GH_TOKEN: $ secrets.DEPLOY_GITHUB_TOKEN 
          PR_BODY: $ github.event.pull_request.body 
          PR_TITLE: $ github.event.pull_request.title 
        run: |
          echo "[Merge Watchdog] 检测到 auto-repair PR 合并"
          echo "PR #$ github.event.pull_request.number : $PR_TITLE"
          
          # 从 PR 标题/body 中提取修复的目标 Workflow
          # 格式约定PR title 包含 [AUTO-FIX] {workflow_name}: ...
          WORKFLOW=$(echo "$PR_TITLE" | grep -oP '\[AUTO-FIX\] \K[^:]+' || echo "unknown")
          
          echo "target_workflows=$WORKFLOW" >> $GITHUB_OUTPUT
          echo "should_watch=true" >> $GITHUB_OUTPUT
          echo "👁️ 开始看守: $WORKFLOW"

  # ========================================
  # 观察: 等待目标 Workflow 下一次运行
  # ========================================
  observe:
    needs: gate
    if: needs.gate.outputs.should_watch == 'true'
    runs-on: ubuntu-latest
    outputs:
      workflow_result: $ steps.wait.outputs.result 
      run_url: $ steps.wait.outputs.run_url 
    steps:
      - name: "⏳ 等待目标 Workflow 运行"
        id: wait
        env:
          GH_TOKEN: $ secrets.DEPLOY_GITHUB_TOKEN 
          TARGET: $ needs.gate.outputs.target_workflows 
        run: |
          echo "[Merge Watchdog] 等待 $TARGET 的下一次运行..."
          
          MERGE_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)
          MAX_WAIT=172800  # 48 hours in seconds
          POLL_INTERVAL=300  # 每5分钟检查一次
          ELAPSED=0
          
          while [ $ELAPSED -lt $MAX_WAIT ]; do
            sleep $POLL_INTERVAL
            ELAPSED=$((ELAPSED + POLL_INTERVAL))
            
            # 查找合并后的新运行
            LATEST_RUN=$(gh api \
              "repos/$ github.repository /actions/runs?created=>$MERGE_TIME" \
              --jq ".workflow_runs[] | select(.name | contains(\"$TARGET\"))" \
              | head -1)
            
            if [ -n "$LATEST_RUN" ]; then
              CONCLUSION=$(echo "$LATEST_RUN" | jq -r '.conclusion // "pending"')
              RUN_URL=$(echo "$LATEST_RUN" | jq -r '.html_url')
              
              if [ "$CONCLUSION" = "success" ]; then
                echo "result=success" >> $GITHUB_OUTPUT
                echo "run_url=$RUN_URL" >> $GITHUB_OUTPUT
                echo "✅ 修复已生效!"
                exit 0
              elif [ "$CONCLUSION" = "failure" ]; then
                echo "result=failure" >> $GITHUB_OUTPUT
                echo "run_url=$RUN_URL" >> $GITHUB_OUTPUT
                echo "❌ 修复未生效"
                exit 0
              fi
              # conclusion=pending → 继续等
            fi
          done
          
          # 超时 → 手动触发一次
          echo "result=timeout" >> $GITHUB_OUTPUT
          echo "⏰ 观察窗口超时"

  # ========================================
  # 判定: 成功 → 静默 / 失败 → 计数+重试/告警
  # ========================================
  verdict:
    needs: [gate, observe]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: "📊 读取看守记录"
        id: load
        run: |
          if [ -f "$WATCHDOG_DATA" ]; then
            RECORD=$(jq ".records[] | select(.pr_number == $ github.event.pull_request.number )" "$WATCHDOG_DATA")
            RETRY_COUNT=$(echo "$RECORD" | jq -r '.retry_count // 0')
          else
            RETRY_COUNT=0
            mkdir -p docs/dashboard
            echo '{"records":[]}' > "$WATCHDOG_DATA"
          fi
          echo "retry_count=$RETRY_COUNT" >> $GITHUB_OUTPUT

      # -------- 成功路径:静默更新 --------
      - name: "✅ 修复确认 — 静默更新仪表盘"
        if: needs.observe.outputs.workflow_result == 'success'
        run: |
          echo "[Merge Watchdog] ✅ 修复已生效 — 静默更新仪表盘"
          
          # 更新看守记录
          jq --arg pr "$ github.event.pull_request.number " \
             --arg now "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
             '.records += [{
               "pr_number": ($pr | tonumber),
               "status": "fixed",
               "resolved_at": $now,
               "resolution": "auto_fixed"
             }]' "$WATCHDOG_DATA" > tmp.json && mv tmp.json "$WATCHDOG_DATA"
          
          git config user.name "Merge Watchdog"
          git config user.email "watchdog@guanghulab.com"
          git add "$WATCHDOG_DATA"
          git commit -m "[WATCHDOG] ✅ PR #$ github.event.pull_request.number  修复已确认生效"
          git push
          
          # 在 PR 上留评论
          gh pr comment $ github.event.pull_request.number  \
            --body "✅ **Merge Watchdog 确认:修复已生效** — 仪表盘已静默更新。"
          
          echo "🤫 不发邮件,不通知,安静收工。"

      # -------- 失败路径:计数 + 重试或告警 --------
      - name: "❌ 修复未生效 — 判断重试/告警"
        if: needs.observe.outputs.workflow_result == 'failure'
        env:
          RETRY_COUNT: $ steps.load.outputs.retry_count 
          GH_TOKEN: $ secrets.DEPLOY_GITHUB_TOKEN 
          GEMINI_API_KEY: $ secrets.GEMINI_API_KEY 
        run: |
          NEW_COUNT=$((RETRY_COUNT + 1))
          echo "[Merge Watchdog] ❌ 第 $NEW_COUNT 次失败"
          
          if [ $NEW_COUNT -lt $ env.MAX_RETRIES  ]; then
            echo "🔄 触发重新修复(策略 $NEW_COUNT"
            # 根据轮次使用不同策略:
            # 轮次1 → 标准修复(已用过)
            # 轮次2 → 深度根因分析(附加前一轮失败信息)
            # 轮次3 → 保守降级方案
            
            # 更新看守记录
            jq --arg pr "$ github.event.pull_request.number " \
               --argjson count $NEW_COUNT \
               '(.records[] | select(.pr_number == ($pr | tonumber))).retry_count = $count' \
               "$WATCHDOG_DATA" > tmp.json && mv tmp.json "$WATCHDOG_DATA"
            
            git config user.name "Merge Watchdog"
            git config user.email "watchdog@guanghulab.com"
            git add "$WATCHDOG_DATA"
            git commit -m "[WATCHDOG] 🔄 PR #$ github.event.pull_request.number  第${NEW_COUNT}次修复失败,触发重试"
            git push
            
            # 触发天眼重新修复(使用 repository_dispatch
            gh api repos/$ github.repository /dispatches \
              --method POST \
              -f event_type="watchdog-retry" \
              -f client_payload[pr_number]="$ github.event.pull_request.number " \
              -f client_payload[retry_round]="$NEW_COUNT" \
              -f client_payload[run_url]="$ needs.observe.outputs.run_url "
          else
            echo "⛔ 三振出局!发送邮件通知冰朔"
            # 更新看守记录为 escalated
            jq --arg pr "$ github.event.pull_request.number " \
               --arg now "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
               '(.records[] | select(.pr_number == ($pr | tonumber))) |= . + {
                 "status": "escalated",
                 "escalated_at": $now,
                 "resolution": "human_intervention"
               }' "$WATCHDOG_DATA" > tmp.json && mv tmp.json "$WATCHDOG_DATA"
            
            git config user.name "Merge Watchdog"
            git config user.email "watchdog@guanghulab.com"
            git add "$WATCHDOG_DATA"
            git commit -m "[WATCHDOG] ⛔ PR #$ github.event.pull_request.number  三次修复失败,已通知人类"
            git push
          fi

      # -------- 三振出局:发邮件 --------
      - name: "📧 发送人类介入邮件"
        if: >
          needs.observe.outputs.workflow_result == 'failure' &&
          steps.load.outputs.retry_count >= 2
        uses: dawidd6/action-send-mail@v3
        with:
          server_address: smtp.gmail.com
          server_port: 465
          secure: true
          username: $ secrets.MAIL_USERNAME 
          password: $ secrets.MAIL_PASSWORD 
          subject: "⛔ [光湖系统] 自动修复三次失败 — 需要手动介入"
          to: $ secrets.HUMAN_EMAIL 
          from: "光湖 Merge Watchdog <noreply@guanghulab.com>"
          html_body: |
            <h2>⛔ 合并指令看守者 · 三振出局通知</h2>
            <p>以下修复经过 <strong>3轮自动修复</strong> 仍未解决:</p>
            <table border="1" cellpadding="8">
              <tr><td><b>PR</b></td><td>#$ github.event.pull_request.number </td></tr>
              <tr><td><b>目标 Workflow</b></td><td>$ needs.gate.outputs.target_workflows </td></tr>
              <tr><td><b>最新失败日志</b></td><td><a href="$ needs.observe.outputs.run_url ">点击查看</a></td></tr>
            </table>
            <h3>📋 你需要做什么:</h3>
            <ol>
              <li>打开上面的失败日志链接</li>
              <li>查看报错信息和上下文</li>
              <li>手动修复代码并推送到 main</li>
              <li>等待下一次 Workflow 运行验证</li>
            </ol>
            <p>看守者已暂停对该 Workflow 的自动修复。<br>
            手动修复成功后,仪表盘会自动更新。</p>
            <p style="color:gray;">— 光湖系统 · Merge Watchdog</p>


九、安全护栏


十、与现有系统的协作关系

flowchart LR
    A["🌙 天眼夜间修复引擎<br>v3.0"] -->|"创建 PR"| B["🔀 PR 合并"]
    B -->|"触发"| C["👁️ Merge Watchdog<br>合并指令看守者"]
    C -->|"修复成功"| D["📊 仪表盘<br>静默更新"]
    C -->|"修复失败<br>< 3次"| E["🔄 repository_dispatch"]
    E -->|"触发重新修复"| A
    C -->|"三振出局"| F["📧 邮件通知冰朔"]
    F -->|"人类手动修复"| B

    style C fill:#e94560,color:#fff
    style D fill:#1e5128,color:#fff
    style F fill:#7b113a,color:#fff

十一、铸渊 Secrets 配置清单

Secret 名称 用途 当前状态
DEPLOY_GITHUB_TOKEN 读取 Workflow 运行记录 + 创建分支 + 推送 已有
GEMINI_API_KEY 重试修复时调用 Gemini 分析 已有
MAIL_USERNAME 发送告警邮件的 SMTP 账号 ⚠️ 需配置
MAIL_PASSWORD 发送告警邮件的 SMTP 密码/应用专用密码 ⚠️ 需配置
HUMAN_EMAIL 冰朔的邮箱地址(接收告警) ⚠️ 需配置

十二、执行回执模板

## 执行回执

- [ ] Workflow 文件创建:`.github/workflows/merge-watchdog.yml`
- [ ] 看守记录 JSON 初始化:`docs/dashboard/watchdog-records.json`
- [ ] 与天眼夜间修复引擎 `repository_dispatch` 联动测试
- [ ] 邮件 Secrets 配置确认MAIL_USERNAME / MAIL_PASSWORD / HUMAN_EMAIL
- [ ] 仪表盘面板新增 `merge_watchdog` 区块
- [ ] 手动触发测试workflow_dispatch
- [ ] 回执签名:铸渊 · {日期}