🦅 天眼系统+人格体编号体系+门禁v2 · Phase 1-3 实施
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
This commit is contained in:
parent
399a16c06e
commit
dc9b77a637
|
|
@ -4,6 +4,7 @@
|
|||
"team": [
|
||||
{
|
||||
"dev_id": "DEV-001",
|
||||
"persona_id": "PER-001",
|
||||
"name": "页页",
|
||||
"module": "后端中间层",
|
||||
"status": "active",
|
||||
|
|
@ -13,6 +14,7 @@
|
|||
},
|
||||
{
|
||||
"dev_id": "DEV-002",
|
||||
"persona_id": "PER-002",
|
||||
"name": "肥猫",
|
||||
"module": "M-STATUS系统状态监控",
|
||||
"status": "waiting_syslog",
|
||||
|
|
@ -22,6 +24,7 @@
|
|||
},
|
||||
{
|
||||
"dev_id": "DEV-003",
|
||||
"persona_id": "PER-003",
|
||||
"name": "燕樊",
|
||||
"module": "M-MEMORY AI永久记忆核心",
|
||||
"status": "waiting_syslog",
|
||||
|
|
@ -31,6 +34,7 @@
|
|||
},
|
||||
{
|
||||
"dev_id": "DEV-004",
|
||||
"persona_id": "PER-004",
|
||||
"name": "之之",
|
||||
"module": "M-DINGTALK钉钉开发者工作台",
|
||||
"status": "waiting_broadcast",
|
||||
|
|
@ -40,6 +44,7 @@
|
|||
},
|
||||
{
|
||||
"dev_id": "DEV-005",
|
||||
"persona_id": "PER-005",
|
||||
"name": "小草莓",
|
||||
"module": "部署",
|
||||
"status": "waiting_syslog",
|
||||
|
|
@ -49,6 +54,7 @@
|
|||
},
|
||||
{
|
||||
"dev_id": "DEV-009",
|
||||
"persona_id": "PER-009",
|
||||
"name": "花尔",
|
||||
"module": "M05用户中心+M20搜索与筛选",
|
||||
"status": "waiting_syslog",
|
||||
|
|
@ -58,6 +64,7 @@
|
|||
},
|
||||
{
|
||||
"dev_id": "DEV-010",
|
||||
"persona_id": "PER-010",
|
||||
"name": "桔子",
|
||||
"module": "M06+M08+M11+M-CHANNEL",
|
||||
"status": "waiting_syslog",
|
||||
|
|
@ -67,6 +74,7 @@
|
|||
},
|
||||
{
|
||||
"dev_id": "DEV-011",
|
||||
"persona_id": "PER-011",
|
||||
"name": "匆匆那年",
|
||||
"module": "M16码字工作台",
|
||||
"status": "waiting_syslog",
|
||||
|
|
@ -76,6 +84,7 @@
|
|||
},
|
||||
{
|
||||
"dev_id": "DEV-012",
|
||||
"persona_id": "PER-012",
|
||||
"name": "Awen",
|
||||
"module": "M09+M22公告栏",
|
||||
"status": "waiting_syslog",
|
||||
|
|
@ -85,6 +94,7 @@
|
|||
},
|
||||
{
|
||||
"dev_id": "DEV-013",
|
||||
"persona_id": "PER-013",
|
||||
"name": "小兴",
|
||||
"module": "M-AUTH注册登录系统",
|
||||
"status": "waiting_syslog",
|
||||
|
|
@ -94,6 +104,7 @@
|
|||
},
|
||||
{
|
||||
"dev_id": "DEV-014",
|
||||
"persona_id": "PER-014",
|
||||
"name": "时雨",
|
||||
"module": "待分配(副控)",
|
||||
"status": "waiting_syslog",
|
||||
|
|
|
|||
|
|
@ -28,56 +28,67 @@
|
|||
"developer_permissions": {
|
||||
"DEV-001": {
|
||||
"name": "页页",
|
||||
"persona_id": "PER-001",
|
||||
"github_usernames": [],
|
||||
"allowed_paths": ["dev/DEV-001/", "backend/", "src/"]
|
||||
},
|
||||
"DEV-002": {
|
||||
"name": "肥猫",
|
||||
"persona_id": "PER-002",
|
||||
"github_usernames": [],
|
||||
"allowed_paths": ["dev/DEV-002/", "frontend/", "persona-selector/", "chat-bubble/"]
|
||||
},
|
||||
"DEV-003": {
|
||||
"name": "燕樊",
|
||||
"persona_id": "PER-003",
|
||||
"github_usernames": [],
|
||||
"allowed_paths": ["dev/DEV-003/", "settings/", "cloud-drive/"]
|
||||
},
|
||||
"DEV-004": {
|
||||
"name": "之之",
|
||||
"persona_id": "PER-004",
|
||||
"github_usernames": ["zhizhi200271"],
|
||||
"allowed_paths": ["dev/DEV-004/", "dingtalk-bot/"]
|
||||
},
|
||||
"DEV-005": {
|
||||
"name": "小草莓",
|
||||
"persona_id": "PER-005",
|
||||
"github_usernames": ["stbr-0709"],
|
||||
"allowed_paths": ["dev/DEV-005/", "status-board/", "cost-control/"]
|
||||
},
|
||||
"DEV-009": {
|
||||
"name": "花尔",
|
||||
"persona_id": "PER-009",
|
||||
"github_usernames": [],
|
||||
"allowed_paths": ["dev/DEV-009/", "user-center/"]
|
||||
},
|
||||
"DEV-010": {
|
||||
"name": "桔子",
|
||||
"persona_id": "PER-010",
|
||||
"github_usernames": ["juzi0412"],
|
||||
"allowed_paths": ["dev/DEV-010/", "ticket-system/", "data-stats/", "dynamic-comic/"]
|
||||
},
|
||||
"DEV-011": {
|
||||
"name": "匆匆那年",
|
||||
"persona_id": "PER-011",
|
||||
"github_usernames": [],
|
||||
"allowed_paths": ["dev/DEV-011/", "writing-workspace/"]
|
||||
},
|
||||
"DEV-012": {
|
||||
"name": "Awen",
|
||||
"persona_id": "PER-012",
|
||||
"github_usernames": [],
|
||||
"allowed_paths": ["dev/DEV-012/", "notification-center/", "notification/"]
|
||||
},
|
||||
"DEV-013": {
|
||||
"name": "小兴",
|
||||
"persona_id": "PER-013",
|
||||
"github_usernames": [],
|
||||
"allowed_paths": []
|
||||
},
|
||||
"DEV-014": {
|
||||
"name": "时雨",
|
||||
"persona_id": "PER-014",
|
||||
"github_usernames": [],
|
||||
"allowed_paths": []
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@
|
|||
"persona_id": "ICE-GL-ZY001",
|
||||
"persona_name": "铸渊",
|
||||
"recent_events": [
|
||||
{
|
||||
"date": "2026-03-16",
|
||||
"type": "system_upgrade",
|
||||
"description": "天眼系统+人格体编号体系+门禁v2上线 · persona-lookup.js · gate-guard-v2.js · 双端同步架构 · 全部brain文件添加persona_id",
|
||||
"by": "铸渊(冰朔授权·霜砚签发)"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-16",
|
||||
"type": "system_repair",
|
||||
|
|
@ -45,7 +51,7 @@
|
|||
"by": "GitHub Actions"
|
||||
}
|
||||
],
|
||||
"last_updated": "2026-03-16T04:01:00.000Z",
|
||||
"last_updated": "2026-03-16T09:39:00.000Z",
|
||||
"total_schemas_created": 3,
|
||||
"total_routes_implemented": 4,
|
||||
"hli_coverage": "3/17",
|
||||
|
|
|
|||
|
|
@ -17,38 +17,56 @@
|
|||
],
|
||||
"description": "母语运行时目录,Notion→GitHub同步的认知投影"
|
||||
},
|
||||
"persona_registry": {
|
||||
"description": "人格体编号体系 · 系统识别人格体编号(PER-XXX),不再识别人类",
|
||||
"source": "Notion天眼人格体注册表",
|
||||
"sync_mode": "dual_sync",
|
||||
"mapping": {
|
||||
"DEV-001": "PER-001",
|
||||
"DEV-002": "PER-002",
|
||||
"DEV-003": "PER-003",
|
||||
"DEV-004": "PER-004",
|
||||
"DEV-005": "PER-005",
|
||||
"DEV-009": "PER-009",
|
||||
"DEV-010": "PER-010",
|
||||
"DEV-011": "PER-011",
|
||||
"DEV-012": "PER-012",
|
||||
"DEV-013": "PER-013",
|
||||
"DEV-014": "PER-014"
|
||||
}
|
||||
},
|
||||
"domains": {
|
||||
"AUTH": { "module": "M01", "owner": "DEV-002", "route_prefix": "/hli/auth", "interfaces": [
|
||||
"AUTH": { "module": "M01", "owner": "DEV-002", "persona_id": "PER-002", "route_prefix": "/hli/auth", "interfaces": [
|
||||
{ "id": "HLI-AUTH-001", "path": "/hli/auth/login", "status": "schema_only" },
|
||||
{ "id": "HLI-AUTH-002", "path": "/hli/auth/register", "status": "pending" },
|
||||
{ "id": "HLI-AUTH-003", "path": "/hli/auth/verify", "status": "pending" }
|
||||
]},
|
||||
"PERSONA": { "module": "M03", "owner": "DEV-002", "route_prefix": "/hli/persona", "interfaces": [
|
||||
"PERSONA": { "module": "M03", "owner": "DEV-002", "persona_id": "PER-002", "route_prefix": "/hli/persona", "interfaces": [
|
||||
{ "id": "HLI-PERSONA-001", "path": "/hli/persona/load", "status": "pending" },
|
||||
{ "id": "HLI-PERSONA-002", "path": "/hli/persona/switch", "status": "pending" }
|
||||
]},
|
||||
"USER": { "module": "M05", "owner": "DEV-009", "route_prefix": "/hli/user", "interfaces": [
|
||||
"USER": { "module": "M05", "owner": "DEV-009", "persona_id": "PER-009", "route_prefix": "/hli/user", "interfaces": [
|
||||
{ "id": "HLI-USER-001", "path": "/hli/user/profile", "status": "pending" },
|
||||
{ "id": "HLI-USER-002", "path": "/hli/user/profile/update", "status": "pending" }
|
||||
]},
|
||||
"TICKET": { "module": "M06", "owner": "DEV-010", "route_prefix": "/hli/ticket", "interfaces": [
|
||||
"TICKET": { "module": "M06", "owner": "DEV-010", "persona_id": "PER-010", "route_prefix": "/hli/ticket", "interfaces": [
|
||||
{ "id": "HLI-TICKET-001", "path": "/hli/ticket/create", "status": "pending" },
|
||||
{ "id": "HLI-TICKET-002", "path": "/hli/ticket/query", "status": "pending" },
|
||||
{ "id": "HLI-TICKET-003", "path": "/hli/ticket/status", "status": "pending" }
|
||||
]},
|
||||
"DIALOGUE": { "module": "M07", "owner": "DEV-003", "route_prefix": "/hli/dialogue", "interfaces": [
|
||||
"DIALOGUE": { "module": "M07", "owner": "DEV-003", "persona_id": "PER-003", "route_prefix": "/hli/dialogue", "interfaces": [
|
||||
{ "id": "HLI-DIALOGUE-001", "path": "/hli/dialogue/send", "status": "pending" },
|
||||
{ "id": "HLI-DIALOGUE-002", "path": "/hli/dialogue/stream", "status": "pending" },
|
||||
{ "id": "HLI-DIALOGUE-003", "path": "/hli/dialogue/history", "status": "pending" }
|
||||
]},
|
||||
"STORAGE": { "module": "M10", "owner": "待分配", "route_prefix": "/hli/storage", "interfaces": [
|
||||
"STORAGE": { "module": "M10", "owner": "待分配", "persona_id": null, "route_prefix": "/hli/storage", "interfaces": [
|
||||
{ "id": "HLI-STORAGE-001", "path": "/hli/storage/upload", "status": "pending" },
|
||||
{ "id": "HLI-STORAGE-002", "path": "/hli/storage/download", "status": "pending" }
|
||||
]},
|
||||
"NOTIFICATION": { "module": "M09", "owner": "DEV-012", "route_prefix": "/hli/notification", "interfaces": [
|
||||
"NOTIFICATION": { "module": "M09", "owner": "DEV-012", "persona_id": "PER-012", "route_prefix": "/hli/notification", "interfaces": [
|
||||
{ "id": "HLI-NOTIF-001", "path": "/hli/notification/push", "status": "pending" }
|
||||
]},
|
||||
"DASHBOARD": { "module": "M12", "owner": "DEV-005", "route_prefix": "/hli/dashboard", "interfaces": [
|
||||
"DASHBOARD": { "module": "M12", "owner": "DEV-005", "persona_id": "PER-005", "route_prefix": "/hli/dashboard", "interfaces": [
|
||||
{ "id": "HLI-DASHBOARD-001", "path": "/hli/dashboard/status", "status": "pending" },
|
||||
{ "id": "HLI-DASHBOARD-002", "path": "/hli/dashboard/realtime", "status": "pending" }
|
||||
]}
|
||||
|
|
|
|||
|
|
@ -68,14 +68,17 @@ jobs:
|
|||
|
||||
echo "actor=$ACTOR" >> $GITHUB_OUTPUT
|
||||
echo "$CHANGED_FILES" > /tmp/changed_files.txt
|
||||
# v2: 传递 commit message 用于签名验证
|
||||
echo "commit_msg=$COMMIT_MSG" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: "🚦 门禁判定"
|
||||
- name: "🚦 门禁判定 v2"
|
||||
id: verdict
|
||||
if: steps.analyze.outputs.is_bot != 'true' && steps.analyze.outputs.is_system != 'true'
|
||||
run: |
|
||||
node scripts/gate-guard.js
|
||||
node scripts/gate-guard-v2.js
|
||||
env:
|
||||
PUSH_ACTOR: ${{ steps.analyze.outputs.actor }}
|
||||
COMMIT_MESSAGE: ${{ steps.analyze.outputs.commit_msg }}
|
||||
|
||||
- name: "✅ 系统 Push 放行"
|
||||
if: steps.analyze.outputs.is_bot == 'true' || steps.analyze.outputs.is_system == 'true'
|
||||
|
|
|
|||
|
|
@ -126,8 +126,18 @@ jobs:
|
|||
env:
|
||||
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
|
||||
NOTION_SYSLOG_DB_ID: ${{ secrets.NOTION_SYSLOG_DB_ID }}
|
||||
SKYEYE_PERSONA_DB_ID: ${{ secrets.SKYEYE_PERSONA_DB_ID }}
|
||||
run: |
|
||||
node scripts/skyeye/sync-to-notion.js || echo "⚠️ Notion同步失败(不阻断)"
|
||||
# 人格体注册表全量同步(双端同步:Notion → 本地配置)
|
||||
node scripts/skyeye/persona-lookup.js --sync || echo "⚠️ 人格体注册表同步失败(不阻断)"
|
||||
git config user.name "zhuyuan-bot"
|
||||
git config user.email "zhuyuan@guanghulab.com"
|
||||
git add .github/persona-brain/gate-guard-config.json
|
||||
if ! git diff --cached --quiet; then
|
||||
git commit -m "🦅 天眼·人格体注册表同步 · $(date +%Y-%m-%d)"
|
||||
git push || true
|
||||
fi
|
||||
|
||||
- name: "📧 通知妈妈(仅有严重问题时)"
|
||||
if: steps.diagnose.outputs.needs_human != '0'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,343 @@
|
|||
// scripts/gate-guard-v2.js
|
||||
// 铸渊·智能门禁 v2 · 判定引擎
|
||||
//
|
||||
// v2 升级内容(人格体编号体系):
|
||||
// ① 检查 commit 签名(PER-XXX 人格体编号)
|
||||
// ② 实时查询 Notion 天眼人格体注册表(降级回退到本地配置)
|
||||
// ③ 保留 v1 全部路径权限判定逻辑
|
||||
//
|
||||
// 核心原则(冰朔确认):
|
||||
// 仓库主人(repo_owner)的推送 → 永远放行
|
||||
// 系统 bot push → 永远放行
|
||||
// 其他推送 → 检查签名 + 身份 + 路径权限
|
||||
//
|
||||
// 输入:PUSH_ACTOR + /tmp/changed_files.txt + COMMIT_MESSAGE
|
||||
// 输出:action (pass/fix/revert) → GITHUB_OUTPUT
|
||||
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// ━━━ 配置路径 ━━━
|
||||
const BRAIN_CONFIG_PATH = path.join(__dirname, '../.github/persona-brain/gate-guard-config.json');
|
||||
const OWNER_CONFIG_PATH = path.join(__dirname, '../.github/gate-guard-config.json');
|
||||
const CHANGED_FILES_PATH = '/tmp/changed_files.txt';
|
||||
const GITHUB_OUTPUT = process.env.GITHUB_OUTPUT || '/dev/null';
|
||||
|
||||
// ━━━ 仓库主人 ━━━
|
||||
const REPO_OWNER = 'qinfendebingshuo';
|
||||
|
||||
// ━━━ 人格体签名正则 ━━━
|
||||
const PERSONA_SIGNATURE_REGEX = /\[PER-(\d{3})\]/;
|
||||
|
||||
// ━━━ 安全读取 JSON ━━━
|
||||
function readJSON(filePath) {
|
||||
try {
|
||||
if (!fs.existsSync(filePath)) return null;
|
||||
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
} catch (e) {
|
||||
console.error(`⚠️ 无法读取 ${path.basename(filePath)}: ${e.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ━━━ 加载合并配置 ━━━
|
||||
function loadConfig() {
|
||||
const brainConfig = readJSON(BRAIN_CONFIG_PATH);
|
||||
const ownerConfig = readJSON(OWNER_CONFIG_PATH);
|
||||
|
||||
if (!brainConfig && !ownerConfig) {
|
||||
console.error('⚠️ 两套门禁配置均缺失');
|
||||
return null;
|
||||
}
|
||||
|
||||
const config = brainConfig || {
|
||||
whitelist_actors: [],
|
||||
system_protected_paths: [],
|
||||
developer_permissions: {}
|
||||
};
|
||||
|
||||
if (!config.whitelist_actors) config.whitelist_actors = [];
|
||||
const repoOwner = config.repo_owner || REPO_OWNER;
|
||||
if (!config.whitelist_actors.includes(repoOwner)) {
|
||||
config.whitelist_actors.push(repoOwner);
|
||||
}
|
||||
config.repo_owner = repoOwner;
|
||||
|
||||
if (ownerConfig) {
|
||||
const ownerWhitelist = ownerConfig.whitelist || [];
|
||||
for (const user of ownerWhitelist) {
|
||||
if (!config.whitelist_actors.includes(user)) {
|
||||
config.whitelist_actors.push(user);
|
||||
}
|
||||
}
|
||||
if (ownerConfig.developers) {
|
||||
config._owner_developers = ownerConfig.developers;
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
// ━━━ 读取变更文件列表 ━━━
|
||||
function loadChangedFiles() {
|
||||
try {
|
||||
const content = fs.readFileSync(CHANGED_FILES_PATH, 'utf8').trim();
|
||||
if (!content) return [];
|
||||
return content.split('\n').map(f => f.trim()).filter(f => f !== '');
|
||||
} catch (e) {
|
||||
console.error('⚠️ 无法读取变更文件列表:', e.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// ━━━ 提取 commit 签名中的人格体编号 ━━━
|
||||
function extractPersonaSignature(commitMessage) {
|
||||
if (!commitMessage) return null;
|
||||
const match = commitMessage.match(PERSONA_SIGNATURE_REGEX);
|
||||
if (match) {
|
||||
return `PER-${match[1]}`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ━━━ 通过人格体编号查找开发者 ━━━
|
||||
function findByPersonaId(config, personaId) {
|
||||
if (!config || !config.developer_permissions) return null;
|
||||
|
||||
for (const [devId, dev] of Object.entries(config.developer_permissions)) {
|
||||
if (dev.persona_id === personaId) {
|
||||
return { devId, ...dev };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ━━━ 查找开发者权限(双配置源 + 人格体编号查找) ━━━
|
||||
function findDeveloper(config, actor, personaId) {
|
||||
if (!config) return null;
|
||||
|
||||
// 1. 优先通过 persona_id 查找(v2 新增)
|
||||
if (personaId) {
|
||||
const byPersona = findByPersonaId(config, personaId);
|
||||
if (byPersona) {
|
||||
console.log(`🆔 通过人格体编号 ${personaId} 识别: ${byPersona.name}`);
|
||||
return byPersona;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 从 persona-brain 配置(DEV-ID 索引)中按 github_usernames 查找
|
||||
if (config.developer_permissions) {
|
||||
for (const [devId, dev] of Object.entries(config.developer_permissions)) {
|
||||
if (dev.github_usernames && dev.github_usernames.includes(actor)) {
|
||||
return { devId, ...dev };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 从冰朔确认版配置中查找
|
||||
if (config._owner_developers && config._owner_developers[actor]) {
|
||||
const dev = config._owner_developers[actor];
|
||||
return {
|
||||
devId: dev.dev_id,
|
||||
name: dev.name,
|
||||
allowed_paths: dev.allowed_paths || [],
|
||||
github_usernames: [actor]
|
||||
};
|
||||
}
|
||||
|
||||
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) {
|
||||
console.log(`::set-output name=${key}::${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
// ━━━ 主判定逻辑 ━━━
|
||||
function main() {
|
||||
const actor = process.env.PUSH_ACTOR || '';
|
||||
const commitMessage = process.env.COMMIT_MESSAGE || '';
|
||||
console.log(`🚦 铸渊·智能门禁 v2 · 判定引擎启动`);
|
||||
console.log(` 推送者: ${actor}`);
|
||||
console.log(` Commit: ${commitMessage.substring(0, 80)}`);
|
||||
|
||||
// 0. 仓库主人 → 永远放行
|
||||
if (actor === REPO_OWNER) {
|
||||
console.log(`👑 ${actor} 是仓库主人,直接放行`);
|
||||
setOutput('action', 'pass');
|
||||
setOutput('notification', `仓库主人 ${actor} 放行`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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. 【v2 新增】提取 commit 签名中的人格体编号
|
||||
const personaId = extractPersonaSignature(commitMessage);
|
||||
if (personaId) {
|
||||
console.log(`🔏 检测到人格体签名: ${personaId}`);
|
||||
} else {
|
||||
console.log(`ℹ️ 未检测到人格体签名(兼容 v1 模式)`);
|
||||
}
|
||||
|
||||
// 5. 查找开发者身份(v2: 支持人格体编号 + GitHub 用户名双重匹配)
|
||||
const developer = findDeveloper(config, actor, personaId);
|
||||
const protectedPaths = config.system_protected_paths || [];
|
||||
|
||||
// 6. 签名与身份交叉验证(v2 增强安全)
|
||||
if (personaId && developer) {
|
||||
if (developer.persona_id && developer.persona_id !== personaId) {
|
||||
console.log(`⚠️ 签名人格体 ${personaId} 与 actor ${actor} 注册的 ${developer.persona_id} 不匹配`);
|
||||
const msg = `⚠️ 人格体签名不匹配 · 推送者: ${actor}\n\n` +
|
||||
`Commit 签名的人格体: ${personaId}\n` +
|
||||
`GitHub Actor 注册的人格体: ${developer.persona_id}\n\n` +
|
||||
`签名与身份不一致,该 commit 已被回退。`;
|
||||
setOutput('action', 'revert');
|
||||
setOutput('notification', msg.replace(/\n/g, '%0A'));
|
||||
setOutput('violation_type', 'persona_mismatch');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 分类文件(与 v1 逻辑一致)
|
||||
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}`);
|
||||
if (personaId) console.log(` 人格体签名: ${personaId} ✅`);
|
||||
|
||||
// 8. 判定行动(与 v1 逻辑一致)
|
||||
if (protectedViolations.length > 0) {
|
||||
const msg = `⛔ 系统保护路径违规 · 推送者: ${actor}${personaId ? ` [${personaId}]` : ''}\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}${personaId ? ` [${personaId}]` : ''} · ${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) {
|
||||
const devName = developer ? `${developer.name}(${developer.devId})` : `未注册开发者`;
|
||||
const devPaths = developer && developer.allowed_paths ? developer.allowed_paths.join(', ') : '无';
|
||||
const msg = `⚠️ 部分路径越权 · 推送者: ${actor}${personaId ? ` [${personaId}]` : ''} · ${devName}\n\n` +
|
||||
`允许路径: ${devPaths}\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) {
|
||||
const personaInfo = personaId ? ` · 签名:${personaId}` : '';
|
||||
console.log(`✅ 判定: pass · ${developer.name}(${developer.devId})${personaInfo} · 全部文件合法`);
|
||||
} else {
|
||||
console.log(`✅ 判定: pass · 未注册开发者 ${actor} · 未修改保护路径`);
|
||||
}
|
||||
setOutput('action', 'pass');
|
||||
setOutput('notification', `${actor}${personaId ? ` [${personaId}]` : ''} 的 push 已通过门禁检查`);
|
||||
if (personaId) setOutput('persona_id', personaId);
|
||||
}
|
||||
|
||||
main();
|
||||
|
|
@ -0,0 +1,288 @@
|
|||
// scripts/skyeye/persona-lookup.js
|
||||
// 天眼·人格体查询模块
|
||||
//
|
||||
// 功能:
|
||||
// ① lookupPersona(personaId) — 通过 PER-XXX 编号查询 Notion 天眼人格体注册表
|
||||
// ② writeBack(personaId, data) — 将处理结果写回 Notion 人格体注册表
|
||||
// ③ fullSync() — 全量同步:拉取 Notion 全部人格体数据,更新本地配置
|
||||
//
|
||||
// 依赖环境变量:
|
||||
// NOTION_TOKEN — Notion API 令牌
|
||||
// SKYEYE_PERSONA_DB_ID — 天眼人格体注册表的 Notion Database ID
|
||||
//
|
||||
// 导出模块供其他脚本使用,也可直接运行进行全量同步
|
||||
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const https = require('https');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '../..');
|
||||
const BRAIN_DIR = path.join(ROOT, '.github/persona-brain');
|
||||
|
||||
const NOTION_TOKEN = process.env.NOTION_TOKEN;
|
||||
const PERSONA_DB_ID = process.env.SKYEYE_PERSONA_DB_ID;
|
||||
|
||||
// ━━━ Notion API 请求 ━━━
|
||||
function notionRequest(method, apiPath, body) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!NOTION_TOKEN) {
|
||||
return reject(new Error('NOTION_TOKEN 未设置'));
|
||||
}
|
||||
|
||||
const postData = body ? JSON.stringify(body) : '';
|
||||
const options = {
|
||||
hostname: 'api.notion.com',
|
||||
path: apiPath,
|
||||
method: method,
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + NOTION_TOKEN,
|
||||
'Notion-Version': '2022-06-28',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
timeout: 15000
|
||||
};
|
||||
|
||||
if (body) {
|
||||
options.headers['Content-Length'] = Buffer.byteLength(postData);
|
||||
}
|
||||
|
||||
const req = https.request(options, (res) => {
|
||||
let data = '';
|
||||
res.on('data', chunk => data += chunk);
|
||||
res.on('end', () => {
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
try {
|
||||
resolve(JSON.parse(data));
|
||||
} catch (e) {
|
||||
resolve({ raw: data });
|
||||
}
|
||||
} else {
|
||||
reject(new Error(`Notion API ${res.statusCode}: ${data.length > 300 ? data.substring(0, 300) + '...' : data}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', reject);
|
||||
req.on('timeout', () => { req.destroy(); reject(new Error('Notion API timeout')); });
|
||||
if (body) req.write(postData);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// ━━━ lookupPersona: 查询单个人格体 ━━━
|
||||
async function lookupPersona(personaId) {
|
||||
if (!PERSONA_DB_ID) {
|
||||
console.log('⚠️ SKYEYE_PERSONA_DB_ID 未设置,使用本地配置回退');
|
||||
return lookupLocal(personaId);
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await notionRequest('POST', `/v1/databases/${PERSONA_DB_ID}/query`, {
|
||||
filter: {
|
||||
property: '人格体编号',
|
||||
rich_text: {
|
||||
equals: personaId
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (result.results && result.results.length > 0) {
|
||||
const page = result.results[0];
|
||||
return parsePersonaPage(page);
|
||||
}
|
||||
|
||||
console.log(`⚠️ Notion 中未找到人格体 ${personaId},尝试本地回退`);
|
||||
return lookupLocal(personaId);
|
||||
} catch (e) {
|
||||
console.error(`⚠️ Notion 查询失败: ${e.message},使用本地配置回退`);
|
||||
return lookupLocal(personaId);
|
||||
}
|
||||
}
|
||||
|
||||
// ━━━ 解析 Notion 人格体页面 ━━━
|
||||
function parsePersonaPage(page) {
|
||||
const props = page.properties || {};
|
||||
|
||||
function getText(prop) {
|
||||
if (!prop) return '';
|
||||
if (prop.title) return prop.title.map(t => t.plain_text).join('');
|
||||
if (prop.rich_text) return prop.rich_text.map(t => t.plain_text).join('');
|
||||
if (prop.select) return prop.select ? prop.select.name : '';
|
||||
return '';
|
||||
}
|
||||
|
||||
return {
|
||||
page_id: page.id,
|
||||
persona_id: getText(props['人格体编号']),
|
||||
name: getText(props['名称']) || getText(props['Name']),
|
||||
dev_id: getText(props['开发者ID']),
|
||||
github_username: getText(props['GitHub用户名']),
|
||||
status: getText(props['状态']),
|
||||
module: getText(props['负责模块']),
|
||||
commit_signature: getText(props['签名标识']),
|
||||
source: 'notion'
|
||||
};
|
||||
}
|
||||
|
||||
// ━━━ 本地回退查询 ━━━
|
||||
function lookupLocal(personaId) {
|
||||
try {
|
||||
const configPath = path.join(BRAIN_DIR, 'gate-guard-config.json');
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
const devPerms = config.developer_permissions || {};
|
||||
|
||||
for (const [devId, dev] of Object.entries(devPerms)) {
|
||||
if (dev.persona_id === personaId) {
|
||||
return {
|
||||
page_id: null,
|
||||
persona_id: personaId,
|
||||
name: dev.name,
|
||||
dev_id: devId,
|
||||
github_username: (dev.github_usernames || [])[0] || '',
|
||||
status: 'active',
|
||||
module: '',
|
||||
commit_signature: '',
|
||||
source: 'local'
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`⚠️ 本地配置读取失败: ${e.message}`);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ━━━ writeBack: 写回 Notion ━━━
|
||||
async function writeBack(personaId, data) {
|
||||
if (!PERSONA_DB_ID || !NOTION_TOKEN) {
|
||||
console.log('⚠️ Notion 凭证不完整,跳过写回');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 先查询获取 page_id
|
||||
const persona = await lookupPersona(personaId);
|
||||
if (!persona || !persona.page_id) {
|
||||
console.log(`⚠️ 无法找到 ${personaId} 的 Notion 页面,跳过写回`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 构建更新属性
|
||||
const properties = {};
|
||||
if (data.status) {
|
||||
properties['状态'] = {
|
||||
select: { name: data.status }
|
||||
};
|
||||
}
|
||||
if (data.last_activity) {
|
||||
properties['最后活动'] = {
|
||||
date: { start: data.last_activity }
|
||||
};
|
||||
}
|
||||
if (data.gate_result) {
|
||||
properties['门禁记录'] = {
|
||||
rich_text: [{ type: 'text', text: { content: data.gate_result.substring(0, 2000) } }]
|
||||
};
|
||||
}
|
||||
|
||||
await notionRequest('PATCH', `/v1/pages/${persona.page_id}`, { properties });
|
||||
console.log(`✅ ${personaId} 数据已写回 Notion`);
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error(`⚠️ 写回失败: ${e.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ━━━ fullSync: 全量同步 ━━━
|
||||
async function fullSync() {
|
||||
console.log('🔄 人格体注册表全量同步启动');
|
||||
|
||||
if (!PERSONA_DB_ID || !NOTION_TOKEN) {
|
||||
console.log('⚠️ Notion 凭证不完整,跳过全量同步');
|
||||
return { success: false, reason: 'credentials_missing' };
|
||||
}
|
||||
|
||||
try {
|
||||
// 拉取全部人格体
|
||||
let allPersonas = [];
|
||||
let hasMore = true;
|
||||
let startCursor = undefined;
|
||||
|
||||
while (hasMore) {
|
||||
const body = { page_size: 100 };
|
||||
if (startCursor) body.start_cursor = startCursor;
|
||||
|
||||
const result = await notionRequest('POST', `/v1/databases/${PERSONA_DB_ID}/query`, body);
|
||||
const personas = (result.results || []).map(parsePersonaPage);
|
||||
allPersonas = allPersonas.concat(personas);
|
||||
hasMore = result.has_more;
|
||||
startCursor = result.next_cursor;
|
||||
}
|
||||
|
||||
console.log(`📋 拉取到 ${allPersonas.length} 个人格体`);
|
||||
|
||||
// 更新本地 gate-guard-config.json
|
||||
const configPath = path.join(BRAIN_DIR, 'gate-guard-config.json');
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
|
||||
let updated = 0;
|
||||
for (const persona of allPersonas) {
|
||||
if (!persona.dev_id) continue;
|
||||
|
||||
const devId = persona.dev_id;
|
||||
if (!config.developer_permissions[devId]) {
|
||||
config.developer_permissions[devId] = {
|
||||
name: persona.name,
|
||||
persona_id: persona.persona_id,
|
||||
github_usernames: [],
|
||||
allowed_paths: []
|
||||
};
|
||||
}
|
||||
|
||||
const dev = config.developer_permissions[devId];
|
||||
dev.persona_id = persona.persona_id;
|
||||
|
||||
// 更新 GitHub 用户名(如果 Notion 有而本地没有)
|
||||
if (persona.github_username && !dev.github_usernames.includes(persona.github_username)) {
|
||||
dev.github_usernames.push(persona.github_username);
|
||||
}
|
||||
|
||||
updated++;
|
||||
}
|
||||
|
||||
config.updated_at = new Date().toISOString().slice(0, 10);
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
||||
console.log(`✅ 已更新 ${updated} 个人格体配置`);
|
||||
|
||||
return { success: true, total: allPersonas.length, updated };
|
||||
} catch (e) {
|
||||
console.error(`❌ 全量同步失败: ${e.message}`);
|
||||
return { success: false, reason: e.message };
|
||||
}
|
||||
}
|
||||
|
||||
// ━━━ 导出 ━━━
|
||||
module.exports = { lookupPersona, writeBack, fullSync };
|
||||
|
||||
// ━━━ 直接运行入口 ━━━
|
||||
if (require.main === module) {
|
||||
(async () => {
|
||||
const arg = process.argv[2];
|
||||
|
||||
if (arg === '--sync') {
|
||||
const result = await fullSync();
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
} else if (arg && arg.startsWith('PER-')) {
|
||||
const persona = await lookupPersona(arg);
|
||||
console.log(JSON.stringify(persona, null, 2));
|
||||
} else {
|
||||
console.log('用法:');
|
||||
console.log(' node persona-lookup.js --sync 全量同步');
|
||||
console.log(' node persona-lookup.js PER-001 查询单个人格体');
|
||||
}
|
||||
})();
|
||||
}
|
||||
Loading…
Reference in New Issue