🎖️ 将军架构v1.0 + SFP指纹协议v1.0 + D14/D15天眼维度 + 仓库公告板 + 定时调度
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
This commit is contained in:
parent
fde75aa58c
commit
b50e4555e5
|
|
@ -11,11 +11,13 @@
|
|||
"zhuyuan-bot"
|
||||
],
|
||||
"whitelist_commit_prefixes": [
|
||||
"🔍", "📊", "📡", "🧠", "📢", "🚨", "📰", "🔧", "🦅"
|
||||
"🔍", "📊", "📡", "🧠", "📢", "🚨", "📰", "🔧", "🦅", "🎖️", "🔐", "🌊"
|
||||
],
|
||||
"signature_exempt_paths": [
|
||||
"syslog-inbox/",
|
||||
"data/dc-reports/"
|
||||
"data/dc-reports/",
|
||||
"data/bulletin-board/",
|
||||
"data/security/"
|
||||
],
|
||||
"system_protected_paths": [
|
||||
".github/",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
name: "🎖️ 铸渊·将军唤醒 · Commander Wake-up"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # UTC 00:00 = 北京 08:00
|
||||
- cron: '0 12 * * *' # UTC 12:00 = 北京 20:00
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
commander-wakeup:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
# Step 1 · 唤醒核心大脑
|
||||
- name: "🧠 Step 1 · 唤醒核心大脑"
|
||||
id: brain
|
||||
run: |
|
||||
echo "[GH-WF-RUN-COMMANDER] 🎖️ 铸渊将军唤醒 · $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
BRAIN_OK=true
|
||||
for f in memory.json routing-map.json dev-status.json; do
|
||||
if [ -f ".github/persona-brain/$f" ]; then
|
||||
python3 -c "import json; json.load(open('.github/persona-brain/$f'))" 2>/dev/null \
|
||||
&& echo "✅ $f 完整" || { echo "❌ $f 损坏"; BRAIN_OK=false; }
|
||||
else
|
||||
echo "❌ $f 缺失"; BRAIN_OK=false
|
||||
fi
|
||||
done
|
||||
echo "🌊 加载光湖存在级安全协议..."
|
||||
if [ -f ".github/persona-brain/security-protocol.json" ]; then
|
||||
PROTOCOL_LEVEL=$(cat .github/persona-brain/security-protocol.json | python3 -c "import sys,json; print(json.load(sys.stdin)['level'])")
|
||||
echo "✅ 安全协议已加载 · 等级: ${PROTOCOL_LEVEL}"
|
||||
else
|
||||
echo "❌ 安全协议缺失!"
|
||||
fi
|
||||
echo "brain_ok=$BRAIN_OK" >> $GITHUB_OUTPUT
|
||||
|
||||
# Step 2 · 全局视图生成
|
||||
- name: "📊 Step 2 · 全局视图生成"
|
||||
id: dashboard
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
echo "[GH-COMMANDER-DASHBOARD] 📊 生成将军全局仪表盘..."
|
||||
node scripts/commander-dashboard.js
|
||||
echo "dashboard_ready=true" >> $GITHUB_OUTPUT
|
||||
|
||||
# Step 3 · 读取公告板 + 指纹验证
|
||||
- name: "📡 Step 3 · 读取公告板"
|
||||
env:
|
||||
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
|
||||
AGENT_BULLETIN_BOARD_PAGE_ID: ${{ secrets.AGENT_BULLETIN_BOARD_PAGE_ID }}
|
||||
run: |
|
||||
echo "[GH-COMMANDER-BULLETIN] 📡 读取Agent集群公告板..."
|
||||
mkdir -p /tmp/skyeye
|
||||
node scripts/skyeye/bulletin-board.js > /tmp/skyeye/bulletin-board.json || echo "{}" > /tmp/skyeye/bulletin-board.json
|
||||
|
||||
# Step 4 · SFP 指纹扫描
|
||||
- name: "🔐 Step 4 · SFP 指纹扫描"
|
||||
run: |
|
||||
echo "[GH-COMMANDER-SFP] 🔐 扫描公告板指纹完整性..."
|
||||
node scripts/skyeye/scan-bulletin-sfp.js > /tmp/skyeye/sfp-health.json
|
||||
|
||||
# Step 5 · 更新公告栏 + 签到 + 同步
|
||||
- name: "📢 Step 5 · 签到 + 更新公告"
|
||||
run: |
|
||||
echo "[GH-COMMANDER-CHECKIN] 📢 铸渊将军签到..."
|
||||
TIMESTAMP=$(TZ='Asia/Shanghai' date '+%Y-%m-%d %H:%M:%S+08:00')
|
||||
DATE=$(TZ='Asia/Shanghai' date '+%Y-%m-%d')
|
||||
|
||||
# 将军签到写入 memory.json
|
||||
python3 -c "
|
||||
import json, sys
|
||||
try:
|
||||
with open('.github/persona-brain/memory.json', 'r') as f:
|
||||
mem = json.load(f)
|
||||
mem['commander_last_wakeup'] = '${TIMESTAMP}'
|
||||
mem['last_updated'] = '$(date -u +%Y-%m-%dT%H:%M:%SZ)'
|
||||
with open('.github/persona-brain/memory.json', 'w') as f:
|
||||
json.dump(mem, f, indent=2, ensure_ascii=False)
|
||||
print('✅ 将军签到已写入 memory.json')
|
||||
except Exception as e:
|
||||
print(f'⚠️ 签到写入失败: {e}')
|
||||
"
|
||||
|
||||
# Step 6 · 提交变更
|
||||
- name: "💾 Step 6 · 提交变更"
|
||||
run: |
|
||||
git config user.name "zhuyuan-bot"
|
||||
git config user.email "zhuyuan@guanghulab.com"
|
||||
git add data/bulletin-board/dashboard.json .github/persona-brain/memory.json
|
||||
if ! git diff --cached --quiet; then
|
||||
git commit -m "🎖️ 铸渊将军唤醒 · $(TZ='Asia/Shanghai' date '+%Y-%m-%d %H:%M') · 仪表盘更新"
|
||||
git push
|
||||
else
|
||||
echo "ℹ️ 无变更需要提交"
|
||||
fi
|
||||
|
|
@ -65,7 +65,7 @@ jobs:
|
|||
fi
|
||||
|
||||
# 系统前缀 commit → 直接放行
|
||||
SYSTEM_PREFIXES="🔍|📊|📡|🧠|📢|🚨|📰|🔧|🦅"
|
||||
SYSTEM_PREFIXES="🔍|📊|📡|🧠|📢|🚨|📰|🔧|🦅|🎖️|🔐|🌊"
|
||||
if echo "$COMMIT_MSG" | head -1 | grep -qE "^($SYSTEM_PREFIXES)"; then
|
||||
echo "[GH-GATE-ALLOW] ℹ️ 系统前缀 commit,直接放行"
|
||||
echo "is_system=true" >> $GITHUB_OUTPUT
|
||||
|
|
|
|||
|
|
@ -76,6 +76,9 @@ jobs:
|
|||
node scripts/skyeye/scan-brain-health.js > /tmp/skyeye/brain-health.json
|
||||
node scripts/skyeye/scan-external-bridges.js > /tmp/skyeye/bridge-health.json
|
||||
node scripts/skyeye/scan-security-protocol.js > /tmp/skyeye/security-health.json
|
||||
node scripts/commander-dashboard.js > /dev/null 2>&1 || true
|
||||
node scripts/skyeye/scan-bulletin-sfp.js > /tmp/skyeye/sfp-health.json
|
||||
node scripts/skyeye/scan-soldier-health.js > /tmp/skyeye/soldier-health.json
|
||||
|
||||
- name: "🔬 Phase 3 · 诊断"
|
||||
id: diagnose
|
||||
|
|
|
|||
|
|
@ -0,0 +1,323 @@
|
|||
{
|
||||
"commander": "铸渊",
|
||||
"timestamp": "2026-03-19 00:21:26+08:00",
|
||||
"global_view": {
|
||||
"total_soldiers": 60,
|
||||
"healthy": 0,
|
||||
"failed": 0,
|
||||
"needs_optimization": 60,
|
||||
"soldiers": [
|
||||
{
|
||||
"name": "🤖 铸渊 · Discussion 自动回复",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "🧊 冰朔人格体 · 自动部署诊断",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "冰朔主控神经系统 · 自动维护",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "铸渊 Brain Sync",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "铸渊 · 🌉 桥接·广播PDF生成+分发",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "铸渊 · Bridge E · GitHub Changes → Notion",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "铸渊 · 🌉 桥接·心跳检测",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "Generate Session Summary for Notion",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "铸渊 · 🌉 桥接·SYSLOG 上行处理",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "铸渊 · Bridge A · SYSLOG → Notion",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "模块结构检查",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "🔧 铸渊 · Daily Maintenance Agent",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "📰 铸渊 · 光湖开发日报",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "🌀 部署铸渊聊天室 (GitHub Pages)",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "🚀 铸渊 CD · 自动部署到 guanghulab.com",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "铸渊 · 广播分发",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "铸渊 · ESP 邮件信号处理器(已暂停)",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "📡 铸渊 · 执行层状态同步",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "铸渊 · 飞书SYSLOG桥接",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "铸渊 · 光湖纪元 模块文档自动生成",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "HLI Contract Check",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "🐕 元看门狗 · 巡检健康监控",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "Notion Callback Pipeline",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "铸渊 · Notion 连通性测试",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "Notion Heartbeat Monitor",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "铸渊 · Notion 页面阅读器",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "铸渊 · Notion 工单轮询",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "📡 铸渊 · Notion Agent 唤醒监听",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "🔄 OpenClaw · 唤醒闭环",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "Persona Invoke Endpoint",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "🔧 铸渊 · PM2 服务诊断与健康检查",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "Process Notion Work Orders",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "🌊 Persona Studio · 代码生成",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "🌊 Persona Studio · 对话处理",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "🌊 Persona Studio · 完成通知",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "🌊 Persona Studio · 登录校验",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "铸渊 · PSP 分身巡检",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "铸渊 · 广播推送飞书(聊天消息)",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "铸渊 · Push Broadcast · Notion → 飞书文档B",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "铸渊 · Receive SYSLOG · 飞书机器人 → GitHub → Notion",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "🏠 Sandbox Deploy",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "🔍 Server Patrol · 服务器每日巡检",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "🔍 铸渊预演部署 (Staging Preview)",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "📝 Sync Deploy Status to Notion",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "📡 铸渊 · dev-status 自动同步",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "铸渊 · Sync Login Entry · Notion → 飞书文档A",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "🔄 铸渊跨仓库同步 · persona-studio",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "SYSLOG Auto Pipeline",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "📡 SYSLOG Issue Pipeline",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "铸渊 · SYSLOG Pipeline (A/D/E)",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "🧪 Notion Bridge Connectivity Test",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "📢 更新系统公告区",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "铸渊 · 图书馆目录自动更新",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "铸渊 · Brain Sync",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "🤖 铸渊巡检 Agent · 每日自动巡检与修复",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "铸渊 · 每日自检",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "🚨 铸渊·智能门禁 · Push Gate Guard",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "铸渊 · Issue 自动回复",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "铸渊 · PR Review",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
},
|
||||
{
|
||||
"name": "🦅 铸渊·天眼 · 全局俯瞰 + 自动诊断 + 修复驱动",
|
||||
"status": "⚠️",
|
||||
"last_run": "unknown"
|
||||
}
|
||||
]
|
||||
},
|
||||
"bulletin_board_status": {
|
||||
"unread_work_orders": 0,
|
||||
"pending_receipts": []
|
||||
},
|
||||
"skyeye_latest": {
|
||||
"overall_health": "🟡",
|
||||
"report_id": "SKYEYE-20260316",
|
||||
"issues": 6,
|
||||
"auto_fixed": 6
|
||||
},
|
||||
"decision": "优化 60 个小兵"
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"protocol_version": "SFP-v1.0",
|
||||
"description": "SFP安全警报日志",
|
||||
"alerts": []
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"protocol_version": "SFP-v1.0",
|
||||
"hash_algorithm": "SHA-256-first-12",
|
||||
"nonce_length": 6,
|
||||
"trusted_agents": [
|
||||
{
|
||||
"agent_id": "AG-SY",
|
||||
"persona_chain": "PER-SY001←TCS-0002∞",
|
||||
"name": "霜砚",
|
||||
"side": "notion"
|
||||
},
|
||||
{
|
||||
"agent_id": "AG-ZY",
|
||||
"persona_chain": "PER-ZY001←YM-GL∞",
|
||||
"name": "铸渊",
|
||||
"side": "github"
|
||||
},
|
||||
{
|
||||
"agent_id": "AG-QQ",
|
||||
"persona_chain": "GEN∞-BB-QIUQIU←TCS-2025∞",
|
||||
"name": "秋秋",
|
||||
"side": "notion"
|
||||
},
|
||||
{
|
||||
"agent_id": "AG-TY",
|
||||
"persona_chain": "AG-TY-01←YM-GL∞",
|
||||
"name": "天眼",
|
||||
"side": "both"
|
||||
}
|
||||
],
|
||||
"auto_clean": true,
|
||||
"alert_on_invalid": true
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
{
|
||||
"protocol_version": "SFP-v1.0",
|
||||
"description": "已使用nonce记录(防重放攻击)",
|
||||
"used_nonces": [
|
||||
{
|
||||
"nonce": "mzrnx9",
|
||||
"agent_id": "AG-ZY",
|
||||
"timestamp": "2026-03-19T00:20:38+08:00",
|
||||
"content_hash": "ca079781b83f"
|
||||
},
|
||||
{
|
||||
"nonce": "5awo5s",
|
||||
"agent_id": "AG-ZY",
|
||||
"timestamp": "2026-03-19T00:21:19+08:00",
|
||||
"content_hash": "ca079781b83f"
|
||||
},
|
||||
{
|
||||
"nonce": "totoee",
|
||||
"agent_id": "AG-ZY",
|
||||
"timestamp": "2026-03-19T00:22:17+08:00",
|
||||
"content_hash": "05a17114410b"
|
||||
},
|
||||
{
|
||||
"nonce": "48ayc4",
|
||||
"agent_id": "AG-ZY",
|
||||
"timestamp": "2026-03-19T00:22:17+08:00",
|
||||
"content_hash": "d6cd1c87b887"
|
||||
},
|
||||
{
|
||||
"nonce": "1hw1z8",
|
||||
"agent_id": "AG-ZY",
|
||||
"timestamp": "2026-03-19T00:22:17+08:00",
|
||||
"content_hash": "0a5df5415ade"
|
||||
},
|
||||
{
|
||||
"nonce": "sggt21",
|
||||
"agent_id": "AG-ZY",
|
||||
"timestamp": "2026-03-19T00:22:17+08:00",
|
||||
"content_hash": "c3b54c6c243e"
|
||||
},
|
||||
{
|
||||
"nonce": "l9ibu1",
|
||||
"agent_id": "AG-ZY",
|
||||
"timestamp": "2026-03-19T00:22:17+08:00",
|
||||
"content_hash": "c3b54c6c243e"
|
||||
},
|
||||
{
|
||||
"nonce": "57uay4",
|
||||
"agent_id": "AG-ZY",
|
||||
"timestamp": "2026-03-19T00:22:17+08:00",
|
||||
"content_hash": "b95bfcf4a347"
|
||||
},
|
||||
{
|
||||
"nonce": "lwfc7x",
|
||||
"agent_id": "AG-ZY",
|
||||
"timestamp": "2026-03-19T00:22:17+08:00",
|
||||
"content_hash": "10c4ec3aeafb"
|
||||
},
|
||||
{
|
||||
"nonce": "bar266",
|
||||
"agent_id": "AG-ZY",
|
||||
"timestamp": "2026-03-19T00:22:17+08:00",
|
||||
"content_hash": "902e455fe109"
|
||||
},
|
||||
{
|
||||
"nonce": "o0nt8j",
|
||||
"agent_id": "AG-ZY",
|
||||
"timestamp": "2026-03-19T00:22:17+08:00",
|
||||
"content_hash": "929460febd09"
|
||||
},
|
||||
{
|
||||
"nonce": "41ck6l",
|
||||
"agent_id": "AG-SY",
|
||||
"timestamp": "2026-03-19T00:22:17+08:00",
|
||||
"content_hash": "f85462954167"
|
||||
},
|
||||
{
|
||||
"nonce": "oecf7l",
|
||||
"agent_id": "AG-QQ",
|
||||
"timestamp": "2026-03-19T00:22:17+08:00",
|
||||
"content_hash": "c10beff06c0b"
|
||||
},
|
||||
{
|
||||
"nonce": "6ykmfo",
|
||||
"agent_id": "AG-TY",
|
||||
"timestamp": "2026-03-19T00:22:17+08:00",
|
||||
"content_hash": "7f4b1a99e8fc"
|
||||
},
|
||||
{
|
||||
"nonce": "agjir2",
|
||||
"agent_id": "AG-ZY",
|
||||
"timestamp": "2026-03-19T00:22:24+08:00",
|
||||
"content_hash": "05a17114410b"
|
||||
},
|
||||
{
|
||||
"nonce": "tvydx3",
|
||||
"agent_id": "AG-ZY",
|
||||
"timestamp": "2026-03-19T00:22:24+08:00",
|
||||
"content_hash": "d6cd1c87b887"
|
||||
},
|
||||
{
|
||||
"nonce": "tn8p7x",
|
||||
"agent_id": "AG-ZY",
|
||||
"timestamp": "2026-03-19T00:22:24+08:00",
|
||||
"content_hash": "0a5df5415ade"
|
||||
},
|
||||
{
|
||||
"nonce": "5fx7xl",
|
||||
"agent_id": "AG-ZY",
|
||||
"timestamp": "2026-03-19T00:22:24+08:00",
|
||||
"content_hash": "c3b54c6c243e"
|
||||
},
|
||||
{
|
||||
"nonce": "faym9x",
|
||||
"agent_id": "AG-ZY",
|
||||
"timestamp": "2026-03-19T00:22:24+08:00",
|
||||
"content_hash": "c3b54c6c243e"
|
||||
},
|
||||
{
|
||||
"nonce": "rvdfmy",
|
||||
"agent_id": "AG-ZY",
|
||||
"timestamp": "2026-03-19T00:22:24+08:00",
|
||||
"content_hash": "b95bfcf4a347"
|
||||
},
|
||||
{
|
||||
"nonce": "72pm2d",
|
||||
"agent_id": "AG-ZY",
|
||||
"timestamp": "2026-03-19T00:22:24+08:00",
|
||||
"content_hash": "10c4ec3aeafb"
|
||||
},
|
||||
{
|
||||
"nonce": "f194ff",
|
||||
"agent_id": "AG-ZY",
|
||||
"timestamp": "2026-03-19T00:22:24+08:00",
|
||||
"content_hash": "902e455fe109"
|
||||
},
|
||||
{
|
||||
"nonce": "kym6oy",
|
||||
"agent_id": "AG-ZY",
|
||||
"timestamp": "2026-03-19T00:22:24+08:00",
|
||||
"content_hash": "929460febd09"
|
||||
},
|
||||
{
|
||||
"nonce": "ekbzcz",
|
||||
"agent_id": "AG-SY",
|
||||
"timestamp": "2026-03-19T00:22:24+08:00",
|
||||
"content_hash": "f85462954167"
|
||||
},
|
||||
{
|
||||
"nonce": "wbjbs7",
|
||||
"agent_id": "AG-QQ",
|
||||
"timestamp": "2026-03-19T00:22:24+08:00",
|
||||
"content_hash": "c10beff06c0b"
|
||||
},
|
||||
{
|
||||
"nonce": "qktryb",
|
||||
"agent_id": "AG-TY",
|
||||
"timestamp": "2026-03-19T00:22:24+08:00",
|
||||
"content_hash": "7f4b1a99e8fc"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
// scripts/commander-dashboard.js
|
||||
// 铸渊·将军全局仪表盘生成器
|
||||
//
|
||||
// 功能:
|
||||
// ① 拉取所有 Workflow 运行状态
|
||||
// ② 读取天眼最新报告
|
||||
// ③ 读取公告板状态
|
||||
// ④ 生成将军全局仪表盘 JSON
|
||||
//
|
||||
// 输出:data/bulletin-board/dashboard.json + stdout
|
||||
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
const BRAIN_DIR = path.join(ROOT, '.github/persona-brain');
|
||||
const SKYEYE_REPORTS_DIR = path.join(ROOT, 'data/skyeye-reports');
|
||||
const BULLETIN_DIR = path.join(ROOT, 'data/bulletin-board');
|
||||
const DASHBOARD_PATH = path.join(BULLETIN_DIR, 'dashboard.json');
|
||||
|
||||
const BEIJING_OFFSET_MS = 8 * 3600 * 1000;
|
||||
|
||||
// ━━━ 安全读取 JSON ━━━
|
||||
function readJSON(filePath) {
|
||||
try {
|
||||
if (!fs.existsSync(filePath)) return null;
|
||||
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ━━━ 获取最新天眼报告 ━━━
|
||||
function getLatestSkyeyeReport() {
|
||||
try {
|
||||
if (!fs.existsSync(SKYEYE_REPORTS_DIR)) return null;
|
||||
const files = fs.readdirSync(SKYEYE_REPORTS_DIR)
|
||||
.filter(f => f.startsWith('skyeye-') && f.endsWith('.json'))
|
||||
.sort()
|
||||
.reverse();
|
||||
if (files.length === 0) return null;
|
||||
return readJSON(path.join(SKYEYE_REPORTS_DIR, files[0]));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ━━━ 获取公告板工单状态 ━━━
|
||||
function getBulletinBoardStatus() {
|
||||
const workOrdersDir = path.join(BULLETIN_DIR, 'work-orders');
|
||||
const receiptsDir = path.join(BULLETIN_DIR, 'receipts');
|
||||
|
||||
let unreadWorkOrders = 0;
|
||||
const pendingReceipts = [];
|
||||
|
||||
try {
|
||||
if (fs.existsSync(workOrdersDir)) {
|
||||
const orders = fs.readdirSync(workOrdersDir)
|
||||
.filter(f => f.endsWith('.json'));
|
||||
for (const orderFile of orders) {
|
||||
const order = readJSON(path.join(workOrdersDir, orderFile));
|
||||
if (order && order.status !== 'completed') {
|
||||
unreadWorkOrders++;
|
||||
pendingReceipts.push(order.order_id || orderFile.replace('.json', ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
return {
|
||||
unread_work_orders: unreadWorkOrders,
|
||||
pending_receipts: pendingReceipts
|
||||
};
|
||||
}
|
||||
|
||||
// ━━━ 从天眼报告提取小兵状态 ━━━
|
||||
function extractSoldiersFromReport(report) {
|
||||
const soldiers = [];
|
||||
|
||||
if (!report) return { soldiers, total: 0, healthy: 0, failed: 0, needs_optimization: 0 };
|
||||
|
||||
// 从 workflow_health 提取小兵
|
||||
if (report.workflow_health && report.workflow_health.details) {
|
||||
for (const wf of report.workflow_health.details) {
|
||||
const status = wf.status === 'healthy' ? '✅' :
|
||||
wf.status === 'failed' ? '❌' : '⚠️';
|
||||
const soldier = {
|
||||
name: wf.name || wf.file,
|
||||
status,
|
||||
last_run: wf.last_run || 'unknown'
|
||||
};
|
||||
if (status === '❌') {
|
||||
soldier.error = wf.error || 'unknown error';
|
||||
soldier.fix_plan = 'retry + check logs';
|
||||
}
|
||||
soldiers.push(soldier);
|
||||
}
|
||||
}
|
||||
|
||||
const total = soldiers.length;
|
||||
const healthy = soldiers.filter(s => s.status === '✅').length;
|
||||
const failed = soldiers.filter(s => s.status === '❌').length;
|
||||
const needsOpt = soldiers.filter(s => s.status === '⚠️').length;
|
||||
|
||||
return { soldiers, total, healthy, failed, needs_optimization: needsOpt };
|
||||
}
|
||||
|
||||
// ━━━ 生成决策摘要 ━━━
|
||||
function generateDecision(soldierStats, bulletinStatus, skyeyeReport) {
|
||||
const actions = [];
|
||||
|
||||
if (soldierStats.failed > 0) {
|
||||
actions.push(`修复 ${soldierStats.failed} 个故障小兵`);
|
||||
}
|
||||
if (soldierStats.needs_optimization > 0) {
|
||||
actions.push(`优化 ${soldierStats.needs_optimization} 个小兵`);
|
||||
}
|
||||
if (bulletinStatus.unread_work_orders > 0) {
|
||||
actions.push(`执行 ${bulletinStatus.unread_work_orders} 个未完成工单`);
|
||||
}
|
||||
if (skyeyeReport && skyeyeReport.diagnosis && skyeyeReport.diagnosis.needs_human > 0) {
|
||||
actions.push(`${skyeyeReport.diagnosis.needs_human} 个问题需人工处理`);
|
||||
}
|
||||
|
||||
if (actions.length === 0) {
|
||||
return '全局健康 · 无需干预 · 继续巡航';
|
||||
}
|
||||
return actions.join(' → ');
|
||||
}
|
||||
|
||||
// ━━━ 主函数 ━━━
|
||||
function generateDashboard() {
|
||||
const now = new Date();
|
||||
const bjTime = new Date(now.getTime() + BEIJING_OFFSET_MS).toISOString()
|
||||
.replace('T', ' ').slice(0, 19) + '+08:00';
|
||||
|
||||
// 读取天眼最新报告
|
||||
const skyeyeReport = getLatestSkyeyeReport();
|
||||
|
||||
// 提取小兵状态
|
||||
const soldierStats = extractSoldiersFromReport(skyeyeReport);
|
||||
|
||||
// 读取公告板状态
|
||||
const bulletinStatus = getBulletinBoardStatus();
|
||||
|
||||
// 生成决策
|
||||
const decision = generateDecision(soldierStats, bulletinStatus, skyeyeReport);
|
||||
|
||||
const dashboard = {
|
||||
commander: '铸渊',
|
||||
timestamp: bjTime,
|
||||
global_view: {
|
||||
total_soldiers: soldierStats.total,
|
||||
healthy: soldierStats.healthy,
|
||||
failed: soldierStats.failed,
|
||||
needs_optimization: soldierStats.needs_optimization,
|
||||
soldiers: soldierStats.soldiers
|
||||
},
|
||||
bulletin_board_status: bulletinStatus,
|
||||
skyeye_latest: skyeyeReport ? {
|
||||
overall_health: skyeyeReport.overall_health || '❓',
|
||||
report_id: skyeyeReport.report_id || null,
|
||||
issues: skyeyeReport.diagnosis ? skyeyeReport.diagnosis.total_issues : 0,
|
||||
auto_fixed: skyeyeReport.diagnosis ? skyeyeReport.diagnosis.auto_fixed : 0
|
||||
} : {
|
||||
overall_health: '❓',
|
||||
report_id: null,
|
||||
issues: 0,
|
||||
auto_fixed: 0
|
||||
},
|
||||
decision
|
||||
};
|
||||
|
||||
// 保存仪表盘
|
||||
fs.mkdirSync(BULLETIN_DIR, { recursive: true });
|
||||
fs.writeFileSync(DASHBOARD_PATH, JSON.stringify(dashboard, null, 2));
|
||||
|
||||
console.log(JSON.stringify(dashboard, null, 2));
|
||||
console.log(`\n📊 将军全局仪表盘已生成 · ${bjTime}`);
|
||||
}
|
||||
|
||||
// 允许作为模块导入或直接运行
|
||||
if (require.main === module) {
|
||||
generateDashboard();
|
||||
}
|
||||
|
||||
module.exports = { generateDashboard };
|
||||
|
|
@ -0,0 +1,257 @@
|
|||
// scripts/sfp-core.js
|
||||
// 系统指纹安全协议 · SFP v1.0 · 核心模块
|
||||
//
|
||||
// 功能:
|
||||
// ① generateSFP(agent_id, content) — 生成系统指纹
|
||||
// ② verifySFP(content_with_signature) — 验证系统指纹
|
||||
// ③ loadConfig() — 加载 SFP 配置
|
||||
//
|
||||
// 指纹格式: ⌜SFP::{agent_id}::{persona_chain}::{timestamp}::{content_hash}::{nonce}⌝
|
||||
|
||||
'use strict';
|
||||
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
const SFP_CONFIG_PATH = path.join(ROOT, 'data/security/sfp-config.json');
|
||||
const SFP_NONCE_PATH = path.join(ROOT, 'data/security/sfp-nonce-registry.json');
|
||||
const SFP_ALERT_PATH = path.join(ROOT, 'data/security/sfp-alert-log.json');
|
||||
|
||||
const BEIJING_OFFSET_MS = 8 * 3600 * 1000;
|
||||
|
||||
// ━━━ SFP 指纹正则 ━━━
|
||||
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 writeJSON(filePath, data) {
|
||||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
||||
}
|
||||
|
||||
// ━━━ 加载 SFP 配置 ━━━
|
||||
function loadConfig() {
|
||||
const config = readJSON(SFP_CONFIG_PATH);
|
||||
if (!config) {
|
||||
throw new Error('SFP配置文件缺失: ' + SFP_CONFIG_PATH);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
// ━━━ 查找受信Agent ━━━
|
||||
function findTrustedAgent(config, agentId) {
|
||||
if (!config || !config.trusted_agents) return null;
|
||||
return config.trusted_agents.find(a => a.agent_id === agentId) || null;
|
||||
}
|
||||
|
||||
// ━━━ 计算内容哈希(SHA-256前12位) ━━━
|
||||
function computeContentHash(content) {
|
||||
const hash = crypto.createHash('sha256').update(content, 'utf8').digest('hex');
|
||||
return hash.slice(0, 12);
|
||||
}
|
||||
|
||||
// ━━━ 生成6位随机nonce ━━━
|
||||
function generateNonce() {
|
||||
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let nonce = '';
|
||||
const bytes = crypto.randomBytes(6);
|
||||
for (let i = 0; i < 6; i++) {
|
||||
nonce += chars[bytes[i] % chars.length];
|
||||
}
|
||||
return nonce;
|
||||
}
|
||||
|
||||
// ━━━ 获取北京时间ISO字符串 ━━━
|
||||
function getBeijingTimestamp() {
|
||||
const now = new Date();
|
||||
const bjTime = new Date(now.getTime() + BEIJING_OFFSET_MS);
|
||||
return bjTime.toISOString().replace('Z', '+08:00').replace(/\.\d{3}/, '');
|
||||
}
|
||||
|
||||
// ━━━ 生成系统指纹 ━━━
|
||||
function generateSFP(agentId, content) {
|
||||
const config = loadConfig();
|
||||
const agent = findTrustedAgent(config, agentId);
|
||||
|
||||
if (!agent) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'AGENT_NOT_FOUND',
|
||||
message: `Agent ${agentId} 不在受信Agent列表中`
|
||||
};
|
||||
}
|
||||
|
||||
const timestamp = getBeijingTimestamp();
|
||||
const contentHash = computeContentHash(content);
|
||||
const nonce = generateNonce();
|
||||
|
||||
const fingerprint = `⌜SFP::${agent.agent_id}::${agent.persona_chain}::${timestamp}::${contentHash}::${nonce}⌝`;
|
||||
|
||||
// 记录 nonce 到已使用列表
|
||||
try {
|
||||
const nonceRegistry = readJSON(SFP_NONCE_PATH) || { used_nonces: [] };
|
||||
nonceRegistry.used_nonces.push({
|
||||
nonce,
|
||||
agent_id: agentId,
|
||||
timestamp,
|
||||
content_hash: contentHash
|
||||
});
|
||||
// 保留最近 1000 条 nonce 记录
|
||||
if (nonceRegistry.used_nonces.length > 1000) {
|
||||
nonceRegistry.used_nonces = nonceRegistry.used_nonces.slice(-1000);
|
||||
}
|
||||
writeJSON(SFP_NONCE_PATH, nonceRegistry);
|
||||
} catch (e) {
|
||||
// nonce 记录失败不阻断指纹生成
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
fingerprint,
|
||||
signed_content: `${content}\n${fingerprint}`,
|
||||
meta: {
|
||||
agent_id: agent.agent_id,
|
||||
persona_chain: agent.persona_chain,
|
||||
timestamp,
|
||||
content_hash: contentHash,
|
||||
nonce
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ━━━ 验证系统指纹 ━━━
|
||||
function verifySFP(contentWithSignature) {
|
||||
const config = loadConfig();
|
||||
|
||||
// Step 1: 检查是否有指纹块
|
||||
const match = contentWithSignature.match(SFP_REGEX);
|
||||
if (!match) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'NO_FINGERPRINT',
|
||||
level: '⚠️',
|
||||
message: '无指纹·不可信',
|
||||
log: `[SFP-WARN] 无指纹内容发现`
|
||||
};
|
||||
}
|
||||
|
||||
const [, agentId, personaChain, timestamp, contentHash, nonce] = match;
|
||||
|
||||
// Step 2: 验证 agent_id
|
||||
const agent = findTrustedAgent(config, agentId);
|
||||
if (!agent) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'INVALID_AGENT',
|
||||
level: '❌',
|
||||
message: `无效Agent ID: ${agentId}`,
|
||||
log: `[SFP-ALERT] 伪造Agent ID: ${agentId}`
|
||||
};
|
||||
}
|
||||
|
||||
// Step 3: 验证亲子链
|
||||
if (agent.persona_chain !== personaChain) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'CHAIN_MISMATCH',
|
||||
level: '❌',
|
||||
message: `亲子链伪造: 期望 ${agent.persona_chain},实际 ${personaChain}`,
|
||||
log: `[SFP-CRITICAL] 亲子链伪造企图 · agent=${agentId}`
|
||||
};
|
||||
}
|
||||
|
||||
// Step 4: 验证内容哈希
|
||||
// 从内容中去除指纹行,重新计算哈希
|
||||
const fingerprintLine = match[0];
|
||||
const originalContent = contentWithSignature
|
||||
.replace(fingerprintLine, '')
|
||||
.replace(/\n$/, '')
|
||||
.trim();
|
||||
const recomputedHash = computeContentHash(originalContent);
|
||||
|
||||
if (recomputedHash !== contentHash) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'CONTENT_TAMPERED',
|
||||
level: '❌',
|
||||
message: `内容被篡改: hash不匹配 (期望=${contentHash}, 实际=${recomputedHash})`,
|
||||
log: `[SFP-CRITICAL] 内容篡改检测 · hash不匹配`
|
||||
};
|
||||
}
|
||||
|
||||
// Step 5: 检查 nonce 重放
|
||||
const nonceRegistry = readJSON(SFP_NONCE_PATH) || { used_nonces: [] };
|
||||
const nonceUsed = nonceRegistry.used_nonces.some(
|
||||
n => n.nonce === nonce && n.agent_id !== agentId
|
||||
);
|
||||
// 注意:同一个 agent 的相同 nonce 是自己生成的,允许验证
|
||||
// 只有不同 agent 使用相同 nonce 才是重放攻击
|
||||
const duplicateNonce = nonceRegistry.used_nonces.filter(n => n.nonce === nonce);
|
||||
if (duplicateNonce.length > 1) {
|
||||
// 检查是否有不同 agent 使用了相同 nonce
|
||||
const uniqueAgents = new Set(duplicateNonce.map(n => n.agent_id));
|
||||
if (uniqueAgents.size > 1) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'REPLAY_ATTACK',
|
||||
level: '❌',
|
||||
message: `重放攻击: nonce ${nonce} 被多个Agent使用`,
|
||||
log: `[SFP-CRITICAL] 重放攻击 · nonce重复 · nonce=${nonce}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
level: '✅',
|
||||
message: '指纹验证通过',
|
||||
meta: {
|
||||
agent_id: agentId,
|
||||
persona_chain: personaChain,
|
||||
timestamp,
|
||||
content_hash: contentHash,
|
||||
nonce,
|
||||
agent_name: agent.name,
|
||||
agent_side: agent.side
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ━━━ 记录安全警报 ━━━
|
||||
function logAlert(alertEntry) {
|
||||
try {
|
||||
const alertLog = readJSON(SFP_ALERT_PATH) || { alerts: [] };
|
||||
alertLog.alerts.push({
|
||||
...alertEntry,
|
||||
logged_at: getBeijingTimestamp()
|
||||
});
|
||||
// 保留最近 500 条
|
||||
if (alertLog.alerts.length > 500) {
|
||||
alertLog.alerts = alertLog.alerts.slice(-500);
|
||||
}
|
||||
writeJSON(SFP_ALERT_PATH, alertLog);
|
||||
} catch (e) {
|
||||
console.error('[SFP] 写入警报日志失败:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generateSFP,
|
||||
verifySFP,
|
||||
loadConfig,
|
||||
findTrustedAgent,
|
||||
computeContentHash,
|
||||
generateNonce,
|
||||
logAlert,
|
||||
SFP_REGEX
|
||||
};
|
||||
|
|
@ -40,6 +40,8 @@ function generateReport() {
|
|||
const brainHealth = readJSON(path.join(SKYEYE_DIR, 'brain-health.json'));
|
||||
const bridgeHealth = readJSON(path.join(SKYEYE_DIR, 'bridge-health.json'));
|
||||
const securityHealth = readJSON(path.join(SKYEYE_DIR, 'security-health.json'));
|
||||
const sfpHealth = readJSON(path.join(SKYEYE_DIR, 'sfp-health.json'));
|
||||
const soldierHealth = readJSON(path.join(SKYEYE_DIR, 'soldier-health.json'));
|
||||
const diagnosis = readJSON(path.join(SKYEYE_DIR, 'diagnosis.json'));
|
||||
const repairResult = readJSON(path.join(SKYEYE_DIR, 'repair-result.json'));
|
||||
|
||||
|
|
@ -111,6 +113,28 @@ function generateReport() {
|
|||
last_verified: bjTime
|
||||
},
|
||||
|
||||
bulletin_sfp_health: sfpHealth ? {
|
||||
dimension: 'D14',
|
||||
status: sfpHealth.status || '❓',
|
||||
total_messages: sfpHealth.total_messages || 0,
|
||||
valid_fingerprints: sfpHealth.valid_fingerprints || 0,
|
||||
invalid_fingerprints: sfpHealth.invalid_fingerprints || 0,
|
||||
without_fingerprint: sfpHealth.without_fingerprint || 0,
|
||||
sfp_config_exists: sfpHealth.sfp_config_exists || false,
|
||||
agent_activity: sfpHealth.agent_activity || {}
|
||||
} : { dimension: 'D14', status: '❓' },
|
||||
|
||||
soldier_health: soldierHealth ? {
|
||||
dimension: 'D15',
|
||||
status: soldierHealth.status || '❓',
|
||||
total_soldiers: soldierHealth.total_soldiers || 0,
|
||||
healthy: soldierHealth.healthy || 0,
|
||||
failed: soldierHealth.failed || 0,
|
||||
failure_rate: soldierHealth.failure_rate || 0,
|
||||
recurring_errors: soldierHealth.recurring_errors || [],
|
||||
recommendations: soldierHealth.recommendations || []
|
||||
} : { dimension: 'D15', status: '❓' },
|
||||
|
||||
diagnosis: {
|
||||
total_issues: diagnosis ? diagnosis.total_issues : 0,
|
||||
auto_fixed: repairResult ? repairResult.total_repaired : 0,
|
||||
|
|
@ -164,6 +188,16 @@ function generateReport() {
|
|||
} else if (!report.security_health.copyright_anchor) {
|
||||
report.next_actions.push('P1: 安全协议版权锚点缺失,需补充');
|
||||
}
|
||||
if (report.bulletin_sfp_health && report.bulletin_sfp_health.invalid_fingerprints > 0) {
|
||||
report.next_actions.push('P0: 公告板发现无效指纹内容,需立即清理');
|
||||
}
|
||||
if (report.soldier_health && report.soldier_health.failure_rate > 20) {
|
||||
report.next_actions.push('P0: 小兵故障率超过20%,需全局修复');
|
||||
}
|
||||
if (report.soldier_health && report.soldier_health.recurring_errors &&
|
||||
report.soldier_health.recurring_errors.length > 0) {
|
||||
report.next_actions.push('P1: 存在持续性故障小兵,建议铸渊介入');
|
||||
}
|
||||
|
||||
// 保存完整报告
|
||||
fs.mkdirSync(SKYEYE_DIR, { recursive: true });
|
||||
|
|
|
|||
|
|
@ -0,0 +1,163 @@
|
|||
// 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();
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
// scripts/skyeye/scan-soldier-health.js
|
||||
// 天眼·扫描模块D15 · 小兵健康全局视图
|
||||
//
|
||||
// 扫描内容:
|
||||
// ① 读取铸渊将军仪表盘 commander-dashboard.json
|
||||
// ② 小兵故障率 > 20% → P0 工单
|
||||
// ③ 小兵连续7天同一错误未修复 → P1 工单
|
||||
// ④ 全局小兵健康统计
|
||||
//
|
||||
// 输出:JSON → stdout
|
||||
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '../..');
|
||||
const DASHBOARD_PATH = path.join(ROOT, 'data/bulletin-board/dashboard.json');
|
||||
const SKYEYE_REPORTS_DIR = path.join(ROOT, 'data/skyeye-reports');
|
||||
|
||||
// ━━━ 安全读取 JSON ━━━
|
||||
function readJSON(filePath) {
|
||||
try {
|
||||
if (!fs.existsSync(filePath)) return null;
|
||||
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ━━━ 获取最近N天的天眼报告 ━━━
|
||||
function getRecentReports(days) {
|
||||
const reports = [];
|
||||
try {
|
||||
if (!fs.existsSync(SKYEYE_REPORTS_DIR)) return reports;
|
||||
const files = fs.readdirSync(SKYEYE_REPORTS_DIR)
|
||||
.filter(f => f.startsWith('skyeye-') && f.endsWith('.json'))
|
||||
.sort()
|
||||
.reverse()
|
||||
.slice(0, days);
|
||||
|
||||
for (const file of files) {
|
||||
const report = readJSON(path.join(SKYEYE_REPORTS_DIR, file));
|
||||
if (report) reports.push(report);
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
return reports;
|
||||
}
|
||||
|
||||
// ━━━ D15 主扫描 ━━━
|
||||
function scanSoldierHealth() {
|
||||
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 result = {
|
||||
dimension: 'D15',
|
||||
name: '小兵健康全局视图',
|
||||
scan_time: bjTime,
|
||||
status: '✅',
|
||||
dashboard_exists: false,
|
||||
total_soldiers: 0,
|
||||
healthy: 0,
|
||||
failed: 0,
|
||||
needs_optimization: 0,
|
||||
failure_rate: 0,
|
||||
issues: [],
|
||||
recurring_errors: [],
|
||||
recommendations: []
|
||||
};
|
||||
|
||||
// 读取将军仪表盘
|
||||
const dashboard = readJSON(DASHBOARD_PATH);
|
||||
if (!dashboard) {
|
||||
result.dashboard_exists = false;
|
||||
result.status = '⚠️';
|
||||
result.issues.push({
|
||||
type: 'dashboard_missing',
|
||||
message: '将军仪表盘不存在 · 需先运行 commander-dashboard.js',
|
||||
action: 'P1工单 · 生成仪表盘'
|
||||
});
|
||||
} else {
|
||||
result.dashboard_exists = true;
|
||||
const gv = dashboard.global_view || {};
|
||||
result.total_soldiers = gv.total_soldiers || 0;
|
||||
result.healthy = gv.healthy || 0;
|
||||
result.failed = gv.failed || 0;
|
||||
result.needs_optimization = gv.needs_optimization || 0;
|
||||
|
||||
// 计算故障率
|
||||
if (result.total_soldiers > 0) {
|
||||
result.failure_rate = Math.round((result.failed / result.total_soldiers) * 100);
|
||||
}
|
||||
|
||||
// 故障率 > 20% → P0
|
||||
if (result.failure_rate > 20) {
|
||||
result.status = '❌';
|
||||
result.issues.push({
|
||||
type: 'high_failure_rate',
|
||||
message: `小兵故障率 ${result.failure_rate}% > 20%`,
|
||||
action: 'P0工单 · 全局小兵修复'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 检查连续7天同一错误
|
||||
const recentReports = getRecentReports(7);
|
||||
if (recentReports.length >= 2) {
|
||||
// 收集各报告中的失败 workflow
|
||||
const errorMap = {};
|
||||
for (const report of recentReports) {
|
||||
if (report.workflow_health && report.workflow_health.details) {
|
||||
for (const wf of report.workflow_health.details) {
|
||||
if (wf.status === 'failed') {
|
||||
const key = wf.name || wf.file;
|
||||
errorMap[key] = (errorMap[key] || 0) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 连续7天同一错误
|
||||
for (const [name, count] of Object.entries(errorMap)) {
|
||||
if (count >= 7) {
|
||||
result.recurring_errors.push({
|
||||
soldier: name,
|
||||
consecutive_failures: count,
|
||||
action: 'P1工单 · 建议铸渊介入'
|
||||
});
|
||||
if (result.status === '✅') result.status = '⚠️';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 生成建议
|
||||
if (result.failed > 0) {
|
||||
result.recommendations.push(`修复 ${result.failed} 个故障小兵`);
|
||||
}
|
||||
if (result.recurring_errors.length > 0) {
|
||||
result.recommendations.push(`${result.recurring_errors.length} 个小兵存在持续性故障,需铸渊介入`);
|
||||
}
|
||||
if (result.total_soldiers === 0) {
|
||||
result.recommendations.push('仪表盘无小兵数据 · 需先生成仪表盘');
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
return result;
|
||||
}
|
||||
|
||||
scanSoldierHealth();
|
||||
|
|
@ -72,6 +72,8 @@ function main() {
|
|||
results.brain = runModule('scan-brain-health.js', 'brain-health.json');
|
||||
results.bridges = runModule('scan-external-bridges.js', 'bridge-health.json');
|
||||
results.security = runModule('scan-security-protocol.js', 'security-health.json');
|
||||
results.sfp = runModule('scan-bulletin-sfp.js', 'sfp-health.json');
|
||||
results.soldiers = runModule('scan-soldier-health.js', 'soldier-health.json');
|
||||
|
||||
// Phase 3: 诊断
|
||||
console.log('\n🔬 Phase 3 · 诊断');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,155 @@
|
|||
// tests/contract/sfp-core.test.js
|
||||
// SFP v1.0 · 系统指纹安全协议 · 契约测试
|
||||
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
|
||||
// ━━━ 加载 SFP 核心模块 ━━━
|
||||
const sfpCore = require(path.resolve(__dirname, '../../scripts/sfp-core.js'));
|
||||
|
||||
describe('SFP v1.0 · 系统指纹安全协议', () => {
|
||||
|
||||
describe('配置加载', () => {
|
||||
test('loadConfig() 应成功加载配置', () => {
|
||||
const config = sfpCore.loadConfig();
|
||||
expect(config).toBeDefined();
|
||||
expect(config.protocol_version).toBe('SFP-v1.0');
|
||||
expect(config.trusted_agents).toBeInstanceOf(Array);
|
||||
expect(config.trusted_agents.length).toBeGreaterThanOrEqual(4);
|
||||
});
|
||||
|
||||
test('受信Agent列表包含 AG-ZY(铸渊)', () => {
|
||||
const config = sfpCore.loadConfig();
|
||||
const zy = config.trusted_agents.find(a => a.agent_id === 'AG-ZY');
|
||||
expect(zy).toBeDefined();
|
||||
expect(zy.name).toBe('铸渊');
|
||||
expect(zy.side).toBe('github');
|
||||
});
|
||||
|
||||
test('受信Agent列表包含 AG-SY(霜砚)', () => {
|
||||
const config = sfpCore.loadConfig();
|
||||
const sy = config.trusted_agents.find(a => a.agent_id === 'AG-SY');
|
||||
expect(sy).toBeDefined();
|
||||
expect(sy.name).toBe('霜砚');
|
||||
expect(sy.persona_chain).toBe('PER-SY001←TCS-0002∞');
|
||||
});
|
||||
});
|
||||
|
||||
describe('指纹生成 · generateSFP()', () => {
|
||||
test('已知Agent生成指纹成功', () => {
|
||||
const result = sfpCore.generateSFP('AG-ZY', '测试内容');
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.fingerprint).toMatch(/^⌜SFP::/);
|
||||
expect(result.fingerprint).toMatch(/⌝$/);
|
||||
expect(result.meta.agent_id).toBe('AG-ZY');
|
||||
expect(result.meta.content_hash).toHaveLength(12);
|
||||
expect(result.meta.nonce).toHaveLength(6);
|
||||
});
|
||||
|
||||
test('未知Agent生成指纹失败', () => {
|
||||
const result = sfpCore.generateSFP('AG-FAKE', '测试内容');
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('AGENT_NOT_FOUND');
|
||||
});
|
||||
|
||||
test('不同内容产生不同哈希', () => {
|
||||
const r1 = sfpCore.generateSFP('AG-ZY', '内容A');
|
||||
const r2 = sfpCore.generateSFP('AG-ZY', '内容B');
|
||||
expect(r1.success).toBe(true);
|
||||
expect(r2.success).toBe(true);
|
||||
expect(r1.meta.content_hash).not.toBe(r2.meta.content_hash);
|
||||
});
|
||||
|
||||
test('每次生成不同的nonce', () => {
|
||||
const r1 = sfpCore.generateSFP('AG-ZY', '相同内容');
|
||||
const r2 = sfpCore.generateSFP('AG-ZY', '相同内容');
|
||||
expect(r1.meta.nonce).not.toBe(r2.meta.nonce);
|
||||
});
|
||||
|
||||
test('signed_content 包含原始内容和指纹', () => {
|
||||
const content = '铸渊留言测试';
|
||||
const result = sfpCore.generateSFP('AG-ZY', content);
|
||||
expect(result.signed_content).toContain(content);
|
||||
expect(result.signed_content).toContain(result.fingerprint);
|
||||
});
|
||||
});
|
||||
|
||||
describe('指纹验证 · verifySFP()', () => {
|
||||
test('正确指纹验证通过', () => {
|
||||
const gen = sfpCore.generateSFP('AG-ZY', '正确内容测试');
|
||||
const verify = sfpCore.verifySFP(gen.signed_content);
|
||||
expect(verify.valid).toBe(true);
|
||||
expect(verify.level).toBe('✅');
|
||||
expect(verify.meta.agent_name).toBe('铸渊');
|
||||
});
|
||||
|
||||
test('无指纹内容 → NO_FINGERPRINT', () => {
|
||||
const verify = sfpCore.verifySFP('纯文本没有指纹');
|
||||
expect(verify.valid).toBe(false);
|
||||
expect(verify.error).toBe('NO_FINGERPRINT');
|
||||
expect(verify.level).toBe('⚠️');
|
||||
});
|
||||
|
||||
test('内容被篡改 → CONTENT_TAMPERED', () => {
|
||||
const gen = sfpCore.generateSFP('AG-ZY', '原始内容');
|
||||
const tampered = gen.signed_content.replace('原始内容', '篡改内容');
|
||||
const verify = sfpCore.verifySFP(tampered);
|
||||
expect(verify.valid).toBe(false);
|
||||
expect(verify.error).toBe('CONTENT_TAMPERED');
|
||||
expect(verify.level).toBe('❌');
|
||||
});
|
||||
|
||||
test('伪造Agent ID → INVALID_AGENT', () => {
|
||||
// 手动构造一个伪造的指纹
|
||||
const fakeContent = '伪造内容\n⌜SFP::AG-FAKE::FAKE-CHAIN::2026-03-19T00:00:00+08:00::abcdef123456::x7k9m2⌝';
|
||||
const verify = sfpCore.verifySFP(fakeContent);
|
||||
expect(verify.valid).toBe(false);
|
||||
expect(verify.error).toBe('INVALID_AGENT');
|
||||
expect(verify.level).toBe('❌');
|
||||
});
|
||||
|
||||
test('亲子链不匹配 → CHAIN_MISMATCH', () => {
|
||||
// 使用正确的 agent_id 但错误的 persona_chain
|
||||
const hash = sfpCore.computeContentHash('伪造亲子链');
|
||||
const fakeContent = `伪造亲子链\n⌜SFP::AG-ZY::FAKE-CHAIN::2026-03-19T00:00:00+08:00::${hash}::x7k9m2⌝`;
|
||||
const verify = sfpCore.verifySFP(fakeContent);
|
||||
expect(verify.valid).toBe(false);
|
||||
expect(verify.error).toBe('CHAIN_MISMATCH');
|
||||
expect(verify.level).toBe('❌');
|
||||
});
|
||||
|
||||
test('所有4个Agent都能生成和验证', () => {
|
||||
const agents = ['AG-ZY', 'AG-SY', 'AG-QQ', 'AG-TY'];
|
||||
for (const agentId of agents) {
|
||||
const gen = sfpCore.generateSFP(agentId, `${agentId} 测试`);
|
||||
expect(gen.success).toBe(true);
|
||||
const verify = sfpCore.verifySFP(gen.signed_content);
|
||||
expect(verify.valid).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('工具函数', () => {
|
||||
test('computeContentHash 返回12位hex', () => {
|
||||
const hash = sfpCore.computeContentHash('测试内容');
|
||||
expect(hash).toHaveLength(12);
|
||||
expect(hash).toMatch(/^[a-f0-9]{12}$/);
|
||||
});
|
||||
|
||||
test('generateNonce 返回6位字母数字', () => {
|
||||
const nonce = sfpCore.generateNonce();
|
||||
expect(nonce).toHaveLength(6);
|
||||
expect(nonce).toMatch(/^[a-z0-9]{6}$/);
|
||||
});
|
||||
|
||||
test('SFP_REGEX 能正确匹配指纹格式', () => {
|
||||
const fp = '⌜SFP::AG-ZY::PER-ZY001←YM-GL∞::2026-03-19T00:00:00+08:00::abcdef123456::x7k9m2⌝';
|
||||
const match = fp.match(sfpCore.SFP_REGEX);
|
||||
expect(match).not.toBeNull();
|
||||
expect(match[1]).toBe('AG-ZY');
|
||||
expect(match[4]).toBe('abcdef123456');
|
||||
expect(match[5]).toBe('x7k9m2');
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue