Source snapshot: 97d7f0fae96dc04b7ddad56fc1db6a108ed662cc [SEC-CLEAN] · pre-push-clean v1.0 · 109处敏感信息已自动转乱码
23 KiB
23 KiB
belongs_to
| belongs_to | |
|---|---|
|
👁️ 铸渊指令|合并指令看守者 · 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)
- [ ] 回执签名:铸渊 · {日期}