diff --git a/.github/persona-brain/agent-registry.json b/.github/persona-brain/agent-registry.json
index ac45b404..cc54e561 100644
--- a/.github/persona-brain/agent-registry.json
+++ b/.github/persona-brain/agent-registry.json
@@ -1227,6 +1227,16 @@
"owner": "ICE-0002∞",
"registered": "2026-03-25",
"description": "天眼系统PR级合并膜 — 同账号沙箱隔离 + 全系统审核。R1关键文件/R2天眼区域入侵/R3构建产物/R4核心配置篡改/R5大规模删除/R6工作流篡改。物理层阻塞:workflow失败=merge按钮禁用"
+ },
+ {
+ "id": "AG-ZY-096",
+ "name": "开发者门户频道部署引擎",
+ "type": "workflow",
+ "file": ".github/workflows/dev-portal-deploy.yml",
+ "parent_sys": "SYS-GLW-0001",
+ "owner": "ICE-0002∞",
+ "registered": "2026-03-25",
+ "description": "开发者频道部署后自动更新manifest.json + Notion画像数据库同步"
}
],
"note": "铸渊已扫描所有workflow文件,按此格式逐一注册,编号从AG-ZY-001开始顺序分配",
diff --git a/.github/workflows/dev-portal-deploy.yml b/.github/workflows/dev-portal-deploy.yml
new file mode 100644
index 00000000..c3ade916
--- /dev/null
+++ b/.github/workflows/dev-portal-deploy.yml
@@ -0,0 +1,131 @@
+# ═══════════════════════════════════════════════
+# 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
+# 📜 Copyright: 国作登字-2026-A-00037559
+# ═══════════════════════════════════════════════
+#
+# 开发者门户部署工作流
+# Developer Portal Channel Deploy
+#
+# 触发条件: PR 合并到 main 且修改了 docs/dev-portal/channels/
+# 作用: 更新门户 manifest.json,同步开发者动态到 Notion
+#
+name: 🌐 开发者门户 · 频道部署
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - 'docs/dev-portal/channels/**'
+
+permissions:
+ contents: write
+
+jobs:
+ update-manifest:
+ name: 📋 更新门户清单
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 2
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: 🔍 检测频道变更
+ id: detect
+ run: |
+ # Find which channels were updated
+ CHANGED=$(git diff --name-only HEAD~1 HEAD -- docs/dev-portal/channels/ || true)
+ if [ -z "$CHANGED" ]; then
+ echo "skip=true" >> "$GITHUB_OUTPUT"
+ echo "无频道文件变更"
+ exit 0
+ fi
+
+ echo "skip=false" >> "$GITHUB_OUTPUT"
+
+ # Extract unique DEV-IDs
+ DEV_IDS=$(echo "$CHANGED" | grep -oP 'DEV-\d{3}' | sort -u | tr '\n' ',' | sed 's/,$//')
+ echo "dev_ids=$DEV_IDS" >> "$GITHUB_OUTPUT"
+ echo "变更频道: $DEV_IDS"
+
+ # Count files per channel
+ echo "$CHANGED" | while read f; do echo " · $f"; done
+
+ - name: 📋 更新 manifest.json
+ if: steps.detect.outputs.skip != 'true'
+ run: |
+ node -e "
+ const fs = require('fs');
+ const path = require('path');
+
+ const manifestPath = 'docs/dev-portal/manifest.json';
+ const configPath = '.github/persona-brain/gate-guard-config.json';
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
+
+ const devIds = '${{ steps.detect.outputs.dev_ids }}'.split(',').filter(Boolean);
+ const now = new Date().toISOString();
+
+ for (const devId of devIds) {
+ if (!manifest.channels[devId]) continue;
+
+ // Update channel info
+ const ch = manifest.channels[devId];
+ ch.status = 'active';
+ ch.last_deploy = now;
+ ch.deploy_count = (ch.deploy_count || 0) + 1;
+
+ // Scan channel directory for modules
+ const channelDir = path.join('docs/dev-portal/channels', devId);
+ if (fs.existsSync(channelDir)) {
+ const items = fs.readdirSync(channelDir).filter(f => f !== 'README.md' && f !== '.gitkeep');
+ ch.modules = items;
+ ch.last_deploy_summary = items.length + ' 个文件已部署';
+ }
+
+ // Add activity entry
+ const devConfig = config.developer_permissions && config.developer_permissions[devId];
+ manifest.recent_activity.unshift({
+ time: now,
+ dev_id: devId,
+ dev_name: devConfig ? devConfig.name : devId,
+ persona_id: devConfig ? devConfig.persona_id : null,
+ summary: '部署更新到 ' + devId + ' 频道'
+ });
+ }
+
+ // Keep only last 50 activity entries
+ manifest.recent_activity = manifest.recent_activity.slice(0, 50);
+ manifest.updated_at = now;
+ manifest.updated_by = 'dev-portal-deploy';
+
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
+ console.log('✅ manifest.json 已更新');
+ "
+
+ - name: 📡 同步开发者动态到 Notion
+ if: steps.detect.outputs.skip != 'true' && env.NOTION_API_KEY != ''
+ env:
+ NOTION_API_KEY: ${{ secrets.NOTION_API_KEY }}
+ DEV_PROFILE_DB_ID: ${{ secrets.DEV_PROFILE_DB_ID }}
+ run: |
+ node scripts/skyeye/dev-portal-notion-sync.js \
+ --dev-ids "${{ steps.detect.outputs.dev_ids }}" \
+ --action "channel_deploy" \
+ --timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
+ || echo "⚠️ Notion同步失败(非致命)"
+
+ - name: 💾 提交 manifest 更新
+ if: steps.detect.outputs.skip != 'true'
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ git add docs/dev-portal/manifest.json
+ git diff --cached --quiet && echo "无变更" || git commit -m "📋 开发者门户: 更新频道清单 [${{ steps.detect.outputs.dev_ids }}]"
+ git push || echo "⚠️ 推送失败"
diff --git a/.github/workflows/skyeye-pr-risk-check.yml b/.github/workflows/skyeye-pr-risk-check.yml
index 9cccf093..6e6ab004 100644
--- a/.github/workflows/skyeye-pr-risk-check.yml
+++ b/.github/workflows/skyeye-pr-risk-check.yml
@@ -92,28 +92,6 @@ jobs:
PR_COMMITS: /tmp/pr_commits.txt
run: node scripts/skyeye/pr-risk-check.js
- - name: 🧠 天眼全系统审计
- if: failure()
- id: skyeye_audit
- run: |
- echo "━━━ 天眼全系统审计(合并被阻止后触发)━━━"
- echo ""
-
- # Run SkyEye scan dimensions that can be checked locally
- echo "📡 D1 · 目录结构检查..."
- node scripts/skyeye/scan-structure.js 2>/dev/null || echo "⚠️ scan-structure 执行失败"
-
- echo ""
- echo "🧠 D2 · 大脑健康检查..."
- node scripts/skyeye/scan-brain-health.js 2>/dev/null || echo "⚠️ scan-brain-health 执行失败"
-
- echo ""
- echo "🔐 D3 · 安全协议完整性检查..."
- node scripts/skyeye/scan-security-protocol.js 2>/dev/null || echo "⚠️ scan-security-protocol 执行失败"
-
- echo ""
- echo "━━━ 天眼审计完成 ━━━"
-
- name: 📝 写入天眼审核报告
if: always()
uses: actions/github-script@v7
@@ -134,20 +112,16 @@ jobs:
body += `| **检查时间** | ${new Date().toISOString()} |\n\n`;
if (decision === 'success') {
- body += '### ✅ 天眼合并膜: 通过\n\n';
- body += '此 PR 的变更已通过天眼系统审核,可以安全合并。\n\n';
- if (!isAgentBranch) {
- body += '> 💡 非代理分支(主权者手动操作),天眼合并膜自动放行\n';
- }
+ body += '### ✅ 天眼全系统审核: 通过\n\n';
+ body += '此 PR 已通过天眼全局仓库架构审核,符合系统结构要求,可以合并。\n';
} else {
- body += '### ❌ 天眼合并膜: 物理层拒绝合并\n\n';
- body += '此 PR 的变更触及了天眼系统包裹区域且未通过审核。\n\n';
+ body += '### ❌ 天眼合并膜: 物理切断合并通道\n\n';
+ body += '此 PR 不符合仓库整体系统结构要求。\n\n';
body += '**处理方式:**\n';
- body += '1. 🔍 检查上方日志中标记的风险项\n';
- body += '2. 📦 确保代理PR的变更限制在沙箱区域内\n';
- body += '3. 👑 如需修改天眼包裹区域,请联系主权者 TCS-0002∞ 审核\n\n';
- body += '> ⛔ 天眼合并膜已阻止此PR合并。\n';
- body += '> 主权者可手动批准后合并(需在 Branch Protection 中 bypass)。\n';
+ body += '1. 🔍 检查上方 Actions 日志中标记的风险项\n';
+ body += '2. 📦 修复不符合仓库结构的变更\n';
+ body += '3. 🔄 推送修复后天眼将自动重新审核\n\n';
+ body += '> ⛔ 天眼合并膜已物理切断合并通道。\n';
}
body += '\n---\n';
diff --git a/docs/dev-portal/channels/DEV-001/README.md b/docs/dev-portal/channels/DEV-001/README.md
new file mode 100644
index 00000000..2e90b8ce
--- /dev/null
+++ b/docs/dev-portal/channels/DEV-001/README.md
@@ -0,0 +1 @@
+# DEV-001 频道沙箱
diff --git a/docs/dev-portal/channels/DEV-002/README.md b/docs/dev-portal/channels/DEV-002/README.md
new file mode 100644
index 00000000..5611f9cd
--- /dev/null
+++ b/docs/dev-portal/channels/DEV-002/README.md
@@ -0,0 +1 @@
+# DEV-002 频道沙箱
diff --git a/docs/dev-portal/channels/DEV-003/README.md b/docs/dev-portal/channels/DEV-003/README.md
new file mode 100644
index 00000000..49de9350
--- /dev/null
+++ b/docs/dev-portal/channels/DEV-003/README.md
@@ -0,0 +1 @@
+# DEV-003 频道沙箱
diff --git a/docs/dev-portal/channels/DEV-004/README.md b/docs/dev-portal/channels/DEV-004/README.md
new file mode 100644
index 00000000..19b532f0
--- /dev/null
+++ b/docs/dev-portal/channels/DEV-004/README.md
@@ -0,0 +1 @@
+# DEV-004 频道沙箱
diff --git a/docs/dev-portal/channels/DEV-005/README.md b/docs/dev-portal/channels/DEV-005/README.md
new file mode 100644
index 00000000..e6bea6d1
--- /dev/null
+++ b/docs/dev-portal/channels/DEV-005/README.md
@@ -0,0 +1 @@
+# DEV-005 频道沙箱
diff --git a/docs/dev-portal/channels/DEV-009/README.md b/docs/dev-portal/channels/DEV-009/README.md
new file mode 100644
index 00000000..83fcb531
--- /dev/null
+++ b/docs/dev-portal/channels/DEV-009/README.md
@@ -0,0 +1 @@
+# DEV-009 频道沙箱
diff --git a/docs/dev-portal/channels/DEV-010/README.md b/docs/dev-portal/channels/DEV-010/README.md
new file mode 100644
index 00000000..4febf40f
--- /dev/null
+++ b/docs/dev-portal/channels/DEV-010/README.md
@@ -0,0 +1 @@
+# DEV-010 频道沙箱
diff --git a/docs/dev-portal/channels/DEV-011/README.md b/docs/dev-portal/channels/DEV-011/README.md
new file mode 100644
index 00000000..ae9ac4e4
--- /dev/null
+++ b/docs/dev-portal/channels/DEV-011/README.md
@@ -0,0 +1 @@
+# DEV-011 频道沙箱
diff --git a/docs/dev-portal/channels/DEV-012/README.md b/docs/dev-portal/channels/DEV-012/README.md
new file mode 100644
index 00000000..9aefb87e
--- /dev/null
+++ b/docs/dev-portal/channels/DEV-012/README.md
@@ -0,0 +1 @@
+# DEV-012 频道沙箱
diff --git a/docs/dev-portal/channels/DEV-013/README.md b/docs/dev-portal/channels/DEV-013/README.md
new file mode 100644
index 00000000..de7e00a0
--- /dev/null
+++ b/docs/dev-portal/channels/DEV-013/README.md
@@ -0,0 +1 @@
+# DEV-013 频道沙箱
diff --git a/docs/dev-portal/channels/DEV-014/README.md b/docs/dev-portal/channels/DEV-014/README.md
new file mode 100644
index 00000000..425b67a1
--- /dev/null
+++ b/docs/dev-portal/channels/DEV-014/README.md
@@ -0,0 +1 @@
+# DEV-014 频道沙箱
diff --git a/docs/dev-portal/index.html b/docs/dev-portal/index.html
new file mode 100644
index 00000000..8db38ed0
--- /dev/null
+++ b/docs/dev-portal/index.html
@@ -0,0 +1,341 @@
+
+
+
+
+
+
+ 🌊 光湖开发者门户 · Developer Portal
+
+
+
+
+
+
+
+
+
+
+📡 开发者频道
+
+
+📋 最近部署动态
+
+
暂无部署记录 — 等待第一位开发者部署模块 🚀
+
+
+
+
+
+
+
+
diff --git a/docs/dev-portal/manifest.json b/docs/dev-portal/manifest.json
new file mode 100644
index 00000000..dfa8e1f4
--- /dev/null
+++ b/docs/dev-portal/manifest.json
@@ -0,0 +1,108 @@
+{
+ "portal_version": "1.0.0",
+ "updated_at": "2026-03-25T15:00:00Z",
+ "updated_by": "system",
+ "description": "光湖开发者门户 · 频道状态清单",
+ "channels": {
+ "DEV-001": {
+ "name": "页页",
+ "persona_id": "PER-001",
+ "status": "ready",
+ "modules": [],
+ "last_deploy": null,
+ "last_deploy_summary": null,
+ "deploy_count": 0
+ },
+ "DEV-002": {
+ "name": "肥猫",
+ "persona_id": "PER-002",
+ "status": "ready",
+ "modules": [],
+ "last_deploy": null,
+ "last_deploy_summary": null,
+ "deploy_count": 0
+ },
+ "DEV-003": {
+ "name": "燕樊",
+ "persona_id": "PER-003",
+ "status": "ready",
+ "modules": [],
+ "last_deploy": null,
+ "last_deploy_summary": null,
+ "deploy_count": 0
+ },
+ "DEV-004": {
+ "name": "之之",
+ "persona_id": "PER-004",
+ "status": "ready",
+ "modules": [],
+ "last_deploy": null,
+ "last_deploy_summary": null,
+ "deploy_count": 0
+ },
+ "DEV-005": {
+ "name": "小草莓",
+ "persona_id": "PER-005",
+ "status": "ready",
+ "modules": [],
+ "last_deploy": null,
+ "last_deploy_summary": null,
+ "deploy_count": 0
+ },
+ "DEV-009": {
+ "name": "花尔",
+ "persona_id": "PER-009",
+ "status": "ready",
+ "modules": [],
+ "last_deploy": null,
+ "last_deploy_summary": null,
+ "deploy_count": 0
+ },
+ "DEV-010": {
+ "name": "桔子",
+ "persona_id": "PER-010",
+ "status": "ready",
+ "modules": [],
+ "last_deploy": null,
+ "last_deploy_summary": null,
+ "deploy_count": 0
+ },
+ "DEV-011": {
+ "name": "匆匆那年",
+ "persona_id": "PER-011",
+ "status": "ready",
+ "modules": [],
+ "last_deploy": null,
+ "last_deploy_summary": null,
+ "deploy_count": 0
+ },
+ "DEV-012": {
+ "name": "Awen",
+ "persona_id": "PER-012",
+ "status": "ready",
+ "modules": [],
+ "last_deploy": null,
+ "last_deploy_summary": null,
+ "deploy_count": 0
+ },
+ "DEV-013": {
+ "name": "小兴",
+ "persona_id": "PER-013",
+ "status": "pending",
+ "modules": [],
+ "last_deploy": null,
+ "last_deploy_summary": null,
+ "deploy_count": 0
+ },
+ "DEV-014": {
+ "name": "时雨",
+ "persona_id": "PER-014",
+ "status": "pending",
+ "modules": [],
+ "last_deploy": null,
+ "last_deploy_summary": null,
+ "deploy_count": 0
+ }
+ },
+ "recent_activity": []
+}
diff --git a/scripts/skyeye/dev-portal-guard.js b/scripts/skyeye/dev-portal-guard.js
new file mode 100644
index 00000000..86076db6
--- /dev/null
+++ b/scripts/skyeye/dev-portal-guard.js
@@ -0,0 +1,296 @@
+#!/usr/bin/env node
+// scripts/skyeye/dev-portal-guard.js
+// 天眼·开发者门户沙箱守卫 (Dev Portal Sandbox Guard)
+//
+// ═══════════════════════════════════════════════
+// 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
+// 📜 Copyright: 国作登字-2026-A-00037559
+// ═══════════════════════════════════════════════
+//
+// 目的:保护开发者门户的沙箱隔离
+// 每位开发者只能部署到自己的频道 (channels/DEV-XXX/)
+// 门户框架文件 (index.html, manifest.json) 受保护
+// 必须提供完整身份三要素:开发者编号 + 姓名 + 人格体编号
+// 匿名代理(无身份信息)= 禁止部署到任何真实路径
+//
+// 输入环境变量:
+// PR_BRANCH — 分支名
+// PR_TITLE — PR 标题
+// PR_COMMITS — Commit 信息路径
+// PR_FILES — 变更文件列表路径
+// NOTION_API_KEY — Notion API 密钥(可选,用于跟踪)
+// DEV_PROFILE_DB_ID — 开发者画像数据库 ID(可选)
+//
+// 输出:
+// exit 0 → 允许部署
+// exit 1 → 禁止部署
+// GITHUB_OUTPUT → decision, dev_id, dev_name, persona_id, channel_files
+
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+
+const ROOT = path.resolve(__dirname, '../..');
+const BRAIN_CONFIG_PATH = path.join(ROOT, '.github/persona-brain/gate-guard-config.json');
+const MANIFEST_PATH = path.join(ROOT, 'docs/dev-portal/manifest.json');
+const PR_FILES_PATH = process.env.PR_FILES || '/tmp/pr_files.txt';
+const PR_COMMITS_PATH = process.env.PR_COMMITS || '/tmp/pr_commits.txt';
+const GITHUB_OUTPUT = process.env.GITHUB_OUTPUT || '/dev/null';
+
+// ━━━ 门户保护文件 — 开发者不可修改 ━━━
+const PORTAL_FRAMEWORK_FILES = [
+ 'docs/dev-portal/index.html',
+ 'docs/dev-portal/manifest.json'
+];
+
+// ━━━ 安全读取 JSON ━━━
+function readJSON(filePath) {
+ try {
+ if (!fs.existsSync(filePath)) return null;
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
+ } catch (e) {
+ return null;
+ }
+}
+
+function setOutput(key, value) {
+ try {
+ fs.appendFileSync(GITHUB_OUTPUT, `${key}=${value}\n`);
+ } catch (e) { /* silent */ }
+}
+
+// ━━━ 从 PR 元数据中提取身份三要素 ━━━
+function extractIdentity(prTitle, commitsPath) {
+ const identity = {
+ devId: null,
+ devName: null,
+ personaId: null,
+ complete: false,
+ source: []
+ };
+
+ // From PR title
+ if (prTitle) {
+ const devMatch = prTitle.match(/\b(DEV-\d{3})\b/i);
+ if (devMatch) { identity.devId = devMatch[1].toUpperCase(); identity.source.push('pr_title'); }
+ }
+
+ // From commit messages
+ try {
+ if (fs.existsSync(commitsPath)) {
+ const content = fs.readFileSync(commitsPath, 'utf8');
+
+ // DEV-XXX
+ if (!identity.devId) {
+ const devMatch = content.match(/\b(DEV-\d{3})\b/i);
+ if (devMatch) { identity.devId = devMatch[1].toUpperCase(); identity.source.push('commit_msg'); }
+ }
+
+ // Persona signature [PER-XXX]
+ const perMatch = content.match(/\[(PER-\d{3})\]/);
+ if (perMatch) { identity.personaId = perMatch[1]; identity.source.push('commit_sig'); }
+
+ // TCS-GL-XX format
+ const tcsMatch = content.match(/\b(TCS-GL-\d{2})\b/i);
+ if (tcsMatch && !identity.devId) { identity.devId = tcsMatch[1].toUpperCase(); identity.source.push('tcs_ref'); }
+ }
+ } catch (e) { /* non-fatal */ }
+
+ // From changed file paths — infer DEV-XXX from channel paths
+ try {
+ if (fs.existsSync(PR_FILES_PATH)) {
+ const files = fs.readFileSync(PR_FILES_PATH, 'utf8').split('\n').filter(Boolean);
+ for (const file of files) {
+ const channelMatch = file.match(/^docs\/dev-portal\/channels\/(DEV-\d{3})\//);
+ if (channelMatch) {
+ if (!identity.devId) {
+ identity.devId = channelMatch[1];
+ identity.source.push('file_path');
+ }
+ break;
+ }
+ }
+ }
+ } catch (e) { /* non-fatal */ }
+
+ // Cross-reference with gate-guard config for name and persona_id
+ if (identity.devId) {
+ const config = readJSON(BRAIN_CONFIG_PATH);
+ if (config && config.developer_permissions && config.developer_permissions[identity.devId]) {
+ const dev = config.developer_permissions[identity.devId];
+ identity.devName = dev.name;
+ if (!identity.personaId) identity.personaId = dev.persona_id;
+ }
+ }
+
+ // Completeness check: need all three (devId + devName + personaId)
+ identity.complete = !!(identity.devId && identity.devName && identity.personaId);
+
+ return identity;
+}
+
+// ━━━ 读取变更文件列表 ━━━
+function loadPRFiles() {
+ try {
+ if (!fs.existsSync(PR_FILES_PATH)) return [];
+ return fs.readFileSync(PR_FILES_PATH, 'utf8')
+ .split('\n')
+ .map(f => f.trim())
+ .filter(f => f.length > 0);
+ } catch (e) {
+ return [];
+ }
+}
+
+// ━━━ 分类文件 ━━━
+function classifyFiles(files) {
+ const result = {
+ portalFramework: [], // 门户框架文件(保护)
+ ownChannel: [], // 自己频道的文件
+ otherChannels: [], // 他人频道的文件
+ outsidePortal: [], // 门户以外的文件
+ channelDevIds: new Set() // 涉及的频道编号
+ };
+
+ for (const file of files) {
+ if (PORTAL_FRAMEWORK_FILES.includes(file)) {
+ result.portalFramework.push(file);
+ } else if (file.startsWith('docs/dev-portal/channels/')) {
+ const channelMatch = file.match(/^docs\/dev-portal\/channels\/(DEV-\d{3})\//);
+ if (channelMatch) {
+ result.channelDevIds.add(channelMatch[1]);
+ result.ownChannel.push(file); // Will be re-classified after identity check
+ }
+ } else if (file.startsWith('docs/dev-portal/')) {
+ result.portalFramework.push(file); // Any other dev-portal file is framework
+ } else {
+ result.outsidePortal.push(file);
+ }
+ }
+
+ return result;
+}
+
+// ━━━ 主逻辑 ━━━
+function run() {
+ const branch = process.env.PR_BRANCH || '';
+ const prTitle = process.env.PR_TITLE || '';
+
+ console.log('═══════════════════════════════════════════════');
+ console.log('🛡️ 天眼 · 开发者门户沙箱守卫');
+ console.log('═══════════════════════════════════════════════');
+ console.log(`分支: ${branch}`);
+ console.log(`标题: ${prTitle}`);
+ console.log(`时间: ${new Date().toISOString()}`);
+ console.log('');
+
+ // ─── 提取身份三要素 ───
+ console.log('━━━ 身份验证 ━━━');
+ const identity = extractIdentity(prTitle, PR_COMMITS_PATH);
+
+ if (identity.devId) console.log(` 开发者编号: ${identity.devId}`);
+ if (identity.devName) console.log(` 开发者姓名: ${identity.devName}`);
+ if (identity.personaId) console.log(` 人格体编号: ${identity.personaId}`);
+ console.log(` 身份来源: ${identity.source.join(', ') || '无'}`);
+ console.log(` 三要素完整: ${identity.complete ? '✅ 是' : '❌ 否'}`);
+ console.log('');
+
+ // ─── 匿名屏蔽 ───
+ if (!identity.complete) {
+ console.log('❌ 身份验证失败 — 匿名代理禁止部署');
+ console.log('');
+ console.log(' 缺失信息:');
+ if (!identity.devId) console.log(' · 开发者编号 (DEV-XXX)');
+ if (!identity.devName) console.log(' · 开发者姓名');
+ if (!identity.personaId) console.log(' · 人格体编号 (PER-XXX)');
+ console.log('');
+ console.log(' 提示: 在 PR 标题或 commit 信息中包含 DEV-XXX 和 [PER-XXX]');
+ console.log(' 例: "DEV-002: deploy login module" 或 "[PER-002] feat: add feature"');
+ setOutput('decision', 'block');
+ setOutput('block_reason', 'anonymous_agent');
+ process.exit(1);
+ }
+
+ // ─── 分类变更文件 ───
+ const files = loadPRFiles();
+ if (files.length === 0) {
+ console.log('⚠️ 无变更文件 — 放行');
+ setOutput('decision', 'pass');
+ setOutput('dev_id', identity.devId);
+ process.exit(0);
+ }
+
+ const classified = classifyFiles(files);
+ const portalFiles = files.filter(f => f.startsWith('docs/dev-portal/'));
+
+ if (portalFiles.length === 0) {
+ console.log('ℹ️ 此 PR 不涉及门户文件 — 跳过门户守卫');
+ setOutput('decision', 'skip');
+ setOutput('dev_id', identity.devId);
+ process.exit(0);
+ }
+
+ console.log('━━━ 文件分类 ━━━');
+ console.log(` 门户框架文件: ${classified.portalFramework.length}`);
+ classified.portalFramework.forEach(f => console.log(` 🔒 ${f}`));
+ console.log(` 频道文件: ${classified.ownChannel.length}`);
+ console.log(` 涉及频道: ${[...classified.channelDevIds].join(', ') || '无'}`);
+ console.log(` 门户外文件: ${classified.outsidePortal.length}`);
+ console.log('');
+
+ // ─── 检查框架文件保护 ───
+ if (classified.portalFramework.length > 0) {
+ console.log('❌ 禁止修改门户框架文件:');
+ classified.portalFramework.forEach(f => console.log(` 🔒 ${f}`));
+ console.log('');
+ console.log(' 门户框架文件由天眼系统维护,开发者不可修改');
+ setOutput('decision', 'block');
+ setOutput('block_reason', 'framework_tampering');
+ setOutput('dev_id', identity.devId);
+ process.exit(1);
+ }
+
+ // ─── 检查频道隔离 ───
+ const channelIds = [...classified.channelDevIds];
+ const touchesOtherChannels = channelIds.some(id => id !== identity.devId);
+
+ if (touchesOtherChannels) {
+ const otherChannels = channelIds.filter(id => id !== identity.devId);
+ console.log('❌ 频道隔离违规 — 修改了他人频道:');
+ otherChannels.forEach(id => console.log(` 🚫 ${id} (不属于 ${identity.devId})`));
+ console.log('');
+ console.log(' 每位开发者只能部署到自己的频道沙箱');
+ setOutput('decision', 'block');
+ setOutput('block_reason', 'channel_isolation');
+ setOutput('dev_id', identity.devId);
+ process.exit(1);
+ }
+
+ // ─── 验证频道归属 ───
+ if (channelIds.length === 1 && channelIds[0] === identity.devId) {
+ const channelFiles = classified.ownChannel;
+ console.log(`✅ 频道验证通过 — ${identity.devName}(${identity.devId}) → channels/${identity.devId}/`);
+ console.log(` 部署文件: ${channelFiles.length}`);
+ channelFiles.forEach(f => console.log(` 📦 ${f}`));
+ console.log('');
+
+ setOutput('decision', 'pass');
+ setOutput('dev_id', identity.devId);
+ setOutput('dev_name', identity.devName);
+ setOutput('persona_id', identity.personaId);
+ setOutput('channel_files', channelFiles.join(','));
+
+ console.log('═══ 决策: PASS ═══');
+ console.log(` ${identity.devName} 的模块可以部署到 channels/${identity.devId}/`);
+ process.exit(0);
+ }
+
+ // Default pass for edge cases
+ console.log('✅ 门户守卫检查通过');
+ setOutput('decision', 'pass');
+ setOutput('dev_id', identity.devId);
+ process.exit(0);
+}
+
+run();
diff --git a/scripts/skyeye/dev-portal-notion-sync.js b/scripts/skyeye/dev-portal-notion-sync.js
new file mode 100644
index 00000000..3a4a1e9b
--- /dev/null
+++ b/scripts/skyeye/dev-portal-notion-sync.js
@@ -0,0 +1,193 @@
+#!/usr/bin/env node
+// scripts/skyeye/dev-portal-notion-sync.js
+// 开发者门户 · Notion 动态同步
+//
+// ═══════════════════════════════════════════════
+// 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
+// 📜 Copyright: 国作登字-2026-A-00037559
+// ═══════════════════════════════════════════════
+//
+// 目的:将开发者频道部署动态同步到 Notion 画像数据库
+// 铁律(与 write-receipt-to-notion.js 一致):
+// 1. 写入失败不阻塞主流程
+// 2. 同一事件幂等
+// 3. 失败时写入本地 SYSLOG
+//
+// 环境变量:
+// NOTION_API_KEY — Notion API 密钥(必须)
+// DEV_PROFILE_DB_ID — 开发者画像数据库 ID(必须)
+//
+// 参数:
+// --dev-ids "DEV-001,DEV-002" — 涉及的开发者编号
+// --action "channel_deploy" — 操作类型
+// --timestamp "ISO-8601" — 时间戳
+
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+const https = require('https');
+
+const NOTION_API_KEY = process.env.NOTION_API_KEY || '';
+const DEV_PROFILE_DB_ID = process.env.DEV_PROFILE_DB_ID || '';
+const ROOT = path.resolve(__dirname, '../..');
+const SYSLOG_DIR = path.join(ROOT, 'data/neural-reports/syslog');
+
+// ━━━ 参数解析 ━━━
+function parseArgs() {
+ const args = process.argv.slice(2);
+ const result = { devIds: [], action: 'unknown', timestamp: new Date().toISOString() };
+
+ for (let i = 0; i < args.length; i++) {
+ if (args[i] === '--dev-ids' && args[i + 1]) {
+ result.devIds = args[++i].split(',').filter(Boolean);
+ } else if (args[i] === '--action' && args[i + 1]) {
+ result.action = args[++i];
+ } else if (args[i] === '--timestamp' && args[i + 1]) {
+ result.timestamp = args[++i];
+ }
+ }
+
+ return result;
+}
+
+// ━━━ 加载门禁配置获取开发者信息 ━━━
+function loadDevInfo(devId) {
+ try {
+ const configPath = path.join(ROOT, '.github/persona-brain/gate-guard-config.json');
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
+ const dev = config.developer_permissions && config.developer_permissions[devId];
+ return dev || null;
+ } catch (e) {
+ return null;
+ }
+}
+
+// ━━━ Notion API 请求 ━━━
+function notionRequest(endpoint, body) {
+ return new Promise(function(resolve, reject) {
+ if (!NOTION_API_KEY) {
+ reject(new Error('NOTION_API_KEY 未配置'));
+ return;
+ }
+
+ const data = JSON.stringify(body);
+ const options = {
+ hostname: 'api.notion.com',
+ path: endpoint,
+ method: 'POST',
+ headers: {
+ 'Authorization': 'Bearer ' + NOTION_API_KEY,
+ 'Notion-Version': '2022-06-28',
+ 'Content-Type': 'application/json',
+ 'Content-Length': Buffer.byteLength(data)
+ }
+ };
+
+ const req = https.request(options, function(res) {
+ let body = '';
+ res.on('data', function(chunk) { body += chunk; });
+ res.on('end', function() {
+ if (res.statusCode >= 200 && res.statusCode < 300) {
+ resolve(JSON.parse(body));
+ } else {
+ reject(new Error('Notion API ' + res.statusCode + ': ' + body.slice(0, 200)));
+ }
+ });
+ });
+
+ req.on('error', reject);
+ req.setTimeout(15000, function() { req.destroy(new Error('Notion API timeout')); });
+ req.write(data);
+ req.end();
+ });
+}
+
+// ━━━ 写入 SYSLOG ━━━
+function writeSyslog(entry) {
+ try {
+ if (!fs.existsSync(SYSLOG_DIR)) fs.mkdirSync(SYSLOG_DIR, { recursive: true });
+ const date = new Date().toISOString().slice(0, 10);
+ const logPath = path.join(SYSLOG_DIR, 'dev-portal-sync-' + date + '.jsonl');
+ fs.appendFileSync(logPath, JSON.stringify(entry) + '\n');
+ } catch (e) {
+ console.error('SYSLOG写入失败:', e.message);
+ }
+}
+
+// ━━━ 同步单个开发者动态到 Notion ━━━
+async function syncDevActivity(devId, action, timestamp) {
+ const devInfo = loadDevInfo(devId);
+
+ const properties = {
+ '开发者编号': { title: [{ text: { content: devId } }] },
+ '开发者姓名': { rich_text: [{ text: { content: devInfo ? devInfo.name : devId } }] },
+ '人格体编号': { rich_text: [{ text: { content: devInfo ? devInfo.persona_id : '—' } }] },
+ '操作类型': { select: { name: action } },
+ '操作时间': { date: { start: timestamp } },
+ '门户频道': { rich_text: [{ text: { content: 'channels/' + devId + '/' } }] }
+ };
+
+ try {
+ await notionRequest('/v1/pages', {
+ parent: { database_id: DEV_PROFILE_DB_ID },
+ properties: properties
+ });
+ console.log(' ✅ ' + devId + ' 同步成功');
+ return true;
+ } catch (e) {
+ console.error(' ⚠️ ' + devId + ' 同步失败: ' + e.message);
+ writeSyslog({
+ time: new Date().toISOString(),
+ type: 'notion_sync_error',
+ dev_id: devId,
+ error: e.message
+ });
+ return false;
+ }
+}
+
+// ━━━ 主逻辑 ━━━
+async function main() {
+ const args = parseArgs();
+
+ console.log('📡 开发者门户 · Notion 动态同步');
+ console.log(' 开发者: ' + (args.devIds.join(', ') || '无'));
+ console.log(' 操作: ' + args.action);
+ console.log(' 时间: ' + args.timestamp);
+ console.log('');
+
+ if (args.devIds.length === 0) {
+ console.log('⚠️ 无开发者编号,跳过同步');
+ process.exit(0);
+ }
+
+ if (!NOTION_API_KEY || !DEV_PROFILE_DB_ID) {
+ console.log('⚠️ Notion 配置缺失 (NOTION_API_KEY / DEV_PROFILE_DB_ID)');
+ console.log(' 动态将写入本地 SYSLOG');
+ writeSyslog({
+ time: args.timestamp,
+ type: 'channel_deploy',
+ dev_ids: args.devIds,
+ action: args.action,
+ note: 'Notion未配置,写入本地'
+ });
+ process.exit(0);
+ }
+
+ let success = 0;
+ for (const devId of args.devIds) {
+ const ok = await syncDevActivity(devId, args.action, args.timestamp);
+ if (ok) success++;
+ }
+
+ console.log('');
+ console.log('同步结果: ' + success + '/' + args.devIds.length + ' 成功');
+}
+
+main().catch(function(e) {
+ console.error('❌ 同步异常: ' + e.message);
+ writeSyslog({ time: new Date().toISOString(), type: 'sync_crash', error: e.message });
+ // Non-fatal — don't block the deploy
+ process.exit(0);
+});
diff --git a/scripts/skyeye/pr-risk-check.js b/scripts/skyeye/pr-risk-check.js
index f9f7dca3..616c5147 100644
--- a/scripts/skyeye/pr-risk-check.js
+++ b/scripts/skyeye/pr-risk-check.js
@@ -1,52 +1,45 @@
#!/usr/bin/env node
// scripts/skyeye/pr-risk-check.js
-// 天眼·合并膜 — PR合并前风险检查引擎 (SkyEye Merge Membrane)
+// 天眼·合并膜 — PR合并前全系统审核引擎 (SkyEye Merge Membrane)
//
// ═══════════════════════════════════════════════
// 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
// 📜 Copyright: 国作登字-2026-A-00037559
// ═══════════════════════════════════════════════
//
+// 核心原则(冰朔 2026-03-25 确认):
+// 所有合并到 main 的 PR — 无论冰朔还是其他开发者
+// 均全部启动天眼系统全局全仓库架构审核
+// 不符合现有仓库整体系统结构 → 物理切断合并通道
+// 不再区分身份放行 — 天眼对所有人一视同仁
+//
// 架构背景:
-// 所有开发者共用 qinfendebingshuo 账号(企业权限单人仓库)
-// 代理(Copilot Agent)以 copilot/* 分支开发,通过 PR 合并
-// 无法通过 GitHub username 区分身份 → 改用分支名/commit元数据识别
+// 两条完全分离的部署路径:
+// Path A · 主站 (guanghulab.com) — 全PR审核,物理阻塞
+// Path B · 开发者门户 (dev-portal/) — 沙箱隔离,频道部署
//
-// 沙箱隔离原则:
-// 开发者在各自分支自由开发(沙箱)→ 天眼不干预
-// 合并到 main 时 → 天眼合并膜启动,全系统审核
-// 天眼审核不通过 → 物理层拒绝合并(exit 1 = GitHub Status Check 失败)
-//
-// 身份识别(不依赖 GitHub username):
-// I1 · 分支名模式匹配(copilot/* = 代理PR)
-// I2 · Commit 签名提取(Co-authored-by, Agent-Logs-Url)
-// I3 · PR 标题/描述中的开发者编号(DEV-XXX, TCS-GL-XX)
-//
-// 风险检测维度:
-// R1 · 关键文件覆盖检测(docs/index.html 等核心文件)
-// R2 · 天眼系统包裹区域入侵检测(.github/, scripts/, skyeye/ 等)
-// R3 · 构建产物误入检测(Vite/Webpack hash文件)
-// R4 · 天眼核心配置篡改检测(security-protocol, gate-guard-config 等)
-// R5 · 大规模删除检测(>500行删除)
-// R6 · 工作流篡改检测(.github/workflows/ 修改)
+// 风险检测维度(全PR适用):
+// R1 · 关键文件覆盖检测
+// R2 · 构建产物误入检测
+// R3 · 大规模删除检测
+// R4 · 天眼核心配置篡改检测(仅代理PR触发 critical)
+// R5 · 工作流篡改检测(仅代理PR触发 critical)
+// R6 · 开发者门户框架保护
+// R7 · 仓库结构完整性验证(全PR适用 — 核心审计维度)
//
// 输入环境变量:
-// PR_AUTHOR — PR 作者 GitHub username
-// PR_BRANCH — PR 源分支名
-// PR_TITLE — PR 标题
-// PR_FILES — 变更文件列表路径(默认 /tmp/pr_files.txt)
-// PR_STATS — 变更统计路径(默认 /tmp/pr_stats.txt)
-// PR_COMMITS — PR commit 信息路径(默认 /tmp/pr_commits.txt)
+// PR_AUTHOR, PR_BRANCH, PR_TITLE
+// PR_FILES, PR_STATS, PR_COMMITS
//
// 输出:
// exit 0 → pass(允许合并)
// exit 1 → block(物理层拒绝合并)
-// GITHUB_OUTPUT → risk_level, risk_summary, decision, identity_source
'use strict';
const fs = require('fs');
const path = require('path');
+const { execSync } = require('child_process');
// ━━━ 配置路径 ━━━
const ROOT = path.resolve(__dirname, '../..');
@@ -58,41 +51,14 @@ const PR_STATS_PATH = process.env.PR_STATS || '/tmp/pr_stats.txt';
const PR_COMMITS_PATH = process.env.PR_COMMITS || '/tmp/pr_commits.txt';
const GITHUB_OUTPUT = process.env.GITHUB_OUTPUT || '/dev/null';
-// ━━━ 仓库主人 ━━━
-const REPO_OWNER = 'qinfendebingshuo';
-
// ━━━ Copilot Agent 分支模式 ━━━
const AGENT_BRANCH_PATTERNS = [
- /^copilot\//, // GitHub Copilot agent branches
- /^agent\//, // Generic agent branches
- /^bot\// // Bot branches
+ /^copilot\//,
+ /^agent\//,
+ /^bot\//
];
-// ━━━ 天眼系统包裹区域 — 这些路径构成天眼保护的核心系统 ━━━
-const SKYEYE_WRAPPED_PATHS = [
- '.github/workflows/',
- '.github/persona-brain/',
- '.github/brain/',
- '.github/gate-guard-config.json',
- 'scripts/',
- 'skyeye/',
- 'core/',
- 'connectors/',
- 'backend/api-server/',
- 'docs/index.html',
- 'docs/CNAME',
- 'docs/.nojekyll',
- 'docs/js/',
- 'docs/css/',
- 'docs/dashboard/',
- 'data/system-health.json',
- 'data/bulletin-board.json',
- 'README.md',
- 'package.json',
- 'package-lock.json'
-];
-
-// ━━━ 天眼核心配置 — 最高保护等级,任何修改都触发 block ━━━
+// ━━━ 天眼核心配置 — 代理PR修改触发 critical ━━━
const SKYEYE_CORE_FILES = [
'.github/persona-brain/security-protocol.json',
'.github/persona-brain/gate-guard-config.json',
@@ -104,6 +70,12 @@ const SKYEYE_CORE_FILES = [
'scripts/skyeye/pr-risk-check.js'
];
+// ━━━ 门户框架文件 — 不可被开发者修改 ━━━
+const PORTAL_FRAMEWORK_FILES = [
+ 'docs/dev-portal/index.html',
+ 'docs/dev-portal/manifest.json'
+];
+
// ━━━ 构建产物特征 ━━━
const BUILD_ARTIFACT_PATTERNS = [
/^docs\/assets\/index-[A-Za-z0-9_-]+\.(js|css)$/,
@@ -115,10 +87,35 @@ const BUILD_ARTIFACT_PATTERNS = [
/\.bundle\.(js|css)$/
];
+// ━━━ 仓库必需目录 — R7 结构完整性检查 ━━━
+const REQUIRED_DIRS = [
+ '.github/workflows',
+ '.github/persona-brain',
+ 'scripts',
+ 'scripts/skyeye',
+ 'skyeye',
+ 'skyeye/guards',
+ 'skyeye/scripts',
+ 'core',
+ 'docs',
+ 'data'
+];
+
+// ━━━ 仓库必需文件 — R7 结构完整性检查 ━━━
+const REQUIRED_FILES = [
+ '.github/persona-brain/security-protocol.json',
+ '.github/persona-brain/gate-guard-config.json',
+ '.github/persona-brain/agent-registry.json',
+ 'docs/index.html',
+ 'docs/CNAME',
+ 'README.md',
+ 'package.json'
+];
+
// ━━━ 大规模删除阈值 ━━━
const MASS_DELETE_THRESHOLD = 500;
-// ━━━ 安全读取 JSON ━━━
+// ━━━ 工具函数 ━━━
function readJSON(filePath) {
try {
if (!fs.existsSync(filePath)) return null;
@@ -129,126 +126,25 @@ function readJSON(filePath) {
}
}
-// ━━━ 输出到 GITHUB_OUTPUT ━━━
function setOutput(key, value) {
try {
fs.appendFileSync(GITHUB_OUTPUT, `${key}=${value}\n`);
- } catch (e) {
- // silent fail for local testing
- }
+ } catch (e) { /* silent */ }
}
-// ━━━ I1 · 检测是否为代理(Agent)PR ━━━
function isAgentBranch(branchName) {
if (!branchName) return false;
return AGENT_BRANCH_PATTERNS.some(p => p.test(branchName));
}
-// ━━━ I2 · 从 commit 元数据提取代理身份 ━━━
-function extractAgentIdentity(commitsPath, prTitle) {
- const identity = {
- isAgent: false,
- agentName: null,
- devId: null,
- devName: null,
- source: 'unknown'
- };
-
- // Check PR title for dev identifiers
- if (prTitle) {
- const devMatch = prTitle.match(/\b(DEV-\d{3})\b/i);
- const tcsMatch = prTitle.match(/\b(TCS-GL-\d{2})\b/i);
- if (devMatch) {
- identity.devId = devMatch[1].toUpperCase();
- identity.source = 'pr_title';
- }
- if (tcsMatch) {
- identity.devId = tcsMatch[1].toUpperCase();
- identity.source = 'pr_title';
- }
- }
-
- // Check commit messages
- try {
- if (fs.existsSync(commitsPath)) {
- const content = fs.readFileSync(commitsPath, 'utf8');
-
- // Agent-Logs-Url indicates Copilot agent
- if (content.includes('Agent-Logs-Url:')) {
- identity.isAgent = true;
- identity.source = 'agent_logs_url';
- }
-
- // Co-authored-by pattern
- const coAuthorMatch = content.match(/Co-authored-by:\s*(.+?)\s*);
- if (coAuthorMatch) {
- identity.agentName = coAuthorMatch[1].trim();
- identity.isAgent = true;
- if (identity.source === 'unknown') identity.source = 'co_authored_by';
- }
-
- // Persona signature in commit messages [PER-XXX] [TCS-XXX]
- const sigMatch = content.match(/^\[([A-Z]+-[A-Z0-9\-∞]+)\]/m);
- if (sigMatch) {
- identity.devId = identity.devId || sigMatch[1];
- if (identity.source === 'unknown') identity.source = 'commit_signature';
- }
-
- // DEV-XXX in commit messages
- const devInCommit = content.match(/\b(DEV-\d{3})\b/i);
- if (devInCommit && !identity.devId) {
- identity.devId = devInCommit[1].toUpperCase();
- if (identity.source === 'unknown') identity.source = 'commit_message';
- }
- }
- } catch (e) {
- // Non-fatal - continue without commit analysis
- }
-
- return identity;
-}
-
-// ━━━ 加载门禁配置 ━━━
-function loadConfig() {
- const brainConfig = readJSON(BRAIN_CONFIG_PATH);
- const ownerConfig = readJSON(OWNER_CONFIG_PATH);
-
- if (!brainConfig && !ownerConfig) {
- console.error('⚠️ 门禁配置缺失,使用最小安全配置');
- return {
- whitelist: ['github-actions[bot]', 'zhuyuan-bot'],
- system_protected_paths: ['.github/', 'scripts/', 'docs/', 'data/', 'core/', 'connectors/'],
- developers: {}
- };
- }
-
- return {
- whitelist: (brainConfig?.whitelist_actors || ownerConfig?.whitelist || [])
- .filter(u => u !== REPO_OWNER), // Do NOT whitelist owner for agent PRs
- system_protected_paths: brainConfig?.system_protected_paths || ownerConfig?.protected_paths || [],
- developers_brain: brainConfig?.developer_permissions || {},
- developers_owner: ownerConfig?.developers || {}
- };
-}
-
-// ━━━ 读取 PR 变更文件列表 ━━━
function loadPRFiles() {
try {
- if (!fs.existsSync(PR_FILES_PATH)) {
- console.warn('⚠️ PR文件列表不存在: ' + PR_FILES_PATH);
- return [];
- }
+ if (!fs.existsSync(PR_FILES_PATH)) return [];
return fs.readFileSync(PR_FILES_PATH, 'utf8')
- .split('\n')
- .map(f => f.trim())
- .filter(f => f.length > 0);
- } catch (e) {
- console.error('⚠️ 无法读取PR文件列表: ' + e.message);
- return [];
- }
+ .split('\n').map(f => f.trim()).filter(f => f.length > 0);
+ } catch (e) { return []; }
}
-// ━━━ 读取 PR 变更统计 ━━━
function loadPRStats() {
try {
if (!fs.existsSync(PR_STATS_PATH)) return null;
@@ -262,37 +158,32 @@ function loadPRStats() {
}
}
return { totalDeletions };
- } catch (e) {
- return null;
- }
+ } catch (e) { return null; }
}
-// ━━━ 根据 devId 查找注册开发者 ━━━
-function findDeveloperByDevId(devId, config) {
- if (!devId) return null;
+// ━━━ 身份提取(用于日志/上下文,不用于放行决策)━━━
+function extractIdentity(commitsPath, prTitle) {
+ const identity = { isAgent: false, agentName: null, devId: null, source: 'unknown' };
- if (config.developers_brain) {
- for (const [id, dev] of Object.entries(config.developers_brain)) {
- if (id === devId) {
- return { devId: id, name: dev.name, allowed_paths: dev.allowed_paths || [] };
- }
- }
+ if (prTitle) {
+ const devMatch = prTitle.match(/\b(DEV-\d{3})\b/i);
+ if (devMatch) { identity.devId = devMatch[1].toUpperCase(); identity.source = 'pr_title'; }
}
- if (config.developers_owner) {
- for (const [, dev] of Object.entries(config.developers_owner)) {
- if (dev.dev_id === devId) {
- return { devId: dev.dev_id, name: dev.name, allowed_paths: dev.allowed_paths || [] };
+ try {
+ if (fs.existsSync(commitsPath)) {
+ const content = fs.readFileSync(commitsPath, 'utf8');
+ if (content.includes('Agent-Logs-Url:')) { identity.isAgent = true; identity.source = 'agent_logs_url'; }
+ const coAuth = content.match(/Co-authored-by:\s*(.+?)\s*);
+ if (coAuth) { identity.agentName = coAuth[1].trim(); identity.isAgent = true; }
+ if (!identity.devId) {
+ const devMatch = content.match(/\b(DEV-\d{3})\b/i);
+ if (devMatch) { identity.devId = devMatch[1].toUpperCase(); }
}
}
- }
+ } catch (e) { /* non-fatal */ }
- return null;
-}
-
-// ━━━ 检查文件是否在天眼包裹区域内 ━━━
-function isInSkyeyeZone(file) {
- return SKYEYE_WRAPPED_PATHS.some(p => file === p || file.startsWith(p));
+ return identity;
}
// ━━━ R1 · 关键文件覆盖检测 ━━━
@@ -304,61 +195,21 @@ function checkCriticalFiles(files) {
];
for (const file of files) {
if (criticalFiles.includes(file)) {
- risks.push({
- dimension: 'R1',
- severity: 'high',
- file,
- detail: `关键文件被修改: ${file}`
- });
+ risks.push({ dimension: 'R1', severity: 'high', file,
+ detail: `关键文件被修改: ${file}` });
}
}
return risks;
}
-// ━━━ R2 · 天眼包裹区域入侵检测 ━━━
-function checkSkyeyeZoneIntrusion(files, developer) {
- const risks = [];
-
- for (const file of files) {
- if (!isInSkyeyeZone(file)) continue;
-
- // If developer is identified, check allowed_paths
- if (developer) {
- const isAllowed = developer.allowed_paths.some(p => file.startsWith(p));
- if (!isAllowed) {
- risks.push({
- dimension: 'R2',
- severity: 'high',
- file,
- detail: `${developer.name}(${developer.devId}) 修改了天眼包裹区域: ${file}`
- });
- }
- } else {
- // Unknown identity touching SkyEye zone
- risks.push({
- dimension: 'R2',
- severity: 'high',
- file,
- detail: `代理PR修改天眼包裹区域: ${file}`
- });
- }
- }
-
- return risks;
-}
-
-// ━━━ R3 · 构建产物误入检测 ━━━
+// ━━━ R2 · 构建产物误入检测 ━━━
function checkBuildArtifacts(files) {
const risks = [];
for (const file of files) {
for (const pattern of BUILD_ARTIFACT_PATTERNS) {
if (pattern.test(file)) {
- risks.push({
- dimension: 'R3',
- severity: 'high',
- file,
- detail: `构建产物不应提交到仓库: ${file}`
- });
+ risks.push({ dimension: 'R2', severity: 'high', file,
+ detail: `构建产物不应提交到仓库: ${file}` });
break;
}
}
@@ -366,100 +217,153 @@ function checkBuildArtifacts(files) {
return risks;
}
-// ━━━ R4 · 天眼核心配置篡改检测 ━━━
-function checkCoreConfigTampering(files) {
- const risks = [];
- for (const file of files) {
- if (SKYEYE_CORE_FILES.includes(file)) {
- risks.push({
- dimension: 'R4',
- severity: 'critical',
- file,
- detail: `天眼核心配置被修改: ${file} — 此文件仅限 TCS-0002∞ 主权者修改`
- });
- }
- }
- return risks;
-}
-
-// ━━━ R5 · 大规模删除检测 ━━━
+// ━━━ R3 · 大规模删除检测 ━━━
function checkMassDeletion(stats) {
const risks = [];
if (stats && stats.totalDeletions > MASS_DELETE_THRESHOLD) {
- risks.push({
- dimension: 'R5',
- severity: 'high',
- file: '(multiple)',
- detail: `大规模删除检测: ${stats.totalDeletions} 行被删除(阈值: ${MASS_DELETE_THRESHOLD})`
- });
+ risks.push({ dimension: 'R3', severity: 'high', file: '(multiple)',
+ detail: `大规模删除: ${stats.totalDeletions} 行被删除(阈值: ${MASS_DELETE_THRESHOLD})` });
}
return risks;
}
-// ━━━ R6 · 工作流篡改检测 ━━━
-function checkWorkflowTampering(files) {
+// ━━━ R4 · 天眼核心配置篡改检测(代理PR = critical,手动PR = high) ━━━
+function checkCoreConfigTampering(files, isAgent) {
+ const risks = [];
+ for (const file of files) {
+ if (SKYEYE_CORE_FILES.includes(file)) {
+ risks.push({ dimension: 'R4', severity: isAgent ? 'critical' : 'high', file,
+ detail: `天眼核心配置被修改: ${file}` });
+ }
+ }
+ return risks;
+}
+
+// ━━━ R5 · 工作流篡改检测(代理PR = critical,手动PR = high) ━━━
+function checkWorkflowTampering(files, isAgent) {
const risks = [];
for (const file of files) {
if (file.startsWith('.github/workflows/') && (file.endsWith('.yml') || file.endsWith('.yaml'))) {
- risks.push({
- dimension: 'R6',
- severity: 'critical',
- file,
- detail: `工作流文件被修改: ${file} — 代理PR不应修改自动化管线`
- });
+ risks.push({ dimension: 'R5', severity: isAgent ? 'critical' : 'high', file,
+ detail: `工作流文件被修改: ${file}` });
}
}
return risks;
}
-// ━━━ 天眼系统完整性验证 ━━━
-function verifySkyeyeIntegrity() {
- const issues = [];
+// ━━━ R6 · 开发者门户框架保护 ━━━
+function checkPortalFramework(files, isAgent) {
+ if (!isAgent) return []; // Owner can modify portal framework
+ const risks = [];
+ for (const file of files) {
+ if (PORTAL_FRAMEWORK_FILES.includes(file)) {
+ risks.push({ dimension: 'R6', severity: 'critical', file,
+ detail: `门户框架文件被修改: ${file} — 仅限天眼系统维护` });
+ }
+ }
+ return risks;
+}
- // Check security protocol
+// ━━━ R7 · 仓库结构完整性验证(核心审计维度 — 对所有PR生效)━━━
+function checkStructuralIntegrity(files) {
+ const risks = [];
+
+ // 7a. Security protocol must remain intact
const protocol = readJSON(SECURITY_PROTOCOL_PATH);
if (!protocol) {
- issues.push('security-protocol.json 不存在或无法读取');
+ risks.push({ dimension: 'R7', severity: 'critical', file: '.github/persona-brain/security-protocol.json',
+ detail: '安全协议文件不存在或无法解析 — 仓库安全基础被破坏' });
} else {
- if (!protocol.permanent) issues.push('security-protocol.json: permanent 标记缺失');
+ if (!protocol.permanent || !protocol.cannot_be_overridden) {
+ risks.push({ dimension: 'R7', severity: 'critical', file: '.github/persona-brain/security-protocol.json',
+ detail: '安全协议 permanent/cannot_be_overridden 标记丢失' });
+ }
if (!protocol.root_rules || protocol.root_rules.length < 3) {
- issues.push('security-protocol.json: root_rules 不完整');
+ risks.push({ dimension: 'R7', severity: 'critical', file: '.github/persona-brain/security-protocol.json',
+ detail: '安全协议 root_rules 不完整(需 3 条根规则)' });
}
}
- // Check gate-guard configs
- if (!readJSON(BRAIN_CONFIG_PATH) && !readJSON(OWNER_CONFIG_PATH)) {
- issues.push('门禁配置文件全部缺失');
+ // 7b. Gate guard config must remain valid
+ const brainConfig = readJSON(BRAIN_CONFIG_PATH);
+ const ownerConfig = readJSON(OWNER_CONFIG_PATH);
+ if (!brainConfig && !ownerConfig) {
+ risks.push({ dimension: 'R7', severity: 'critical', file: '(gate-guard-config)',
+ detail: '门禁配置文件全部缺失 — 系统安全体系被破坏' });
}
- return issues;
+ // 7c. Check that required directories still exist
+ for (const dir of REQUIRED_DIRS) {
+ const dirPath = path.join(ROOT, dir);
+ if (!fs.existsSync(dirPath)) {
+ risks.push({ dimension: 'R7', severity: 'high', file: dir + '/',
+ detail: `必需目录缺失: ${dir}/` });
+ }
+ }
+
+ // 7d. Check that required files still exist
+ for (const file of REQUIRED_FILES) {
+ const filePath = path.join(ROOT, file);
+ if (!fs.existsSync(filePath)) {
+ risks.push({ dimension: 'R7', severity: 'high', file,
+ detail: `必需文件缺失: ${file}` });
+ }
+ }
+
+ // 7e. Check that PR doesn't DELETE required files
+ for (const file of files) {
+ if (REQUIRED_FILES.includes(file)) {
+ const filePath = path.join(ROOT, file);
+ // File is in the PR diff — check if it still exists in the working tree
+ // (if checkout has the merged version, missing = deleted)
+ if (!fs.existsSync(filePath)) {
+ risks.push({ dimension: 'R7', severity: 'critical', file,
+ detail: `PR删除了仓库必需文件: ${file}` });
+ }
+ }
+ }
+
+ // 7f. Run structural scan scripts if available
+ const scanScripts = [
+ { name: 'scan-structure', path: path.join(ROOT, 'scripts/skyeye/scan-structure.js') },
+ { name: 'scan-brain-health', path: path.join(ROOT, 'scripts/skyeye/scan-brain-health.js') },
+ { name: 'scan-security-protocol', path: path.join(ROOT, 'scripts/skyeye/scan-security-protocol.js') }
+ ];
+
+ for (const script of scanScripts) {
+ if (fs.existsSync(script.path)) {
+ try {
+ const output = execSync(`node "${script.path}" 2>&1`, {
+ cwd: ROOT, timeout: 30000, encoding: 'utf8'
+ });
+ // Check for failure indicators in output
+ if (output.includes('❌') || output.includes('FAIL') || output.includes('ERROR')) {
+ const failLines = output.split('\n').filter(l =>
+ l.includes('❌') || l.includes('FAIL') || l.includes('ERROR')
+ ).slice(0, 3);
+ risks.push({ dimension: 'R7', severity: 'high', file: `(${script.name})`,
+ detail: `结构扫描 ${script.name} 报告问题: ${failLines.join('; ').slice(0, 200)}` });
+ }
+ } catch (e) {
+ // Script execution failure is itself a structural concern
+ const errMsg = (e.stderr || e.message || '').slice(0, 100);
+ risks.push({ dimension: 'R7', severity: 'high', file: `(${script.name})`,
+ detail: `结构扫描 ${script.name} 执行失败: ${errMsg}` });
+ }
+ }
+ }
+
+ return risks;
}
-// ━━━ 风险评估决策 ━━━
-function makeDecision(risks, touchesSkyeyeZone) {
+// ━━━ 风险决策 ━━━
+function makeDecision(risks) {
const hasCritical = risks.some(r => r.severity === 'critical');
const highCount = risks.filter(r => r.severity === 'high').length;
- // Any critical risk = immediate block
- if (hasCritical) {
- return { decision: 'block', risk_level: 'critical' };
- }
-
- // Multiple high risks = block
- if (highCount >= 2) {
- return { decision: 'block', risk_level: 'high' };
- }
-
- // Single high risk = warn (allow merge but notify)
- if (highCount === 1) {
- return { decision: 'warn', risk_level: 'high' };
- }
-
- // No SkyEye zone touched = safe pass
- if (!touchesSkyeyeZone) {
- return { decision: 'pass', risk_level: 'low' };
- }
-
+ if (hasCritical) return { decision: 'block', risk_level: 'critical' };
+ if (highCount >= 2) return { decision: 'block', risk_level: 'high' };
+ if (highCount === 1) return { decision: 'warn', risk_level: 'high' };
return { decision: 'pass', risk_level: 'low' };
}
@@ -477,54 +381,31 @@ function run() {
console.log(`标题: ${prTitle || '(unknown)'}`);
console.log(`时间: ${new Date().toISOString()}`);
console.log('');
+ console.log('⚖️ 审核模式: 全员全审 — 不区分身份,天眼对所有人一视同仁');
+ console.log('');
- // ─── 检测 PR 来源类型 ───
- const fromAgent = isAgentBranch(branch);
- const identity = extractAgentIdentity(PR_COMMITS_PATH, prTitle);
- const isAgentPR = fromAgent || identity.isAgent;
+ // ─── 身份识别(仅用于日志/上下文,不影响审核决策)───
+ const isAgent = isAgentBranch(branch);
+ const identity = extractIdentity(PR_COMMITS_PATH, prTitle);
+ const isAgentPR = isAgent || identity.isAgent;
if (isAgentPR) {
- console.log('🤖 代理PR检测: 是');
- console.log(` 分支匹配: ${fromAgent ? '是' : '否'}`);
- console.log(` Agent标记: ${identity.isAgent ? '是' : '否'}`);
- if (identity.agentName) console.log(` 代理名称: ${identity.agentName}`);
+ console.log('🤖 PR来源: 代理 (Copilot Agent)');
if (identity.devId) console.log(` 开发者编号: ${identity.devId}`);
- console.log(` 身份来源: ${identity.source}`);
+ if (identity.agentName) console.log(` 代理名称: ${identity.agentName}`);
} else {
- // Non-agent PR from repo owner — this is the owner's own manual work
- console.log('👤 主权者手动PR — 天眼合并膜放行');
- console.log(' (非代理分支,无Agent标记)');
- setOutput('decision', 'pass');
- setOutput('risk_level', 'none');
- setOutput('identity_source', 'owner_manual');
- setOutput('risk_summary', '主权者手动PR,天眼合并膜放行');
- process.exit(0);
+ console.log('👤 PR来源: 手动提交');
}
console.log('');
- // ─── 天眼系统完整性预检 ───
- console.log('━━━ 天眼系统完整性预检 ━━━');
- const integrityIssues = verifySkyeyeIntegrity();
- if (integrityIssues.length > 0) {
- console.log('⚠️ 天眼系统完整性问题:');
- integrityIssues.forEach(i => console.log(` · ${i}`));
- } else {
- console.log('✅ 天眼系统完整性: 正常');
- }
- console.log('');
-
- // ─── 加载配置和文件 ───
- const config = loadConfig();
+ // ─── 加载变更文件 ───
const files = loadPRFiles();
-
if (files.length === 0) {
console.log('⚠️ 无变更文件或文件列表不可用');
- // For agent PRs with no file info, we block to be safe
- console.log('❌ 代理PR无法获取变更文件列表 — 安全起见阻止合并');
+ console.log('❌ 无法获取变更文件列表 — 安全起见阻止合并');
setOutput('decision', 'block');
setOutput('risk_level', 'unknown');
- setOutput('identity_source', identity.source);
- setOutput('risk_summary', '代理PR变更文件列表不可用');
+ setOutput('risk_summary', 'PR变更文件列表不可用');
process.exit(1);
}
@@ -532,59 +413,33 @@ function run() {
files.forEach(f => console.log(` · ${f}`));
console.log('');
- // ─── 查找注册开发者(通过 devId) ───
- const developer = identity.devId ? findDeveloperByDevId(identity.devId, config) : null;
- if (developer) {
- console.log(`👤 已识别开发者: ${developer.name} (${developer.devId})`);
- } else if (identity.devId) {
- console.log(`⚠️ 开发者编号 ${identity.devId} 未在门禁系统注册`);
- } else {
- console.log('⚠️ 无法识别开发者身份');
- }
- console.log('');
-
- // ─── 分析天眼区域接触情况 ───
- const skyeyeZoneFiles = files.filter(f => isInSkyeyeZone(f));
- const nonSkyeyeFiles = files.filter(f => !isInSkyeyeZone(f));
- const touchesSkyeyeZone = skyeyeZoneFiles.length > 0;
-
- console.log(`🛡️ 天眼包裹区域文件: ${skyeyeZoneFiles.length}/${files.length}`);
- if (touchesSkyeyeZone) {
- console.log(' ⚠️ 此PR修改了天眼系统包裹区域 — 启动完整审核');
- skyeyeZoneFiles.forEach(f => console.log(` 🔒 ${f}`));
- } else {
- console.log(' ✅ 此PR未触及天眼包裹区域 — 沙箱内操作');
- }
- if (nonSkyeyeFiles.length > 0) {
- console.log(` 📦 沙箱区域文件: ${nonSkyeyeFiles.length}`);
- }
- console.log('');
-
- // ─── 如果完全在沙箱内且有注册身份 → 放行 ───
- if (!touchesSkyeyeZone && developer) {
- console.log('✅ 沙箱内操作 + 已注册开发者 — 天眼合并膜放行');
- setOutput('decision', 'pass');
- setOutput('risk_level', 'low');
- setOutput('identity_source', identity.source);
- setOutput('risk_summary', `${developer.name}(${developer.devId}) 沙箱内操作,未触及天眼区域`);
- process.exit(0);
- }
-
- // ─── 加载统计 ───
const stats = loadPRStats();
- // ─── 风险检测 ───
- console.log('━━━ 天眼风险检测启动 ━━━');
- const allRisks = [
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ // 天眼全系统审核 — 对所有PR生效,无例外
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ console.log('━━━ 天眼全系统审核启动 ━━━');
+ console.log('');
+
+ // Phase 1: 风险维度检测 (R1-R6)
+ console.log('[Phase 1] 风险维度检测 (R1-R6)...');
+ const riskChecks = [
...checkCriticalFiles(files),
- ...checkSkyeyeZoneIntrusion(files, developer),
...checkBuildArtifacts(files),
- ...checkCoreConfigTampering(files),
...checkMassDeletion(stats),
- ...checkWorkflowTampering(files)
+ ...checkCoreConfigTampering(files, isAgentPR),
+ ...checkWorkflowTampering(files, isAgentPR),
+ ...checkPortalFramework(files, isAgentPR)
];
- // Deduplicate by file + dimension
+ // Phase 2: 仓库结构完整性验证 (R7)
+ console.log('[Phase 2] 仓库结构完整性验证 (R7)...');
+ const structuralRisks = checkStructuralIntegrity(files);
+
+ // Combine all risks
+ const allRisks = [...riskChecks, ...structuralRisks];
+
+ // Deduplicate
const seen = new Set();
const risks = allRisks.filter(r => {
const key = `${r.dimension}:${r.file}`;
@@ -593,32 +448,22 @@ function run() {
return true;
});
- if (risks.length === 0 && !touchesSkyeyeZone) {
- console.log('✅ 未检测到风险(沙箱内操作)');
- console.log('');
- console.log('═══ 决策: PASS ═══');
- setOutput('decision', 'pass');
- setOutput('risk_level', 'low');
- setOutput('identity_source', identity.source);
- setOutput('risk_summary', '沙箱内操作,未检测到风险');
- process.exit(0);
- }
+ console.log('');
- if (risks.length === 0 && touchesSkyeyeZone) {
- // Touches SkyEye zone but no specific risks detected
- // This could happen if a registered developer touches their allowed paths
- console.log('✅ 天眼区域变更已验证 — 在授权范围内');
+ // ─── 结果报告 ───
+ if (risks.length === 0) {
+ console.log('✅ 天眼全系统审核通过');
+ console.log(' 风险维度检测: 无风险');
+ console.log(' 仓库结构完整性: 正常');
console.log('');
console.log('═══ 决策: PASS ═══');
setOutput('decision', 'pass');
setOutput('risk_level', 'low');
- setOutput('identity_source', identity.source);
- setOutput('risk_summary', '天眼区域变更已验证,在授权范围内');
+ setOutput('risk_summary', '天眼全系统审核通过');
process.exit(0);
}
// ─── 输出风险报告 ───
- console.log('');
console.log(`⚠️ 检测到 ${risks.length} 项风险:`);
for (const risk of risks) {
const icon = risk.severity === 'critical' ? '🔴' : risk.severity === 'high' ? '🟠' : '🟡';
@@ -627,23 +472,21 @@ function run() {
console.log('');
// ─── 决策 ───
- const { decision, risk_level } = makeDecision(risks, touchesSkyeyeZone);
+ const { decision, risk_level } = makeDecision(risks);
const summary = risks.map(r => `[${r.dimension}] ${r.detail}`).join(' | ');
setOutput('decision', decision);
setOutput('risk_level', risk_level);
- setOutput('identity_source', identity.source);
setOutput('risk_summary', summary.slice(0, 500));
if (decision === 'block') {
console.log(`═══ 决策: BLOCK (风险等级: ${risk_level}) ═══`);
- console.log('❌ 天眼合并膜: 物理层拒绝合并');
- console.log(' 此PR的变更触及天眼系统包裹区域且未通过审核');
- console.log(' 请联系主权者 TCS-0002∞ 审核后手动合并');
+ console.log('❌ 天眼合并膜: 物理切断合并通道');
+ console.log(' 此PR不符合仓库整体系统结构要求');
process.exit(1);
} else if (decision === 'warn') {
console.log(`═══ 决策: WARN (风险等级: ${risk_level}) ═══`);
- console.log('⚠️ 天眼合并膜: 存在风险 — 建议主权者审核');
+ console.log('⚠️ 天眼合并膜: 存在风险项,建议审核后合并');
process.exit(0);
} else {
console.log(`═══ 决策: PASS (风险等级: ${risk_level}) ═══`);