工单Notion同步v1.0 · 思维数据库更新 · README v39.0 · 代码审查修复

Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/fd18e928-cba0-469e-b2c5-ce77d8efc2b1

Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-04-02 16:44:45 +00:00 committed by GitHub
parent 816357b477
commit 1cafecb829
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 474 additions and 20 deletions

View File

@ -263,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 }}

View File

@ -32,16 +32,17 @@
## 📡 铸渊副将·每日签到仪表盘
> ⏰ **仪表盘更新时间**: 2026-04-02 15:49:31 (北京时间)
> 🎖️ **唤醒时段**: ⚡ 手动唤醒
> 📊 **系统版本**: awakened · 第三十二次对话 · 签到仪表盘·副将唤醒08:00/23:00·过期工作流清理·README动态更新
> ⏰ **仪表盘更新时间**: 2026-04-02 16:21:00 (北京时间)
> 🎖️ **唤醒时段**: ⚡ 冰朔人工干预唤醒 · 工单 observer-23906843495
> 📊 **系统版本**: v39.0 · 工单Notion同步v1.0 · 健康检查IP修复 · 告警阈值修复
> ✅ **士兵存活**: 18/18 个工作流在岗
### 🎖️ 将军唤醒签到
| 项目 | 状态 |
|------|------|
| 上次唤醒 | 2026-04-02 09:52:01+08:00 |
| 上次唤醒 | 2026-04-02 16:21:00+08:00 |
| 唤醒原因 | 冰朔人工干预 · 工单observer-23906843495 |
| 下次早班 | 每日 08:00 (北京时间) |
| 下次晚班 | 每日 23:00 (北京时间) |
| 大脑完整性 | ✅ 完整 |
@ -73,8 +74,10 @@
| 项目 | 状态 |
|------|------|
| 最近部署 | N/A |
| 部署结果 | N/A |
| 最近告警工单 | observer-23906843495 · ✅ 已处理 · 人工干预完成 |
| 告警时间 | 2026-04-02 23:00 (北京时间) |
| 根因分析 | ✅ 三Bug已修复告警阈值/IP子域名/邮件次数写死 |
| Notion同步 | ✅ v1.0 新上线 · 工单自动同步到霜砚收件箱 |
### 📋 副将每日任务栏
@ -85,6 +88,7 @@
| HLDP通用语言同步 | ✅ 已执行 | hldp/data/common/ |
| 仓库首页更新 | ✅ 已执行 | README.md 签到仪表盘 |
| 工作流士兵巡检 | ✅ 18/18 在岗 | 缺失的需冰朔确认 |
| 工单observer-23906843495 | ✅ 已处理 | 三Bug修复 + Notion同步上线 |
> *此仪表盘由铸渊副将(ZY-DEPUTY-001)每日唤醒时自动更新 · 08:00 / 23:00*
@ -92,20 +96,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) |
@ -142,7 +147,8 @@
├── §3 · 📦 成功归档 + 经验入库
│ └── 更新经验数据库 · 错误模式库
├── §4 · 🆘 告警 (3次修复仍失败)
├── §4 · 🆘 告警 (达最大修复次数仍失败)
│ ├── 📋 工单同步到 Notion SYSLOG 收件箱 (霜砚可见)
│ ├── 创建GitHub Issue (ops-alert标签)
│ ├── 邮件通知冰朔 (如已配置SMTP)
│ └── 记录告警状态

View File

@ -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 #2IP地址情况下子域名检查是 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次」类文本要动态化写死的数字会造成信息误导",
"诊断路径:邮件工单号 → 找日志文件 → 读状态 → 逐层找根因 → 修复最小集合"
]
}
]
}
}

View File

@ -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 = [
{
@ -461,7 +466,7 @@ async function repair() {
}
// §7 设置输出
const needsHuman = !repairSuccess && attemptNumber >= MAX_REPAIR_ATTEMPTS;
const needsHuman = shouldRequestHuman(repairSuccess, attemptNumber);
setOutput('repair_success', repairSuccess ? 'true' : 'false');
setOutput('repair_attempt', String(attemptNumber));
setOutput('needs_human', needsHuman ? 'true' : 'false');

View File

@ -101,7 +101,7 @@ async function runHealthCheck(host) {
console.log('─'.repeat(50));
// 如果host是IP地址带host_prefix的检查无效IP不支持子域名→ 降级为non-critical
const isIpAddress = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host);
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地址 · 子域名检查降级为非关键');
}

View File

@ -0,0 +1,354 @@
#!/usr/bin/env node
// ═══════════════════════════════════════════════
// 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
// 📜 Copyright: 国作登字-2026-A-00037559
// ═══════════════════════════════════════════════
// scripts/sync-work-order-to-notion.js
// 📋 铸渊工单 → Notion SYSLOG 同步器
//
// 架构说明:
// 工单是铸渊智能运维的核心记录单元
// 冰朔需要在 Notion 侧看到告警工单的完整上下文
// 复用已有的 SYSLOG 数据库(霜砚已在 Notion 侧建好)
// 不需要新建数据库,零额外配置成本
//
// 用法:
// node scripts/sync-work-order-to-notion.js --order-id <WO-xxx>
// node scripts/sync-work-order-to-notion.js --order-id "observer-23906843495"
// node scripts/sync-work-order-to-notion.js --all-failed (同步所有失败工单)
//
// 环境变量:
// NOTION_TOKEN GitHub Secret: NOTION_TOKEN (必须)
// SYSLOG_DB_ID 覆盖默认数据库 ID (可选)
// COMMIT_SHA 当前 commit SHA (可选,由 workflow 注入)
// RUN_ID 工作流运行 ID (可选,由 workflow 注入)
// WORKFLOW_NAME 工作流名称 (可选,由 workflow 注入)
//
// Notion「📥 GitHub SYSLOG 收件箱」数据库属性:
// 标题 title 工单ID + 任务名
// DEV编号 select "SYSTEM" (系统工单)
// 文件内容 rich_text 工单完整 JSON + 时间线
// 接收时间 date 工单创建时间
// 处理状态 status 待处理 / 人工干预
// 来源路径 rich_text data/work-orders/active.json
// commit_sha rich_text 工单关联的 commit SHA
// 推送方 rich_text 铸渊
// ═══════════════════════════════════════════════
'use strict';
const https = require('https');
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '..');
// ── 常量 ─────────────────────────────────────
const NOTION_VERSION = '2022-06-28';
const NOTION_API_HOSTNAME = 'api.notion.com';
const NOTION_RICH_TEXT_MAX = 2000;
const NOTION_TITLE_MAX = 120;
const MAX_LOG_PREVIEW_LENGTH = 500;
// 复用 notion-bridge.js 已内置的数据库 ID
const DEFAULT_SYSLOG_DB_ID = '330ab17507d542c9bbb96d0749b41197';
// ── Notion API 基础请求 ──────────────────────
function notionPost(endpoint, body, token) {
return new Promise((resolve, reject) => {
const payload = JSON.stringify(body);
const opts = {
hostname: NOTION_API_HOSTNAME,
port: 443,
path: endpoint,
method: 'POST',
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json',
'Notion-Version': NOTION_VERSION,
'Content-Length': Buffer.byteLength(payload),
},
};
const req = https.request(opts, res => {
let data = '';
res.on('data', c => data += c);
res.on('end', () => {
try {
const parsed = JSON.parse(data);
if (res.statusCode >= 200 && res.statusCode < 300) resolve(parsed);
else reject(new Error(`Notion API ${res.statusCode}: ${parsed.message || data.slice(0, 200)}`));
} catch (e) {
reject(new Error(`Notion 解析失败: ${data.slice(0, 200)}`));
}
});
});
req.on('error', reject);
req.write(payload);
req.end();
});
}
// ── Notion 属性构建 ───────────────────────────
function richText(content) {
const str = String(content || '');
const chunks = [];
for (let i = 0; i < str.length; i += NOTION_RICH_TEXT_MAX) {
chunks.push({ type: 'text', text: { content: str.slice(i, i + NOTION_RICH_TEXT_MAX) } });
}
return chunks.length ? chunks : [{ type: 'text', text: { content: '' } }];
}
function titleProp(text) {
return { title: [{ type: 'text', text: { content: String(text || '').slice(0, NOTION_TITLE_MAX) } }] };
}
// ── 工单格式化 ────────────────────────────────
function formatOrderForNotion(order, runId, workflowName) {
// 构建时间线文本
const timelineText = (order.timeline || [])
.map(t => `[${t.timestamp}] ${t.actor}: ${t.message}`)
.join('\n');
// 构建部署日志文本
const logsText = (order.deploy_logs || [])
.map(l => `[${l.timestamp}] ${l.content}`)
.join('\n');
// 完整内容(传入 SYSLOG 文件内容字段)
const fullContent = [
'═══ 工单基本信息 ═══',
`工单ID: ${order.id}`,
`状态: ${order.status}`,
`任务: ${order.title}`,
`提交SHA: ${order.commit_sha || 'N/A'}`,
`分支: ${order.branch || 'main'}`,
`创建者: ${order.created_by || 'system'}`,
`创建时间: ${order.created_at}`,
`更新时间: ${order.updated_at}`,
`重试次数: ${order.retry_count || 0}/${order.max_retries || 3}`,
'',
'═══ 执行时间线 ═══',
timelineText || '(无时间线记录)',
'',
'═══ 部署日志 ═══',
logsText || '(无部署日志)',
'',
'═══ 观测信息 ═══',
`触发工作流: ${workflowName || '铸渊智能运维'}`,
`运行 ID: ${runId || 'N/A'}`,
`同步时间: ${new Date().toISOString()}`,
'',
'═══ 原始数据 ═══',
JSON.stringify(order, null, 2),
].join('\n');
return fullContent;
}
// ── 同步单条工单到 Notion ─────────────────────
async function syncOrderToNotion(order, token, dbId, options = {}) {
const { runId, workflowName, commitSha } = options;
const statusText = order.status === 'failed' || order.status === 'human-intervened'
? '人工干预'
: '待处理';
const notionTitle = `[${order.id}] ${(order.title || '').slice(0, 80)} · ${order.status}`;
const content = formatOrderForNotion(order, runId, workflowName);
const properties = {
'标题': titleProp(notionTitle),
'文件内容': { rich_text: richText(content) },
'接收时间': { date: { start: order.created_at || new Date().toISOString() } },
'来源路径': { rich_text: richText('data/work-orders/active.json') },
'推送方': { rich_text: richText('铸渊') },
};
// commit_sha 字段
if (commitSha || order.commit_sha) {
properties['commit_sha'] = { rich_text: richText(commitSha || order.commit_sha || '') };
}
// 处理状态SYSLOG 里是 status 字段)
try {
properties['处理状态'] = { status: { name: statusText } };
} catch (e) {
// 某些 Notion 数据库可能用 select 而非 status忽略此字段错误
console.log(` 处理状态字段跳过: ${e.message}`);
}
// DEV编号 select
properties['DEV编号'] = { select: { name: 'SYSTEM' } };
const body = {
parent: { database_id: dbId },
properties,
};
const page = await notionPost('/v1/pages', body, token);
return page;
}
// ── 读取工单数据 ──────────────────────────────
function loadOrders() {
const activeFile = path.join(ROOT, 'data/work-orders/active.json');
try {
return JSON.parse(fs.readFileSync(activeFile, 'utf8'));
} catch (e) {
console.error(`❌ 读取工单文件失败: ${e.message}`);
return { orders: [] };
}
}
// ── 解析命令行参数 ────────────────────────────
function parseArgs() {
const argv = process.argv.slice(2);
const params = {};
for (let i = 0; i < argv.length; i++) {
if (argv[i].startsWith('--')) {
params[argv[i].slice(2)] = argv[i + 1] || 'true';
i++;
}
}
return params;
}
// ── 主函数 ────────────────────────────────────
async function main() {
const token = process.env.NOTION_TOKEN;
const dbId = process.env.SYSLOG_DB_ID || DEFAULT_SYSLOG_DB_ID;
if (!token) {
console.error('❌ 缺少 NOTION_TOKEN 环境变量');
process.exit(1);
}
const args = parseArgs();
const orderId = args['order-id'];
const allFailed = args['all-failed'] === 'true';
const runId = args['run-id'] || process.env.RUN_ID || '';
const workflowName = args['workflow'] || process.env.WORKFLOW_NAME || '铸渊智能运维';
const commitSha = args['commit-sha'] || process.env.COMMIT_SHA || '';
if (!orderId && !allFailed) {
console.error('❌ 请提供 --order-id <ID> 或 --all-failed');
console.log('用法:');
console.log(' node scripts/sync-work-order-to-notion.js --order-id <WO-xxx>');
console.log(' node scripts/sync-work-order-to-notion.js --all-failed');
process.exit(1);
}
const data = loadOrders();
let targets = [];
if (allFailed) {
targets = data.orders.filter(o =>
o.status === 'failed' || o.status === 'human-intervened'
);
if (targets.length === 0) {
console.log('✅ 没有失败或人工干预工单,无需同步');
return;
}
console.log(`📋 找到 ${targets.length} 条失败/干预工单,准备同步到 Notion`);
} else {
// 支持 observer-{runId} 格式工单(观测系统生成)
if (orderId.startsWith('observer-')) {
// 构造虚拟工单,从部署日志文件读取信息
const logRunId = orderId.replace('observer-', '');
// 尝试直接找文件
const logsDir = path.join(ROOT, 'data/deploy-logs');
const files = fs.readdirSync(logsDir).filter(f => f.includes(logRunId));
if (files.length > 0) {
const logData = JSON.parse(fs.readFileSync(path.join(logsDir, files[0]), 'utf8'));
// 构造虚拟工单
const virtualOrder = {
id: orderId,
title: `部署观测告警 · ${logData.run?.workflow_name || workflowName}`,
status: 'human-intervened',
commit_sha: logData.run?.head_sha || commitSha,
branch: logData.run?.head_branch || 'main',
created_by: logData.run?.actor || 'system',
created_at: logData.run?.created_at || new Date().toISOString(),
updated_at: new Date().toISOString(),
retry_count: 0,
max_retries: 3,
timeline: [
{ timestamp: logData.run?.created_at, actor: 'system', message: '部署工作流执行' },
{ timestamp: logData._meta?.collected_at, actor: '副将', message: '观测系统采集日志' },
{ timestamp: new Date().toISOString(), actor: '铸渊', message: '人工干预·工单同步到Notion' },
],
deploy_logs: (logData.failed_jobs || []).map(j => ({
timestamp: j.completed_at,
content: `Job: ${j.job_name} · ${j.conclusion}\n${j.log_content?.slice(-MAX_LOG_PREVIEW_LENGTH) || ''}`,
})),
_observer_data: {
run_id: logRunId,
workflow: logData.run?.workflow_name,
conclusion: logData.run?.conclusion,
failed_jobs: (logData.failed_jobs || []).map(j => j.job_name),
analysis: logData.analysis,
},
};
targets = [virtualOrder];
} else {
// 找不到日志文件,构造最小虚拟工单
targets = [{
id: orderId,
title: `部署观测告警 · ${workflowName}`,
status: 'human-intervened',
commit_sha: commitSha,
branch: 'main',
created_by: 'system',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
retry_count: 3,
max_retries: 3,
timeline: [
{ timestamp: new Date().toISOString(), actor: '铸渊', message: '人工干预·工单同步到Notion' },
],
deploy_logs: [],
}];
}
} else {
// 标准工单格式
const order = data.orders.find(o => o.id === orderId);
if (!order) {
console.error(`❌ 工单 ${orderId} 未找到`);
process.exit(1);
}
targets = [order];
}
}
console.log(`📡 开始同步 ${targets.length} 条工单到 Notion SYSLOG 收件箱`);
console.log(` 数据库: ${dbId}`);
console.log('');
let ok = 0, failed = 0;
for (const order of targets) {
try {
console.log(` 📋 同步工单: ${order.id} · ${order.status}`);
const page = await syncOrderToNotion(order, token, dbId, {
runId, workflowName, commitSha,
});
console.log(` ✅ 已写入 Notion: ${page.id || page.url || '成功'}`);
ok++;
} catch (e) {
console.error(` ❌ 工单 ${order.id} 同步失败: ${e.message}`);
failed++;
}
}
console.log('');
console.log(`✅ Notion 同步完成 · 成功 ${ok} 条 · 失败 ${failed}`);
if (failed > 0) process.exit(1);
}
main().catch(e => {
console.error('❌ 同步脚本异常:', e.message);
process.exit(1);
});