🚨 铸渊·智能门禁上线 · gate guard workflow + scripts + config
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
This commit is contained in:
parent
14c59ec39a
commit
203d5c5633
|
|
@ -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": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 }}
|
||||
|
|
@ -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();
|
||||
|
|
@ -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();
|
||||
Loading…
Reference in New Issue