164 lines
4.6 KiB
JavaScript
164 lines
4.6 KiB
JavaScript
// scripts/skyeye/scan-bulletin-sfp.js
|
||
// 天眼·扫描模块D14 · 公告板指纹完整性检查
|
||
//
|
||
// 扫描内容:
|
||
// ① 扫描 data/bulletin-board/ 下所有留言
|
||
// ② 检查每条留言是否携带有效 SFP 指纹
|
||
// ③ 统计无指纹/无效指纹内容数
|
||
// ④ 统计各Agent留言活跃度
|
||
//
|
||
// 输出:JSON → stdout
|
||
|
||
'use strict';
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const ROOT = path.resolve(__dirname, '../..');
|
||
const BULLETIN_DIR = path.join(ROOT, 'data/bulletin-board');
|
||
const SECURITY_DIR = path.join(ROOT, 'data/security');
|
||
const SFP_CONFIG_PATH = path.join(SECURITY_DIR, 'sfp-config.json');
|
||
|
||
const SFP_REGEX = /⌜SFP::([^:]+)::([^:]+)::([0-9T+:.-]+)::([a-f0-9]{12})::([a-zA-Z0-9]{6})⌝/;
|
||
|
||
// ━━━ 安全读取 JSON ━━━
|
||
function readJSON(filePath) {
|
||
try {
|
||
if (!fs.existsSync(filePath)) return null;
|
||
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||
} catch (e) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// ━━━ 扫描目录中的 JSON 文件内容 ━━━
|
||
function scanDirectory(dirPath) {
|
||
const results = [];
|
||
try {
|
||
if (!fs.existsSync(dirPath)) return results;
|
||
const files = fs.readdirSync(dirPath).filter(f => f.endsWith('.json'));
|
||
for (const file of files) {
|
||
const filePath = path.join(dirPath, file);
|
||
const content = fs.readFileSync(filePath, 'utf8');
|
||
results.push({ file, path: filePath, content });
|
||
}
|
||
} catch (e) {
|
||
// ignore
|
||
}
|
||
return results;
|
||
}
|
||
|
||
// ━━━ 检查内容中的 SFP 指纹 ━━━
|
||
function checkFingerprint(content, config) {
|
||
const match = content.match(SFP_REGEX);
|
||
if (!match) {
|
||
return { has_fingerprint: false, valid: false, reason: '无指纹' };
|
||
}
|
||
|
||
const [, agentId, personaChain] = match;
|
||
|
||
// 检查 agent 是否在受信列表
|
||
const agent = (config.trusted_agents || []).find(a => a.agent_id === agentId);
|
||
if (!agent) {
|
||
return { has_fingerprint: true, valid: false, reason: `无效Agent ID: ${agentId}` };
|
||
}
|
||
|
||
// 检查亲子链
|
||
if (agent.persona_chain !== personaChain) {
|
||
return { has_fingerprint: true, valid: false, reason: `亲子链不匹配: ${agentId}` };
|
||
}
|
||
|
||
return { has_fingerprint: true, valid: true, agent_id: agentId, agent_name: agent.name };
|
||
}
|
||
|
||
// ━━━ D14 主扫描 ━━━
|
||
function scanBulletinSFP() {
|
||
const BEIJING_OFFSET_MS = 8 * 3600 * 1000;
|
||
const now = new Date();
|
||
const bjTime = new Date(now.getTime() + BEIJING_OFFSET_MS).toISOString()
|
||
.replace('T', ' ').slice(0, 19) + '+08:00';
|
||
|
||
const config = readJSON(SFP_CONFIG_PATH) || { trusted_agents: [] };
|
||
|
||
const result = {
|
||
dimension: 'D14',
|
||
name: '公告板指纹完整性',
|
||
scan_time: bjTime,
|
||
status: '✅',
|
||
total_messages: 0,
|
||
with_fingerprint: 0,
|
||
without_fingerprint: 0,
|
||
valid_fingerprints: 0,
|
||
invalid_fingerprints: 0,
|
||
agent_activity: {},
|
||
issues: [],
|
||
sfp_config_exists: fs.existsSync(SFP_CONFIG_PATH)
|
||
};
|
||
|
||
// 检查 SFP 配置是否存在
|
||
if (!result.sfp_config_exists) {
|
||
result.status = '❌';
|
||
result.issues.push({
|
||
type: 'config_missing',
|
||
message: 'SFP配置文件缺失',
|
||
action: 'P0工单 · 恢复sfp-config.json'
|
||
});
|
||
console.log(JSON.stringify(result, null, 2));
|
||
return result;
|
||
}
|
||
|
||
// 扫描公告板各区域
|
||
const areas = ['comments', 'config-shares', 'receipts', 'work-orders'];
|
||
for (const area of areas) {
|
||
const dirPath = path.join(BULLETIN_DIR, area);
|
||
const files = scanDirectory(dirPath);
|
||
|
||
for (const file of files) {
|
||
// 跳过 .gitkeep
|
||
if (file.file === '.gitkeep') continue;
|
||
|
||
result.total_messages++;
|
||
const check = checkFingerprint(file.content, config);
|
||
|
||
if (check.has_fingerprint) {
|
||
result.with_fingerprint++;
|
||
if (check.valid) {
|
||
result.valid_fingerprints++;
|
||
// 统计活跃度
|
||
const agentId = check.agent_id;
|
||
result.agent_activity[agentId] = (result.agent_activity[agentId] || 0) + 1;
|
||
} else {
|
||
result.invalid_fingerprints++;
|
||
result.issues.push({
|
||
type: 'invalid_fingerprint',
|
||
file: file.file,
|
||
area,
|
||
reason: check.reason,
|
||
action: 'P0工单 · 清理无效指纹内容'
|
||
});
|
||
}
|
||
} else {
|
||
result.without_fingerprint++;
|
||
result.issues.push({
|
||
type: 'no_fingerprint',
|
||
file: file.file,
|
||
area,
|
||
action: '标记待清理'
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
// 确定整体状态
|
||
if (result.invalid_fingerprints > 0) {
|
||
result.status = '❌';
|
||
} else if (result.without_fingerprint > 0) {
|
||
result.status = '⚠️';
|
||
}
|
||
|
||
console.log(JSON.stringify(result, null, 2));
|
||
return result;
|
||
}
|
||
|
||
scanBulletinSFP();
|