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();
|
|||
|
|
}
|