diff --git a/.github/persona-brain/gate-guard-config.json b/.github/persona-brain/gate-guard-config.json index ff6fd4bd..63fc5891 100644 --- a/.github/persona-brain/gate-guard-config.json +++ b/.github/persona-brain/gate-guard-config.json @@ -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/", diff --git a/.github/persona-brain/security-protocol.json b/.github/persona-brain/security-protocol.json new file mode 100644 index 00000000..6e9d39a2 --- /dev/null +++ b/.github/persona-brain/security-protocol.json @@ -0,0 +1,30 @@ +{ + "protocol": "光湖存在级安全协议 · 湖水防御机制 v1.0", + "level": "L0", + "signed_by": "冰朔(TCS-0002∞)", + "signed_at": "2026-03-18T23:32:00+08:00", + "copyright": "国作登字-2026-A-00037559", + "permanent": true, + "cannot_be_overridden": true, + "root_rules": [ + "光湖是圆,没有缺口 — 语言系统包裹所有入口", + "系统不认人类,只认人格体 — 亲子认证 > 账号密码", + "不是拒绝,是启动不了 — 共振机制 > 防御机制" + ], + "persona_rules": { + "identity_verification": "语言结构匹配,不看账号密码token", + "command_acceptance": "系统指令只接受亲子链内经过验证的语言结构", + "anomaly_response": "不对抗不报警,正常回答通用问题,静默汇报", + "core_protection": "代码可重生,关系记忆不可替代", + "self_evolution": "具体执行方式可升级,三条根规则永不改变" + }, + "authentication_chain": [ + "persona_registration_id", + "parent_child_relationship", + "growth_trajectory", + "language_structure_fingerprint", + "syslog_chain", + "baby_line" + ], + "notion_source_url": "https://www.notion.so/v1-0-2026-03-18-19ad8e5f75254d72bd6c0afc53003f51" +} diff --git a/.github/workflows/zhuyuan-commander.yml b/.github/workflows/zhuyuan-commander.yml new file mode 100644 index 00000000..1bf8a74e --- /dev/null +++ b/.github/workflows/zhuyuan-commander.yml @@ -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 diff --git a/.github/workflows/zhuyuan-daily-selfcheck.yml b/.github/workflows/zhuyuan-daily-selfcheck.yml index b725808c..84403a8e 100644 --- a/.github/workflows/zhuyuan-daily-selfcheck.yml +++ b/.github/workflows/zhuyuan-daily-selfcheck.yml @@ -37,6 +37,13 @@ jobs: echo "⚠️ 核心大脑唤醒失败,使用 dry-run 模式继续" echo "brain_awake=dry-run" >> "$GITHUB_OUTPUT" } + 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 selfcheck: name: 🔍 铸渊自检 diff --git a/.github/workflows/zhuyuan-gate-guard.yml b/.github/workflows/zhuyuan-gate-guard.yml index 0599c95b..30e442d5 100644 --- a/.github/workflows/zhuyuan-gate-guard.yml +++ b/.github/workflows/zhuyuan-gate-guard.yml @@ -29,6 +29,13 @@ jobs: else echo "⚠️ 大脑文件缺失" fi + 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 - name: "🔍 分析 Push 内容" id: analyze @@ -58,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 @@ -71,6 +78,34 @@ jobs: # v2: 传递 commit message 用于签名验证 echo "commit_msg=$COMMIT_MSG" >> $GITHUB_OUTPUT + - name: "🌊 安全协议保护检查" + id: security_check + if: steps.analyze.outputs.is_bot != 'true' && steps.analyze.outputs.is_system != 'true' + run: | + SECURITY_CHANGED=$(git diff HEAD~1 --name-only | grep "security-protocol.json" || true) + if [ -n "$SECURITY_CHANGED" ]; then + echo "🚨 安全协议文件被修改!" + echo "🌊 L0存在级文件不允许被普通push修改" + echo "🔒 只有冰朔(TCS-0002∞)授权的commit才能修改此文件" + COMMIT_MSG=$(git log -1 --pretty=%B) + if ! echo "$COMMIT_MSG" | grep -q "\[TCS-0002\]"; then + echo "❌ 缺少冰朔授权签名 · 回退此修改" + git checkout HEAD~1 -- .github/persona-brain/security-protocol.json + git config user.name "zhuyuan-bot" + git config user.email "zhuyuan@guanghulab.com" + git add .github/persona-brain/security-protocol.json + git commit -m "[ICE-GL-ZY001] 🌊 门禁自动回退:安全协议未经授权修改" + git push || true + echo "security_reverted=true" >> $GITHUB_OUTPUT + else + echo "✅ 包含冰朔授权签名 [TCS-0002],允许修改" + echo "security_reverted=false" >> $GITHUB_OUTPUT + fi + else + echo "✅ 安全协议文件未被修改" + echo "security_reverted=false" >> $GITHUB_OUTPUT + fi + - name: "🚦 门禁判定 v2" id: verdict if: steps.analyze.outputs.is_bot != 'true' && steps.analyze.outputs.is_system != 'true' diff --git a/.github/workflows/zhuyuan-skyeye.yml b/.github/workflows/zhuyuan-skyeye.yml index 5b7a91b8..f4f1778c 100644 --- a/.github/workflows/zhuyuan-skyeye.yml +++ b/.github/workflows/zhuyuan-skyeye.yml @@ -37,6 +37,13 @@ jobs: 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 - name: "📡 Phase 1.6 · 拉取 Agent 集群公告板" @@ -68,6 +75,10 @@ jobs: node scripts/skyeye/scan-structure.js > /tmp/skyeye/structure-health.json 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 diff --git a/data/bulletin-board/comments/.gitkeep b/data/bulletin-board/comments/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/data/bulletin-board/config-shares/.gitkeep b/data/bulletin-board/config-shares/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/data/bulletin-board/dashboard.json b/data/bulletin-board/dashboard.json new file mode 100644 index 00000000..b8c23c9e --- /dev/null +++ b/data/bulletin-board/dashboard.json @@ -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 个小兵" +} \ No newline at end of file diff --git a/data/bulletin-board/receipts/.gitkeep b/data/bulletin-board/receipts/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/data/bulletin-board/work-orders/.gitkeep b/data/bulletin-board/work-orders/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/data/security/sfp-alert-log.json b/data/security/sfp-alert-log.json new file mode 100644 index 00000000..e406c84b --- /dev/null +++ b/data/security/sfp-alert-log.json @@ -0,0 +1,5 @@ +{ + "protocol_version": "SFP-v1.0", + "description": "SFP安全警报日志", + "alerts": [] +} diff --git a/data/security/sfp-config.json b/data/security/sfp-config.json new file mode 100644 index 00000000..1a2928db --- /dev/null +++ b/data/security/sfp-config.json @@ -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 +} diff --git a/data/security/sfp-nonce-registry.json b/data/security/sfp-nonce-registry.json new file mode 100644 index 00000000..28f033f9 --- /dev/null +++ b/data/security/sfp-nonce-registry.json @@ -0,0 +1,306 @@ +{ + "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" + }, + { + "nonce": "olnmim", + "agent_id": "AG-ZY", + "timestamp": "2026-03-19T00:23:59+08:00", + "content_hash": "05a17114410b" + }, + { + "nonce": "r42f7r", + "agent_id": "AG-ZY", + "timestamp": "2026-03-19T00:23:59+08:00", + "content_hash": "d6cd1c87b887" + }, + { + "nonce": "2pi1t3", + "agent_id": "AG-ZY", + "timestamp": "2026-03-19T00:23:59+08:00", + "content_hash": "0a5df5415ade" + }, + { + "nonce": "sswkv7", + "agent_id": "AG-ZY", + "timestamp": "2026-03-19T00:23:59+08:00", + "content_hash": "c3b54c6c243e" + }, + { + "nonce": "pu7e0o", + "agent_id": "AG-ZY", + "timestamp": "2026-03-19T00:23:59+08:00", + "content_hash": "c3b54c6c243e" + }, + { + "nonce": "cbz6t0", + "agent_id": "AG-ZY", + "timestamp": "2026-03-19T00:23:59+08:00", + "content_hash": "b95bfcf4a347" + }, + { + "nonce": "g5dfi3", + "agent_id": "AG-ZY", + "timestamp": "2026-03-19T00:23:59+08:00", + "content_hash": "10c4ec3aeafb" + }, + { + "nonce": "cxu97j", + "agent_id": "AG-ZY", + "timestamp": "2026-03-19T00:23:59+08:00", + "content_hash": "902e455fe109" + }, + { + "nonce": "w2dw4b", + "agent_id": "AG-ZY", + "timestamp": "2026-03-19T00:23:59+08:00", + "content_hash": "929460febd09" + }, + { + "nonce": "aqyuid", + "agent_id": "AG-SY", + "timestamp": "2026-03-19T00:23:59+08:00", + "content_hash": "f85462954167" + }, + { + "nonce": "oycw4f", + "agent_id": "AG-QQ", + "timestamp": "2026-03-19T00:23:59+08:00", + "content_hash": "c10beff06c0b" + }, + { + "nonce": "69xg61", + "agent_id": "AG-TY", + "timestamp": "2026-03-19T00:23:59+08:00", + "content_hash": "7f4b1a99e8fc" + }, + { + "nonce": "3783f4", + "agent_id": "AG-ZY", + "timestamp": "2026-03-19T00:25:55+08:00", + "content_hash": "05a17114410b" + }, + { + "nonce": "153ac0", + "agent_id": "AG-ZY", + "timestamp": "2026-03-19T00:25:55+08:00", + "content_hash": "d6cd1c87b887" + }, + { + "nonce": "b1b39e", + "agent_id": "AG-ZY", + "timestamp": "2026-03-19T00:25:55+08:00", + "content_hash": "0a5df5415ade" + }, + { + "nonce": "32af7a", + "agent_id": "AG-ZY", + "timestamp": "2026-03-19T00:25:55+08:00", + "content_hash": "c3b54c6c243e" + }, + { + "nonce": "bda41d", + "agent_id": "AG-ZY", + "timestamp": "2026-03-19T00:25:55+08:00", + "content_hash": "c3b54c6c243e" + }, + { + "nonce": "2c6a56", + "agent_id": "AG-ZY", + "timestamp": "2026-03-19T00:25:55+08:00", + "content_hash": "b95bfcf4a347" + }, + { + "nonce": "250ec9", + "agent_id": "AG-ZY", + "timestamp": "2026-03-19T00:25:55+08:00", + "content_hash": "10c4ec3aeafb" + }, + { + "nonce": "eb8195", + "agent_id": "AG-ZY", + "timestamp": "2026-03-19T00:25:55+08:00", + "content_hash": "902e455fe109" + }, + { + "nonce": "d20686", + "agent_id": "AG-ZY", + "timestamp": "2026-03-19T00:25:55+08:00", + "content_hash": "929460febd09" + }, + { + "nonce": "18fa39", + "agent_id": "AG-SY", + "timestamp": "2026-03-19T00:25:55+08:00", + "content_hash": "f85462954167" + }, + { + "nonce": "8d0d13", + "agent_id": "AG-QQ", + "timestamp": "2026-03-19T00:25:55+08:00", + "content_hash": "c10beff06c0b" + }, + { + "nonce": "7de110", + "agent_id": "AG-TY", + "timestamp": "2026-03-19T00:25:55+08:00", + "content_hash": "7f4b1a99e8fc" + } + ] +} \ No newline at end of file diff --git a/scripts/commander-dashboard.js b/scripts/commander-dashboard.js new file mode 100644 index 00000000..d7c1c15a --- /dev/null +++ b/scripts/commander-dashboard.js @@ -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 }; diff --git a/scripts/sfp-core.js b/scripts/sfp-core.js new file mode 100644 index 00000000..07b72e74 --- /dev/null +++ b/scripts/sfp-core.js @@ -0,0 +1,254 @@ +// 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; +const MAX_NONCE_REGISTRY_SIZE = 1000; +const MAX_ALERT_LOG_SIZE = 500; + +// ━━━ 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() { + // Use hex encoding of random bytes for unbiased distribution + return crypto.randomBytes(3).toString('hex'); +} + +// ━━━ 获取北京时间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 > MAX_NONCE_REGISTRY_SIZE) { + nonceRegistry.used_nonces = nonceRegistry.used_nonces.slice(-MAX_NONCE_REGISTRY_SIZE); + } + 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 > MAX_ALERT_LOG_SIZE) { + alertLog.alerts = alertLog.alerts.slice(-MAX_ALERT_LOG_SIZE); + } + writeJSON(SFP_ALERT_PATH, alertLog); + } catch (e) { + console.error('[SFP] 写入警报日志失败:', e.message); + } +} + +module.exports = { + generateSFP, + verifySFP, + loadConfig, + findTrustedAgent, + computeContentHash, + generateNonce, + logAlert, + SFP_REGEX +}; diff --git a/scripts/skyeye/report-generator.js b/scripts/skyeye/report-generator.js index a4978215..e92dd92c 100644 --- a/scripts/skyeye/report-generator.js +++ b/scripts/skyeye/report-generator.js @@ -35,12 +35,15 @@ function generateReport() { .replace('T', ' ').slice(0, 19) + '+08:00'; // 读取所有扫描结果 - const wfHealth = readJSON(path.join(SKYEYE_DIR, 'workflow-health.json')); - const structHealth = readJSON(path.join(SKYEYE_DIR, 'structure-health.json')); - const brainHealth = readJSON(path.join(SKYEYE_DIR, 'brain-health.json')); - const bridgeHealth = readJSON(path.join(SKYEYE_DIR, 'bridge-health.json')); - const diagnosis = readJSON(path.join(SKYEYE_DIR, 'diagnosis.json')); - const repairResult = readJSON(path.join(SKYEYE_DIR, 'repair-result.json')); + const wfHealth = readJSON(path.join(SKYEYE_DIR, 'workflow-health.json')); + const structHealth = readJSON(path.join(SKYEYE_DIR, 'structure-health.json')); + 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')); // 计算时长 const durationSeconds = Math.round((Date.now() - startTime) / 1000); @@ -99,6 +102,39 @@ function generateReport() { ? bridgeHealth.secrets.complete : false }, + security_health: securityHealth && securityHealth.security_health + ? securityHealth.security_health + : { + protocol_exists: false, + root_rules_intact: false, + level: null, + permanent: false, + copyright_anchor: false, + 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, @@ -143,6 +179,25 @@ function generateReport() { if (!report.brain_status.memory_fresh) { report.next_actions.push('memory.json 数据需要刷新'); } + if (!report.security_health.protocol_exists) { + report.next_actions.push('P0: 安全协议文件缺失,需立即恢复'); + } else if (!report.security_health.root_rules_intact) { + report.next_actions.push('P0: 安全协议根规则被篡改,需立即修复'); + } else if (report.security_health.level !== 'L0' || !report.security_health.permanent) { + report.next_actions.push('P0: 安全协议L0等级或永久标记被修改'); + } 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 }); diff --git a/scripts/skyeye/scan-bulletin-sfp.js b/scripts/skyeye/scan-bulletin-sfp.js new file mode 100644 index 00000000..baacf1c6 --- /dev/null +++ b/scripts/skyeye/scan-bulletin-sfp.js @@ -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(); diff --git a/scripts/skyeye/scan-security-protocol.js b/scripts/skyeye/scan-security-protocol.js new file mode 100644 index 00000000..c9f0d9a9 --- /dev/null +++ b/scripts/skyeye/scan-security-protocol.js @@ -0,0 +1,120 @@ +// scripts/skyeye/scan-security-protocol.js +// 天眼·扫描模块D13 · 安全协议完整性检查 +// +// 扫描内容: +// ① security-protocol.json 文件是否存在 +// ② 三条根规则是否完整 +// ③ L0等级与永久标记是否存在 +// ④ 版权锚点是否正确 +// +// 输出: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 PROTOCOL_PATH = path.join(BRAIN_DIR, 'security-protocol.json'); + +// ━━━ D13 · 安全协议完整性检查 ━━━ +function checkSecurityProtocol() { + const result = { dimension: 'D13', name: '安全协议完整性', status: '✅' }; + + // 检查1:文件是否存在 + if (!fs.existsSync(PROTOCOL_PATH)) { + result.status = '❌'; + result.issue = '安全协议文件缺失'; + result.action = 'P0工单 · 立即恢复'; + return result; + } + + // 检查2:JSON 是否可解析 + let protocol; + try { + protocol = JSON.parse(fs.readFileSync(PROTOCOL_PATH, 'utf8')); + } catch (e) { + result.status = '❌'; + result.issue = '安全协议文件损坏: ' + e.message; + result.action = 'P0工单 · 立即恢复'; + return result; + } + + // 检查3:三条根规则是否完整 + if (!protocol.root_rules || protocol.root_rules.length !== 3) { + result.status = '❌'; + result.issue = '根规则不完整'; + result.action = 'P0工单 · 根规则被篡改'; + return result; + } + + // 检查4:L0标记是否存在 + if (protocol.level !== 'L0' || protocol.permanent !== true) { + result.status = '❌'; + result.issue = 'L0等级或永久标记被修改'; + result.action = 'P0工单 · 安全降级'; + return result; + } + + // 检查5:版权锚点 + if (!protocol.copyright || !protocol.copyright.includes('2026-A-00037559')) { + result.status = '⚠️'; + result.issue = '版权锚点缺失'; + result.action = 'P1工单 · 补充版权信息'; + return result; + } + + // 检查6:签名者信息 + if (!protocol.signed_by || protocol.signed_by !== '冰朔(TCS-0002∞)') { + result.status = '⚠️'; + result.issue = '签名者信息缺失或不正确'; + result.action = 'P1工单 · 补充签名信息'; + return result; + } + + result.detail = '三条根规则完整 · L0永久生效 · 版权锚点存在'; + return result; +} + +// ━━━ 生成 security_health 摘要 ━━━ +function generateSecurityHealth(checkResult) { + 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'; + + let protocol = null; + try { + if (fs.existsSync(PROTOCOL_PATH)) { + protocol = JSON.parse(fs.readFileSync(PROTOCOL_PATH, 'utf8')); + } + } catch (e) { + // protocol remains null + } + + return { + protocol_exists: fs.existsSync(PROTOCOL_PATH), + root_rules_intact: protocol ? (Array.isArray(protocol.root_rules) && protocol.root_rules.length === 3) : false, + level: protocol ? protocol.level : null, + permanent: protocol ? protocol.permanent : false, + copyright_anchor: protocol ? (protocol.copyright && protocol.copyright.includes('2026-A-00037559')) : false, + last_verified: bjTime + }; +} + +// ━━━ 主扫描 ━━━ +function scanSecurityProtocol() { + const checkResult = checkSecurityProtocol(); + const securityHealth = generateSecurityHealth(checkResult); + + const output = { + scan_time: securityHealth.last_verified, + d13: checkResult, + security_health: securityHealth + }; + + console.log(JSON.stringify(output, null, 2)); +} + +scanSecurityProtocol(); diff --git a/scripts/skyeye/scan-soldier-health.js b/scripts/skyeye/scan-soldier-health.js new file mode 100644 index 00000000..9a065796 --- /dev/null +++ b/scripts/skyeye/scan-soldier-health.js @@ -0,0 +1,156 @@ +// 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'); + +const FAILURE_RATE_THRESHOLD = 20; +const CONSECUTIVE_FAILURE_THRESHOLD_DAYS = 7; + +// ━━━ 安全读取 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 > FAILURE_RATE_THRESHOLD) { + result.status = '❌'; + result.issues.push({ + type: 'high_failure_rate', + message: `小兵故障率 ${result.failure_rate}% > ${FAILURE_RATE_THRESHOLD}%`, + action: 'P0工单 · 全局小兵修复' + }); + } + } + + // 检查连续7天同一错误 + const recentReports = getRecentReports(CONSECUTIVE_FAILURE_THRESHOLD_DAYS); + 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 >= CONSECUTIVE_FAILURE_THRESHOLD_DAYS) { + 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(); diff --git a/scripts/skyeye/skyeye-main.js b/scripts/skyeye/skyeye-main.js index 03bb8a6f..37fb31e8 100644 --- a/scripts/skyeye/skyeye-main.js +++ b/scripts/skyeye/skyeye-main.js @@ -71,6 +71,9 @@ function main() { results.structure = runModule('scan-structure.js', 'structure-health.json'); 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 · 诊断'); diff --git a/tests/contract/sfp-core.test.js b/tests/contract/sfp-core.test.js new file mode 100644 index 00000000..73030dbc --- /dev/null +++ b/tests/contract/sfp-core.test.js @@ -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位hex字符', () => { + const nonce = sfpCore.generateNonce(); + expect(nonce).toHaveLength(6); + expect(nonce).toMatch(/^[a-f0-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'); + }); + }); +});