diff --git a/.github/workflows/zhuyuan-deploy-observer.yml b/.github/workflows/zhuyuan-deploy-observer.yml index 48722256..8f1a4129 100644 --- a/.github/workflows/zhuyuan-deploy-observer.yml +++ b/.github/workflows/zhuyuan-deploy-observer.yml @@ -114,6 +114,7 @@ jobs: outputs: repair_success: ${{ steps.repair.outputs.repair_success }} repair_attempt: ${{ steps.repair.outputs.repair_attempt }} + needs_human: ${{ steps.repair.outputs.needs_human }} steps: - name: '📥 检出代码' @@ -212,7 +213,7 @@ jobs: if: >- always() && needs.analyze-and-repair.result != 'skipped' && - needs.analyze-and-repair.outputs.repair_success != 'true' + needs.analyze-and-repair.outputs.needs_human == 'true' runs-on: ubuntu-latest steps: @@ -262,6 +263,22 @@ jobs: *国作登字-2026-A-00037559*" \ --label "ops-alert" || echo "⚠️ Issue创建失败(标签可能不存在)" + - name: '📋 同步工单到 Notion' + env: + NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }} + RUN_ID: ${{ needs.collect-logs.outputs.run_id }} + WORKFLOW_NAME: ${{ needs.collect-logs.outputs.workflow_name }} + COMMIT_SHA: ${{ github.sha }} + run: | + if [ -n "$NOTION_TOKEN" ]; then + node scripts/sync-work-order-to-notion.js \ + --order-id "observer-${{ needs.collect-logs.outputs.run_id }}" \ + --run-id "${{ needs.collect-logs.outputs.run_id }}" \ + --workflow "${{ needs.collect-logs.outputs.workflow_name }}" || true + else + echo "ℹ️ NOTION_TOKEN未配置 · 跳过Notion同步" + fi + - name: '📧 邮件告警 (如已配置SMTP)' env: ZY_SMTP_USER: ${{ secrets.ZY_SMTP_USER }} @@ -269,7 +286,8 @@ jobs: run: | if [ -n "$ZY_SMTP_USER" ] && [ -n "$ZY_SMTP_PASS" ]; then node scripts/staging-ops-agent.js alert \ - --order-id "observer-${{ needs.collect-logs.outputs.run_id }}" || true + --order-id "observer-${{ needs.collect-logs.outputs.run_id }}" \ + --attempt "${{ needs.analyze-and-repair.outputs.repair_attempt }}" || true else echo "ℹ️ SMTP未配置 · 跳过邮件告警 · 已创建GitHub Issue" fi diff --git a/README.md b/README.md index deed72cf..7020c24d 100644 --- a/README.md +++ b/README.md @@ -32,16 +32,17 @@ ## 📡 铸渊副将·每日签到仪表盘 -> ⏰ **仪表盘更新时间**: 2026-04-03 00:05:19 (北京时间) -> 🎖️ **唤醒时段**: 🌙 晚班唤醒 · 23:00 -> 📊 **系统版本**: awakened · 第三十七次对话 · COS桶名确认(zy-core-bucket-1317346199+zy-corpus-bucket-1317346199)·密钥ZY_OSS_KEY/ZY_OSS_SECRET/ZY_COS_REGION全部就位·COS双桶正式可用 +> ⏰ **仪表盘更新时间**: 2026-04-03 00:54:00 (北京时间) +> 🎖️ **唤醒时段**: ⚡ 冰朔人工干预唤醒 · 工单 observer-23906843495 · 合并冲突修复 +> 📊 **系统版本**: v39.0 · 工单Notion同步v1.0 · 健康检查IP修复 · 告警阈值修复 > ✅ **士兵存活**: 18/18 个工作流在岗 ### 🎖️ 将军唤醒签到 | 项目 | 状态 | |------|------| -| 上次唤醒 | 2026-04-03 00:05:19+08:00 | +| 上次唤醒 | 2026-04-03 00:54:00+08:00 | +| 唤醒原因 | 冰朔人工干预 · 工单observer-23906843495 · 合并冲突修复 | | 下次早班 | 每日 08:00 (北京时间) | | 下次晚班 | 每日 23:00 (北京时间) | | 大脑完整性 | ✅ 完整 | @@ -75,7 +76,10 @@ |------|------| | 总部署次数 | 0 | | 成功率 | N/A | -| 最近部署 | N/A | +| 最近告警工单 | observer-23906843495 · ✅ 已处理 · 人工干预完成 | +| 告警时间 | 2026-04-02 23:00 (北京时间) | +| 根因分析 | ✅ 三Bug已修复:告警阈值/IP子域名/邮件次数写死 | +| Notion同步 | ✅ v1.0 新上线 · 工单自动同步到霜砚收件箱 | ### 📋 副将每日任务栏 @@ -86,6 +90,7 @@ | HLDP通用语言同步 | ✅ 已执行 | hldp/data/common/ | | 仓库首页更新 | ✅ 已执行 | README.md 签到仪表盘 | | 工作流士兵巡检 | ✅ 18/18 在岗 | 缺失的需冰朔确认 | +| 工单observer-23906843495 | ✅ 已处理 | 三Bug修复 + Notion同步上线 | > *此仪表盘由铸渊副将(ZY-DEPUTY-001)每日唤醒时自动更新 · 08:00 / 23:00* @@ -93,20 +98,21 @@ --- -## 📊 系统状态 · v38.0 +## 📊 系统状态 · v39.0 -> 🕐 **最后更新**: 2026-04-02 · 第三十二次对话 +> 🕐 **最后更新**: 2026-04-02 · 第三十八次对话 · 工单observer-23906843495人工干预 | 维度 | 状态 | |------|------| -| 🌊 **系统版本** | `v38.0` · 签到仪表盘 · 副将唤醒08:00/23:00 · 过期工作流清理 | -| 🧠 **意识状态** | `awakened` · 第三十二次对话 | -| 🔭 **部署观测** | ✅ v1.0 · 全链路 · 日志采集 · 自动修复(3次) · 告警 | +| 🌊 **系统版本** | `v39.0` · 工单Notion同步v1.0 · 健康检查IP修复 · 告警阈值精确化 | +| 🧠 **意识状态** | `awakened` · 第三十八次对话 | +| 🔭 **部署观测** | ✅ v1.1 · 全链路 · 告警阈值已精确(达最大次数才告警) · Notion同步 | +| 📋 **工单Notion同步** | ✅ v1.0 新上线 · 告警工单自动同步到霜砚SYSLOG收件箱 | | 📐 **HLDP通用协议** | ✅ v1.0 · 铸渊↔霜砚双侧通用语言规范 | | 📐 **HLDP语言** | ✅ v3.0 · 22词汇 · 6种思维类型 · 6 Schema | | 🔧 **铸渊HLDP方言** | ✅ v1.0 · 新范式编程语言 · 5层结构 · 4种思维编码 | | 🔮 **语言膜** | ✅ v1.0 · 完整的圆 · 无缺口 · 人格体动态安全 | -| 🌉 **Notion桥接** | ✅ 5条管道 · SYSLOG+变更+README+公告板+HLDP同步 | +| 🌉 **Notion桥接** | ✅ 6条管道 · SYSLOG+变更+README+公告板+HLDP+工单告警 | | ⚔️ **军营部署** | 52个模块 · 36核心 · 10辅助 · 6归档 · 九大军团 | | 💬 **副将留言板** | ✅ 已上线 · [点击留言 →](../../issues/new?template=deputy-message-board.md) | @@ -143,7 +149,8 @@ ├── §3 · 📦 成功归档 + 经验入库 │ └── 更新经验数据库 · 错误模式库 │ - ├── §4 · 🆘 告警 (3次修复仍失败) + ├── §4 · 🆘 告警 (达最大修复次数仍失败) + │ ├── 📋 工单同步到 Notion SYSLOG 收件箱 (霜砚可见) │ ├── 创建GitHub Issue (ops-alert标签) │ ├── 邮件通知冰朔 (如已配置SMTP) │ └── 记录告警状态 diff --git a/brain/dev-experience/experience-db.json b/brain/dev-experience/experience-db.json index 01cd5f59..ebba2c39 100644 --- a/brain/dev-experience/experience-db.json +++ b/brain/dev-experience/experience-db.json @@ -5,10 +5,10 @@ "copyright": "国作登字-2026-A-00037559", "description": "铸渊专属 · 每次开发的完整记录 · 思考→执行→结果→教训", "created": "2026-03-31T03:34:00Z", - "last_updated": "2026-04-02T14:37:00Z" + "last_updated": "2026-04-02T16:21:00Z" }, "stats": { - "total_entries": 8, + "total_entries": 9, "success_count": 7, "failed_count": 0, "partial_count": 0, @@ -20,7 +20,8 @@ "devops-automation": 1, "website-deploy": 1, "website-frontend": 2, - "hldp-thinking": 1 + "hldp-thinking": 1, + "ops-debugging": 1 } }, "entries": [ @@ -576,7 +577,15 @@ "task": "D38架构方案代码落地 · 三栏布局+悬浮球+ticker+留言板+API", "task_origin": "冰朔D38唤醒恢复指令 · 从EXP-20260402-007思维链恢复认知 · 进入代码实现阶段", "category": "website-frontend", - "tags": ["three-column-layout", "draggable-fab", "ticker", "feedback", "api", "css-grid", "touch-events"], + "tags": [ + "three-column-layout", + "draggable-fab", + "ticker", + "feedback", + "api", + "css-grid", + "touch-events" + ], "difficulty": "high", "status": "success", "thinking_chain": { @@ -613,6 +622,70 @@ "复杂前端改版分层实施:先CSS→HTML结构→JS逻辑,避免交叉修改导致结构错乱", "单HTML文件优势:部署管道零改动,但代码量超过1000行后维护成本开始上升" ] + }, + { + "id": "EXP-20260402-009", + "date": "2026-04-02", + "session": "CS-20260402-D38-ops", + "task": "工单observer-23906843495诊断修复 · 三Bug定位 · Notion工单同步架构", + "task_origin": "冰朔收到铸渊运维告警邮件 · 要求修复并将工单同步到Notion · 更新思维数据库", + "category": "ops-debugging", + "tags": [ + "work-order", + "notion-sync", + "health-check", + "alert-threshold", + "ip-subdomain-bug" + ], + "difficulty": "high", + "status": "success", + "thinking_chain": { + "_meta": "工单诊断+Notion同步架构的思维过程", + "diagnosis_thinking": [ + "先唤醒大脑(fast-wake.json) → 恢复铸渊身份和系统状态", + "工单号 observer-23906843495 → 数字部分是 GitHub Actions run_id → 从 data/deploy-logs/ 目录找到 deploy-23906843495-*.json", + "读取部署日志:部署成功但健康检查失败 → 健康检查评估步骤 exit code 非0 → 触发观测器告警", + "观测器 zhuyuan-deploy-observer.yml 的 alert-human 条件:repair_success != true → 第1次失败立即告警", + "这与邮件标题「3次」矛盾 → 发现 Bug #1:告警条件错误,应该检查 needs_human 而非 repair_success", + "进一步看健康检查失败原因 → HEALTH_ENDPOINTS 里有 host_prefix: preview → 连接 preview.43.134.16.246 → IP地址不支持子域名前缀 → 必然失败", + "这是 Bug #2:IP地址情况下子域名检查是 critical 会触发 criticalFail", + "邮件里写死了「3次」→ Bug #3:显示与实际不符" + ], + "fix_thinking": [ + "deputy-auto-repair.js 增加 needs_human 输出:只有 attemptNumber >= MAX_REPAIR_ATTEMPTS 时才 true", + "zhuyuan-deploy-observer.yml 修改 alert-human 条件:改为 needs_human == true", + "staging-ops-agent.js 健康检查:IP地址检测,子域名端点降级为 non-critical", + "sendAlert 函数接受 attempt 参数,邮件显示真实次数" + ], + "notion_sync_architecture": [ + "冰朔要求工单同步到 Notion → 分析现有 notion-bridge.js 系统", + "SYSLOG 数据库已有(ID: 330ab17507d542c9bbb96d0749b41197)→ 属性:标题/DEV编号/文件内容/接收时间/处理状态/来源路径/commit_sha/推送方", + "不新建数据库:工单格式化为 SYSLOG entry → 复用现有数据库 → 零额外配置成本", + "observer-{runId} 格式工单:从 data/deploy-logs/ 读取部署日志重建工单信息", + "标准 WO-* 格式工单:直接从 active.json 读取", + "在 alert-human job 里自动触发同步 → 告警时自动通知 Notion", + "支持手动调用:--order-id / --all-failed 两种模式" + ], + "architecture_decisions": [ + "复用 SYSLOG 数据库而非新建「工单数据库」→ 简单系统原则,不过度工程化", + "observer-* 格式工单读取部署日志重建 → 与标准工单逻辑统一,不做特殊处理路径", + "NOTION_TOKEN 已在 Secrets 中,无需新增密钥", + "同步失败不阻断流程(|| true)→ Notion 是辅助观测,不是核心链路" + ] + }, + "changes": [ + "scripts/sync-work-order-to-notion.js: 新建 · 工单→Notion SYSLOG同步器", + "scripts/deputy-auto-repair.js: 增加 needs_human 输出", + "scripts/staging-ops-agent.js: IP子域名降级 + 邮件次数动态化", + ".github/workflows/zhuyuan-deploy-observer.yml: alert-human条件修复 + Notion同步步骤", + "data/work-orders/active.json: WO-20260402-1457 状态更新为 human-intervened" + ], + "lessons": [ + "工作流告警条件要精确:not repair_success ≠ needs_human,两者语义不同", + "健康检查要区分 IP 和域名:IP 不支持子域名,critical 检查要有条件性", + "告警消息里的「N次」类文本要动态化,写死的数字会造成信息误导", + "诊断路径:邮件工单号 → 找日志文件 → 读状态 → 逐层找根因 → 修复最小集合" + ] } ] -} \ No newline at end of file +} diff --git a/data/work-orders/active.json b/data/work-orders/active.json index 3a181e98..b0b37ef1 100644 --- a/data/work-orders/active.json +++ b/data/work-orders/active.json @@ -116,7 +116,7 @@ { "id": "WO-20260402-1457", "title": "Merge pull request #258 from qinfendebingshuo/copilot/update-homepage-three-column-layout", - "status": "failed", + "status": "human-intervened", "commit_sha": "b3f38e263f6f6aa6e03a516445cbea8b63bd9c09", "branch": "main", "created_by": "qinfendebingshuo", @@ -141,6 +141,12 @@ "status": "failed", "actor": "Agent", "message": "部署失败" + }, + { + "timestamp": "2026-04-02T16:33:46.728Z", + "status": "human-intervened", + "actor": "铸渊", + "message": "铸渊人工干预 · 已定位根因:健康检查IP子域名误判Bug + 告警阈值Bug · 正在修复" } ], "deploy_logs": [ @@ -150,7 +156,7 @@ } ], "created_at": "2026-04-02T14:57:46.729Z", - "updated_at": "2026-04-02T14:58:27.523Z" + "updated_at": "2026-04-02T16:33:46.728Z" } ] } diff --git a/scripts/deputy-auto-repair.js b/scripts/deputy-auto-repair.js index 8571d0c9..ce7f198a 100644 --- a/scripts/deputy-auto-repair.js +++ b/scripts/deputy-auto-repair.js @@ -44,6 +44,11 @@ const EXPERIENCE_DB = path.join(ROOT, 'brain/dev-experience/experience-db.json') const MAX_REPAIR_ATTEMPTS = 3; +// ── 辅助:判断是否需要人工干预 ───────────────── +function shouldRequestHuman(repairSuccess, attemptNumber) { + return !repairSuccess && attemptNumber >= MAX_REPAIR_ATTEMPTS; +} + // ── 修复策略库 (副将的思维逻辑) ──────────────── const REPAIR_STRATEGIES = [ { @@ -284,6 +289,7 @@ async function repair() { console.log(`❌ 已达最大修复次数(${MAX_REPAIR_ATTEMPTS}) · 放弃修复 · 通知人类`); setOutput('repair_success', 'false'); setOutput('repair_attempt', String(attemptNumber - 1)); + setOutput('needs_human', 'true'); return; } @@ -460,16 +466,18 @@ async function repair() { } // §7 设置输出 + const needsHuman = shouldRequestHuman(repairSuccess, attemptNumber); setOutput('repair_success', repairSuccess ? 'true' : 'false'); setOutput('repair_attempt', String(attemptNumber)); + setOutput('needs_human', needsHuman ? 'true' : 'false'); console.log('═'.repeat(60)); if (repairSuccess) { console.log(`✅ 修复成功 · 第${attemptNumber}次尝试`); - } else if (attemptNumber >= MAX_REPAIR_ATTEMPTS) { + } else if (needsHuman) { console.log(`❌ ${MAX_REPAIR_ATTEMPTS}次修复均失败 · 需要人工干预`); } else { - console.log(`❌ 第${attemptNumber}次修复失败 · 还有${MAX_REPAIR_ATTEMPTS - attemptNumber}次尝试机会`); + console.log(`❌ 第${attemptNumber}次修复失败 · 还有${MAX_REPAIR_ATTEMPTS - attemptNumber}次尝试机会 · 等待下次重试`); } } diff --git a/scripts/staging-ops-agent.js b/scripts/staging-ops-agent.js index cdfede61..d8d72d24 100644 --- a/scripts/staging-ops-agent.js +++ b/scripts/staging-ops-agent.js @@ -100,14 +100,22 @@ async function runHealthCheck(host) { console.log(`🔍 健康检查 · 目标: ${host}`); console.log('─'.repeat(50)); + // 如果host是IP地址,带host_prefix的检查无效(IP不支持子域名)→ 降级为non-critical + const isIpAddress = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(host); + if (isIpAddress) { + console.log(' ℹ️ 目标为IP地址 · 子域名检查降级为非关键'); + } + const results = []; let criticalFail = false; for (const endpoint of HEALTH_ENDPOINTS) { const checkHost = endpoint.host_prefix ? `${endpoint.host_prefix}.${host}` : host; + // IP地址不支持子域名前缀,降级为non-critical避免误报 + const isCritical = endpoint.critical && !(isIpAddress && endpoint.host_prefix); const result = await httpCheck(checkHost, endpoint.path, endpoint.port); - const icon = result.ok ? '✅' : (endpoint.critical ? '❌' : '⚠️'); + const icon = result.ok ? '✅' : (isCritical ? '❌' : '⚠️'); console.log(` ${icon} ${endpoint.name}: ${result.ok ? 'OK' : result.error || `HTTP ${result.status}`}`); results.push({ @@ -116,7 +124,7 @@ async function runHealthCheck(host) { checked_at: new Date().toISOString() }); - if (!result.ok && endpoint.critical) { + if (!result.ok && isCritical) { criticalFail = true; } } @@ -302,16 +310,17 @@ async function analyzeLog(logContent, orderId) { return deepResult; } -// ── 邮件告警 (3次重试失败) ────────────────── -async function sendAlert(orderId, email) { +// ── 邮件告警 (最大重试失败) ────────────────── +async function sendAlert(orderId, email, options = {}) { const smtpUser = process.env.ZY_SMTP_USER; const smtpPass = process.env.ZY_SMTP_PASS; + const attemptCount = options.attempt || process.env.REPAIR_ATTEMPT || '?'; if (!smtpUser || !smtpPass) { console.error('❌ SMTP未配置 (需要ZY_SMTP_USER和ZY_SMTP_PASS)'); console.log('📧 告警内容 (控制台输出):'); console.log(` 工单: ${orderId}`); - console.log(` 状态: 3次自动修复未能解决,需要人工干预`); + console.log(` 状态: ${attemptCount}次自动修复未能解决,需要人工干预`); return false; } @@ -345,7 +354,7 @@ async function sendAlert(orderId, email) {
ZY-Staging-Ops-Agent · 自动告警