208 lines
6.5 KiB
JavaScript
208 lines
6.5 KiB
JavaScript
// 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();
|
||
}
|