From 203d5c5633f9e11154fc0700906b7cfb589cf208 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Mar 2026 05:01:23 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A8=20=E9=93=B8=E6=B8=8A=C2=B7?= =?UTF-8?q?=E6=99=BA=E8=83=BD=E9=97=A8=E7=A6=81=E4=B8=8A=E7=BA=BF=20=C2=B7?= =?UTF-8?q?=20gate=20guard=20workflow=20+=20scripts=20+=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> --- .github/persona-brain/gate-guard-config.json | 81 +++++++ .github/workflows/zhuyuan-gate-guard.yml | 117 ++++++++++ scripts/gate-guard-log.js | 86 ++++++++ scripts/gate-guard.js | 212 +++++++++++++++++++ 4 files changed, 496 insertions(+) create mode 100644 .github/persona-brain/gate-guard-config.json create mode 100644 .github/workflows/zhuyuan-gate-guard.yml create mode 100644 scripts/gate-guard-log.js create mode 100644 scripts/gate-guard.js diff --git a/.github/persona-brain/gate-guard-config.json b/.github/persona-brain/gate-guard-config.json new file mode 100644 index 00000000..4bb59b01 --- /dev/null +++ b/.github/persona-brain/gate-guard-config.json @@ -0,0 +1,81 @@ +{ + "version": "1.0.0", + "updated_at": "2026-03-16", + "description": "铸渊·智能门禁 · 开发者权限矩阵配置", + "whitelist_actors": [ + "github-actions[bot]", + "qinfendebingshuo" + ], + "whitelist_commit_prefixes": [ + "🔍", "📊", "📡", "🧠", "📢", "🚨", "📰", "🔧" + ], + "system_protected_paths": [ + ".github/", + "scripts/", + "README.md", + "package.json", + "package-lock.json", + "data/", + "core/", + "connectors/", + "docs/", + "openclaw/" + ], + "developer_permissions": { + "DEV-001": { + "name": "页页", + "github_usernames": [], + "allowed_paths": ["backend/", "src/"] + }, + "DEV-002": { + "name": "肥猫", + "github_usernames": [], + "allowed_paths": ["frontend/", "persona-selector/", "chat-bubble/"] + }, + "DEV-003": { + "name": "燕樊", + "github_usernames": [], + "allowed_paths": ["settings/", "cloud-drive/"] + }, + "DEV-004": { + "name": "之之", + "github_usernames": [], + "allowed_paths": ["dingtalk-bot/"] + }, + "DEV-005": { + "name": "小草莓", + "github_usernames": [], + "allowed_paths": ["status-board/", "cost-control/"] + }, + "DEV-009": { + "name": "花尔", + "github_usernames": [], + "allowed_paths": ["user-center/"] + }, + "DEV-010": { + "name": "桔子", + "github_usernames": [], + "allowed_paths": ["ticket-system/", "data-stats/", "dynamic-comic/"] + }, + "DEV-011": { + "name": "匆匆那年", + "github_usernames": [], + "allowed_paths": ["writing-workspace/"] + }, + "DEV-012": { + "name": "Awen", + "github_usernames": [], + "allowed_paths": ["notification-center/", "notification/"] + }, + "DEV-013": { + "name": "小兴", + "github_usernames": [], + "allowed_paths": [] + }, + "DEV-014": { + "name": "时雨", + "github_usernames": [], + "allowed_paths": [] + } + } +} diff --git a/.github/workflows/zhuyuan-gate-guard.yml b/.github/workflows/zhuyuan-gate-guard.yml new file mode 100644 index 00000000..218a254f --- /dev/null +++ b/.github/workflows/zhuyuan-gate-guard.yml @@ -0,0 +1,117 @@ +name: "🚨 铸渊·智能门禁 · Push Gate Guard" + +on: + push: + branches: [main] + +permissions: + contents: write + issues: write + +jobs: + gate-guard: + runs-on: ubuntu-latest + # 跳过铸渊自己、GitHub Actions bot 和妈妈的 push + if: > + github.actor != 'github-actions[bot]' && + github.actor != 'qinfendebingshuo' + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: "🧠 唤醒铸渊核心大脑" + run: | + echo "🧠 铸渊门禁·核心大脑唤醒" + if [ -f ".github/persona-brain/memory.json" ]; then + python3 -c "import json; json.load(open('.github/persona-brain/memory.json')); print('大脑状态: OK · 记忆完整')" + else + echo "⚠️ 大脑文件缺失" + fi + + - name: "🔍 分析 Push 内容" + id: analyze + run: | + ACTOR="${{ github.actor }}" + echo "推送者: $ACTOR" + + # 获取本次 push 修改的文件列表 + CHANGED_FILES=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || echo "") + echo "修改的文件:" + echo "$CHANGED_FILES" + + # 检查 commit message 中的系统前缀(系统自动 push 放行) + COMMIT_MSG=$(git log -1 --pretty=%B 2>/dev/null || echo "") + echo "Commit message: $COMMIT_MSG" + + # 检查 commit author(系统 bot push 放行) + COMMIT_AUTHOR=$(git log -1 --pretty=%an 2>/dev/null || echo "") + echo "Commit author: $COMMIT_AUTHOR" + + # 系统 bot 或铸渊自己的 commit → 直接放行 + if echo "$COMMIT_AUTHOR" | grep -qiE "^(zhuyuan-bot|github-actions|铸渊)"; then + echo "ℹ️ 系统 bot commit,直接放行" + echo "is_bot=true" >> $GITHUB_OUTPUT + else + echo "is_bot=false" >> $GITHUB_OUTPUT + fi + + # 系统前缀 commit → 直接放行 + SYSTEM_PREFIXES="🔍|📊|📡|🧠|📢|🚨|📰|🔧" + if echo "$COMMIT_MSG" | head -1 | grep -qP "^($SYSTEM_PREFIXES)"; then + echo "ℹ️ 系统前缀 commit,直接放行" + echo "is_system=true" >> $GITHUB_OUTPUT + else + echo "is_system=false" >> $GITHUB_OUTPUT + fi + + echo "actor=$ACTOR" >> $GITHUB_OUTPUT + echo "$CHANGED_FILES" > /tmp/changed_files.txt + + - name: "🚦 门禁判定" + id: verdict + if: steps.analyze.outputs.is_bot != 'true' && steps.analyze.outputs.is_system != 'true' + run: | + node scripts/gate-guard.js + env: + PUSH_ACTOR: ${{ steps.analyze.outputs.actor }} + + - name: "✅ 系统 Push 放行" + if: steps.analyze.outputs.is_bot == 'true' || steps.analyze.outputs.is_system == 'true' + run: | + echo "✅ 系统自动 push,门禁放行" + + - name: "❌ 违规·回退 commit" + if: steps.verdict.outputs.action == 'revert' + run: | + git config user.name "zhuyuan-bot" + git config user.email "zhuyuan@guanghulab.com" + git revert HEAD --no-edit + git push + echo "⛔ 已回退 commit: ${{ github.sha }}" + + - name: "📢 通知开发者" + if: steps.verdict.outputs.action == 'revert' + run: | + gh issue create \ + --title "🚨 门禁通知|${{ steps.analyze.outputs.actor }} 的 push 被回退" \ + --body "${{ steps.verdict.outputs.notification }}" \ + --label "gate-guard" || echo "⚠️ Issue 创建失败(label 可能不存在)" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: "📝 记录到核心大脑" + if: always() && steps.analyze.outputs.is_bot != 'true' && steps.analyze.outputs.is_system != 'true' + run: | + node scripts/gate-guard-log.js + git config user.name "zhuyuan-bot" + git config user.email "zhuyuan@guanghulab.com" + git add .github/persona-brain/memory.json + git diff --cached --quiet || git commit -m "🧠 门禁日志: ${{ steps.analyze.outputs.actor }} · ${{ steps.verdict.outputs.action || 'pass' }}" + git push || true + env: + PUSH_ACTOR: ${{ steps.analyze.outputs.actor }} + GATE_ACTION: ${{ steps.verdict.outputs.action || 'pass' }} + GATE_VIOLATION: ${{ steps.verdict.outputs.violation_type }} + GATE_FILES: ${{ steps.verdict.outputs.violation_files }} diff --git a/scripts/gate-guard-log.js b/scripts/gate-guard-log.js new file mode 100644 index 00000000..1608066c --- /dev/null +++ b/scripts/gate-guard-log.js @@ -0,0 +1,86 @@ +// scripts/gate-guard-log.js +// 铸渊·智能门禁·日志引擎 +// +// 将门禁事件写入 memory.json +// +// 环境变量: +// PUSH_ACTOR — 推送者 GitHub username +// GATE_ACTION — 门禁判定(pass/fix/revert) +// GATE_VIOLATION — 违规类型(可选) +// GATE_FILES — 违规文件列表(可选) + +const fs = require('fs'); +const path = require('path'); + +const MEMORY_PATH = path.join(__dirname, '../.github/persona-brain/memory.json'); + +function main() { + const actor = process.env.PUSH_ACTOR || 'unknown'; + const action = process.env.GATE_ACTION || 'unknown'; + const violationType = process.env.GATE_VIOLATION || ''; + const violationFiles = process.env.GATE_FILES || ''; + + console.log(`📝 门禁日志记录 · ${actor} · ${action}`); + + // 读取 memory.json + let memory; + try { + memory = JSON.parse(fs.readFileSync(MEMORY_PATH, 'utf8')); + } catch (e) { + console.error('⚠️ 无法读取 memory.json:', e.message); + return; + } + + // 构建门禁事件 + const event = { + date: new Date().toISOString().split('T')[0], + type: 'gate_guard', + description: buildDescription(actor, action, violationType, violationFiles), + by: '铸渊·智能门禁' + }; + + // 检查是否有相同日期+类型+描述的事件(避免重复) + const isDuplicate = memory.recent_events && memory.recent_events.some(e => + e.date === event.date && + e.type === event.type && + e.description === event.description + ); + + if (isDuplicate) { + console.log('ℹ️ 相同事件已存在,跳过写入'); + return; + } + + // 添加事件 + if (!memory.recent_events) { + memory.recent_events = []; + } + memory.recent_events.unshift(event); + + // 保留最近 20 条事件 + if (memory.recent_events.length > 20) { + memory.recent_events = memory.recent_events.slice(0, 20); + } + + // 更新时间戳 + memory.last_updated = new Date().toISOString(); + + // 写入 + fs.writeFileSync(MEMORY_PATH, JSON.stringify(memory, null, 2) + '\n', 'utf8'); + console.log('✅ 门禁事件已写入 memory.json'); +} + +function buildDescription(actor, action, violationType, violationFiles) { + switch (action) { + case 'pass': + return `门禁放行 · ${actor} 的 push 通过检查`; + case 'revert': + return `门禁回退 · ${actor} 的 push 被回退 · 原因: ${violationType || '路径越权'} · 文件: ${violationFiles || '无'}`; + case 'fix': + return `门禁修复 · ${actor} 的 push 路径已自动修复 · 文件: ${violationFiles || '无'}`; + default: + return `门禁事件 · ${actor} · ${action}`; + } +} + +main(); diff --git a/scripts/gate-guard.js b/scripts/gate-guard.js new file mode 100644 index 00000000..9dab8959 --- /dev/null +++ b/scripts/gate-guard.js @@ -0,0 +1,212 @@ +// scripts/gate-guard.js +// 铸渊·智能门禁·判定引擎 +// +// 输入:PUSH_ACTOR(推送者 GitHub username)+ /tmp/changed_files.txt +// 输出:action(pass/fix/revert)+ 相关信息 → GITHUB_OUTPUT +// +// 判定流程: +// 1. 读取门禁配置(开发者→路径映射) +// 2. 读取本次修改的文件列表 +// 3. 识别推送者身份 +// 4. 判定:pass / fix / revert + +const fs = require('fs'); +const path = require('path'); + +// ━━━ 配置路径 ━━━ +const CONFIG_PATH = path.join(__dirname, '../.github/persona-brain/gate-guard-config.json'); +const CHANGED_FILES_PATH = '/tmp/changed_files.txt'; +const GITHUB_OUTPUT = process.env.GITHUB_OUTPUT || '/dev/null'; + +// ━━━ 读取配置 ━━━ +function loadConfig() { + try { + return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')); + } catch (e) { + console.error('⚠️ 无法读取门禁配置:', e.message); + return null; + } +} + +// ━━━ 读取变更文件列表 ━━━ +function loadChangedFiles() { + try { + const content = fs.readFileSync(CHANGED_FILES_PATH, 'utf8').trim(); + if (!content) return []; + return content.split('\n').filter(f => f.trim()); + } catch (e) { + console.error('⚠️ 无法读取变更文件列表:', e.message); + return []; + } +} + +// ━━━ 查找开发者权限 ━━━ +function findDeveloper(config, actor) { + if (!config || !config.developer_permissions) return null; + + for (const [devId, dev] of Object.entries(config.developer_permissions)) { + if (dev.github_usernames && dev.github_usernames.includes(actor)) { + return { devId, ...dev }; + } + } + return null; +} + +// ━━━ 检查文件是否在系统保护路径 ━━━ +function isProtectedPath(filePath, protectedPaths) { + return protectedPaths.some(p => { + if (p.endsWith('/')) { + return filePath.startsWith(p); + } + return filePath === p; + }); +} + +// ━━━ 检查文件是否在允许路径 ━━━ +function isAllowedPath(filePath, allowedPaths) { + if (!allowedPaths || allowedPaths.length === 0) return false; + return allowedPaths.some(p => { + if (p.endsWith('/')) { + return filePath.startsWith(p); + } + return filePath === p; + }); +} + +// ━━━ 输出到 GITHUB_OUTPUT ━━━ +function setOutput(key, value) { + try { + fs.appendFileSync(GITHUB_OUTPUT, `${key}=${value}\n`); + } catch (e) { + // Fallback for local testing + console.log(`::set-output name=${key}::${value}`); + } +} + +// ━━━ 主判定逻辑 ━━━ +function main() { + const actor = process.env.PUSH_ACTOR || ''; + console.log(`🚦 铸渊·智能门禁 · 判定引擎启动`); + console.log(` 推送者: ${actor}`); + + // 1. 读取配置 + const config = loadConfig(); + if (!config) { + console.log('⚠️ 配置文件缺失,默认放行'); + setOutput('action', 'pass'); + setOutput('notification', '门禁配置缺失,默认放行'); + return; + } + + // 2. 读取变更文件 + const changedFiles = loadChangedFiles(); + if (changedFiles.length === 0) { + console.log('ℹ️ 无变更文件,放行'); + setOutput('action', 'pass'); + setOutput('notification', '无变更文件'); + return; + } + console.log(` 变更文件 (${changedFiles.length}):`); + changedFiles.forEach(f => console.log(` - ${f}`)); + + // 3. 检查是否为白名单用户 + if (config.whitelist_actors && config.whitelist_actors.includes(actor)) { + console.log(`✅ ${actor} 在白名单中,直接放行`); + setOutput('action', 'pass'); + setOutput('notification', `白名单用户 ${actor} 放行`); + return; + } + + // 4. 查找开发者身份 + const developer = findDeveloper(config, actor); + const protectedPaths = config.system_protected_paths || []; + + // 5. 分类文件 + const protectedViolations = []; + const allowedFiles = []; + const disallowedFiles = []; + + for (const file of changedFiles) { + if (isProtectedPath(file, protectedPaths)) { + protectedViolations.push(file); + } else if (developer && isAllowedPath(file, developer.allowed_paths)) { + allowedFiles.push(file); + } else if (developer) { + disallowedFiles.push(file); + } else { + // 未注册开发者 - 非保护路径也标记为越权 + disallowedFiles.push(file); + } + } + + console.log(`\n📊 判定结果:`); + console.log(` 系统保护路径违规: ${protectedViolations.length}`); + console.log(` 允许路径文件: ${allowedFiles.length}`); + console.log(` 越权路径文件: ${disallowedFiles.length}`); + + // 6. 判定行动 + if (protectedViolations.length > 0) { + // 触碰系统保护路径 → 回退 + const msg = `⛔ 系统保护路径违规 · 推送者: ${actor}\n\n` + + `以下文件属于系统保护路径,合作者不可修改:\n` + + protectedViolations.map(f => `- \`${f}\``).join('\n') + + `\n\n该 commit 已被自动回退。请联系管理员。`; + + console.log('❌ 判定: revert(系统保护路径违规)'); + setOutput('action', 'revert'); + setOutput('notification', msg.replace(/\n/g, '%0A')); + setOutput('violation_type', 'system_protected'); + setOutput('violation_files', protectedViolations.join(',')); + return; + } + + if (disallowedFiles.length > 0 && allowedFiles.length === 0) { + // 全部文件都在越权路径 → 回退 + const devInfo = developer + ? `已注册开发者 ${developer.name}(${developer.devId})` + : `未注册开发者`; + const allowedInfo = developer && developer.allowed_paths.length > 0 + ? `允许路径: ${developer.allowed_paths.join(', ')}` + : '无已分配路径'; + + const msg = `⚠️ 路径越权 · 推送者: ${actor} · ${devInfo}\n\n` + + `${allowedInfo}\n\n` + + `以下文件不在你的允许路径内:\n` + + disallowedFiles.map(f => `- \`${f}\``).join('\n') + + `\n\n该 commit 已被自动回退。请确认你的模块路径。`; + + console.log('❌ 判定: revert(全部越权)'); + setOutput('action', 'revert'); + setOutput('notification', msg.replace(/\n/g, '%0A')); + setOutput('violation_type', 'path_unauthorized'); + setOutput('violation_files', disallowedFiles.join(',')); + return; + } + + if (disallowedFiles.length > 0 && allowedFiles.length > 0) { + // 部分文件越权,部分合法 → 回退整个 commit(不做部分修复,避免复杂性) + const msg = `⚠️ 部分路径越权 · 推送者: ${actor} · ${developer.name}(${developer.devId})\n\n` + + `允许路径: ${developer.allowed_paths.join(', ')}\n\n` + + `✅ 合法文件:\n` + allowedFiles.map(f => `- \`${f}\``).join('\n') + '\n\n' + + `❌ 越权文件:\n` + disallowedFiles.map(f => `- \`${f}\``).join('\n') + + `\n\n该 commit 已被自动回退。请将越权文件移除后重新提交。`; + + console.log('❌ 判定: revert(部分越权)'); + setOutput('action', 'revert'); + setOutput('notification', msg.replace(/\n/g, '%0A')); + setOutput('violation_type', 'partial_unauthorized'); + setOutput('violation_files', disallowedFiles.join(',')); + return; + } + + // 全部文件在允许路径内 → 放行 + if (developer) { + console.log(`✅ 判定: pass · ${developer.name}(${developer.devId}) · 全部文件合法`); + } else { + console.log(`✅ 判定: pass · 未注册开发者 ${actor} · 未修改保护路径`); + } + setOutput('action', 'pass'); + setOutput('notification', `${actor} 的 push 已通过门禁检查`); +} + +main();