guanghulab/scripts/skyeye/asop-verifier.js
2026-05-10 13:12:44 +08:00

208 lines
6.5 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// scripts/skyeye/asop-verifier.js
// 天眼·ASOP 优化效果验证器
//
// 天眼运行时检查 data/asop-requests/executed/ 下已执行的优化
// 验证效果:
// ① 优化后相关 Workflow 是否正常运行?
// ② 是否引入了新的问题?
// ③ 连续 2 次失败 → 自动回滚
//
// 验证通过 → 移到 verified/ 归档
// 验证失败 → 回滚 + 记录
'use strict';
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '../..');
const ASOP_DIR = path.join(ROOT, 'data/asop-requests');
const EXECUTED_DIR = path.join(ASOP_DIR, 'executed');
const VERIFIED_DIR = path.join(ASOP_DIR, 'verified');
const SNAPSHOTS_DIR = path.join(ASOP_DIR, 'snapshots');
const SKYEYE_DIR = '/tmp/skyeye';
const BEIJING_OFFSET_MS = 8 * 3600 * 1000;
const CONSECUTIVE_FAILURE_THRESHOLD = 2;
// ━━━ 安全读取 JSON ━━━
function readJSON(filePath) {
try {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
} catch (e) {
return null;
}
}
// ━━━ 检查受影响 Workflow 的最近运行状态 ━━━
function checkWorkflowHealth(affectedWorkflows) {
// 从天眼扫描结果中获取 Workflow 运行状态
const recentRunsPath = path.join(SKYEYE_DIR, 'recent-runs.json');
if (!fs.existsSync(recentRunsPath)) {
return { healthy: true, reason: '无最近运行数据可验证,默认通过' };
}
const recentRuns = readJSON(recentRunsPath) || [];
const failures = [];
for (const wf of affectedWorkflows) {
const wfRuns = recentRuns.filter(r => {
const name = r.name || '';
return name.includes(wf.replace('.yml', '').replace('.yaml', ''));
});
const recentFailures = wfRuns.filter(r => r.conclusion === 'failure');
if (recentFailures.length >= CONSECUTIVE_FAILURE_THRESHOLD) {
failures.push({
workflow: wf,
consecutive_failures: recentFailures.length
});
}
}
if (failures.length > 0) {
return {
healthy: false,
reason: `连续失败检测:${failures.map(f => `${f.workflow}(${f.consecutive_failures}次)`).join(', ')}`,
failures
};
}
return { healthy: true, reason: '所有受影响 Workflow 运行正常' };
}
// ━━━ 回滚优化 ━━━
function rollbackOptimization(request) {
const requestId = request.request_id;
const snapshotDir = path.join(SNAPSHOTS_DIR, requestId);
if (!fs.existsSync(snapshotDir)) {
console.log(`⚠️ ${requestId} 快照不存在,无法回滚`);
return false;
}
const snapshotFiles = fs.readdirSync(snapshotDir).filter(f => f !== '.gitkeep');
let restored = 0;
for (const file of snapshotFiles) {
const originalPath = file.replace(/__/g, '/');
const srcPath = path.join(snapshotDir, file);
const destPath = path.join(ROOT, originalPath);
try {
const destDir = path.dirname(destPath);
fs.mkdirSync(destDir, { recursive: true });
fs.copyFileSync(srcPath, destPath);
restored++;
} catch (e) {
console.error(`⚠️ 回滚 ${originalPath} 失败: ${e.message}`);
}
}
console.log(`🔄 ${requestId} 已回滚 ${restored}/${snapshotFiles.length} 个文件`);
return restored > 0;
}
// ━━━ 验证单个优化 ━━━
function verifyOptimization(request) {
const requestId = request.request_id;
const affectedWorkflows = (request.impact_assessment && request.impact_assessment.affected_workflows) || [];
console.log(`\n🔍 验证: ${requestId}`);
console.log(` 方案: ${request.proposed_optimization}`);
// 检查受影响 Workflow 的健康度
const health = checkWorkflowHealth(affectedWorkflows);
if (!health.healthy) {
console.log(` ❌ 验证失败: ${health.reason}`);
// 异常熔断:回滚
const rolled = rollbackOptimization(request);
return {
verified: false,
reason: health.reason,
rolled_back: rolled,
verified_at: new Date(Date.now() + BEIJING_OFFSET_MS).toISOString().replace('T', ' ').slice(0, 19) + '+08:00'
};
}
console.log(` ✅ 验证通过: ${health.reason}`);
return {
verified: true,
reason: health.reason,
rolled_back: false,
verified_at: new Date(Date.now() + BEIJING_OFFSET_MS).toISOString().replace('T', ' ').slice(0, 19) + '+08:00'
};
}
// ━━━ 主验证流程 ━━━
function verifyAll() {
console.log('🔍 天眼·ASOP 验证器启动');
console.log('═══════════════════════════════════════════\n');
if (!fs.existsSync(EXECUTED_DIR)) {
console.log(' executed/ 目录不存在,无待验证优化');
const result = { total: 0, verified: 0, rolled_back: 0 };
console.log(JSON.stringify(result, null, 2));
return result;
}
const files = fs.readdirSync(EXECUTED_DIR)
.filter(f => f.endsWith('.json') && f !== '.gitkeep');
if (files.length === 0) {
console.log('🔍 ASOP无待验证的已执行优化');
const result = { total: 0, verified: 0, rolled_back: 0 };
console.log(JSON.stringify(result, null, 2));
return result;
}
console.log(`🔍 ASOP发现 ${files.length} 条待验证的已执行优化\n`);
const summary = { total: files.length, verified: 0, rolled_back: 0, results: [] };
for (const file of files) {
const filePath = path.join(EXECUTED_DIR, file);
const req = readJSON(filePath);
if (!req) {
console.log(`⚠️ 无法解析 ${file},跳过`);
continue;
}
const result = verifyOptimization(req);
req.verification_result = result;
if (result.verified) {
// 验证通过 → 移到 verified/ 归档
fs.mkdirSync(VERIFIED_DIR, { recursive: true });
fs.writeFileSync(path.join(VERIFIED_DIR, file), JSON.stringify(req, null, 2) + '\n');
fs.unlinkSync(filePath);
summary.verified++;
} else {
// 验证失败 + 已回滚 → 标记在 executed/(保留记录)
fs.writeFileSync(filePath, JSON.stringify(req, null, 2) + '\n');
summary.rolled_back++;
}
summary.results.push({
request_id: req.request_id,
verified: result.verified,
rolled_back: result.rolled_back,
reason: result.reason
});
}
console.log(`\n📊 验证结果:通过 ${summary.verified} / 回滚 ${summary.rolled_back}`);
console.log(JSON.stringify(summary, null, 2));
return summary;
}
// ━━━ 导出 ━━━
module.exports = { verifyAll, verifyOptimization, rollbackOptimization };
// ━━━ 直接运行 ━━━
if (require.main === module) {
verifyAll();
}