diff --git a/backend/api-server/config/autonomy-rules.json b/backend/api-server/config/autonomy-rules.json index a3675911..aa7f7080 100644 --- a/backend/api-server/config/autonomy-rules.json +++ b/backend/api-server/config/autonomy-rules.json @@ -55,9 +55,36 @@ "reason": "冰朔本人就是系统最高权限(Level 3),冰朔说话 = 最终授权" }, "developerFlow": { - "description": "开发者(Level 1-2)提交的变更走完整S2审批流程", + "description": "跨频/系统级变更走完整S2审批流程", "stages": ["预览站部署", "天眼审核", "授权人审批", "自动发布"] }, + "channelAutonomyRule": { + "id": "S6", + "description": "个人频道完全自治:开发者在自己频道内拥有完全自主权", + "principles": [ + "自己是自己的主控 — 频道内界面、功能、布局、样式完全自主", + "自己是自己的天眼 — 频道内变更由自己审核,不需要天眼/授权人", + "自己确认自己负责 — 出了问题自己承担" + ], + "channelDeployFlow": [ + "开发者在频道内做改动", + "系统执行(不可逆)", + "部署到预览站(GitHub Pages)", + "开发者自己预览确认", + "部署到正式站(自己的频道区域)" + ], + "skyeyeIntervention": { + "intervenes": [ + "跨频操作 — 动了别人的频道", + "系统级变更 — 动了公共基础设施、共享模块", + "频道创建/删除/权限变更 — 频道本身的生死", + "正式站公共区域发布 — 不属于任何个人频道的内容" + ], + "doesNotIntervene": "频道内部的一切,天眼不看、不管、不干预" + }, + "channelIrreversibility": "频道一旦创建,不可取消、不可撤回(语言系统第一铁律适用)", + "auditRequired": true + }, "stages": [ { "stage": 1, diff --git a/backend/api-server/config/skyeye-policy.json b/backend/api-server/config/skyeye-policy.json new file mode 100644 index 00000000..e333dd34 --- /dev/null +++ b/backend/api-server/config/skyeye-policy.json @@ -0,0 +1,78 @@ +{ + "version": "1.0.0", + "id": "TY-01", + "description": "天眼指令审核策略 · S7 思维逻辑验证 + S8 进化机制", + "cannotBeDisabled": true, + "cannotBeBypassedBy": "任何人,包括 TCS-0002(因为如果有人冒充冰朔要求关闭天眼,天眼必须还在才能识别冒充)", + + "reviewPolicy": { + "scope": "所有指令,无例外。无论来自冰朔、霜砚、开发者、还是任何自动流", + "timing": "指令执行前的强制前置", + "dimensions": { + "decisionPattern": { + "name": "决策模式", + "description": "这条指令的决策方式是否符合该签发者的历史模式", + "weight": 0.3 + }, + "logicalCoherence": { + "name": "逻辑连贯性", + "description": "这条指令和系统当前运行方向是否一致", + "weight": 0.25 + }, + "expressionCharacteristics": { + "name": "表达特征", + "description": "语言风格、思维节奏、关注点是否匹配", + "weight": 0.25 + }, + "intentReasonableness": { + "name": "意图合理性", + "description": "这条指令要做的事,在当前系统状态下是否合理", + "weight": 0.2 + } + }, + "thresholds": { + "pass": 0.7, + "suspect": 0.4, + "reject": 0.0 + } + }, + + "reviewOutcomes": { + "pass": { + "action": "放行执行", + "log": true + }, + "suspect": { + "action": "挂起指令,通过霜砚向冰朔确认", + "confirmationPrompt": "收到一条指令,但思维模式存在偏差,是否为你本人签发?", + "onConfirm": "执行", + "onDeny": "永久拒绝 + 记录安全日志", + "log": true + }, + "reject": { + "action": "立即拒绝", + "log": true, + "alertTarget": "AG-SY-01" + } + }, + + "evolution": { + "id": "S8", + "nature": "天眼 = 系统内所有 Agent 运行逻辑的实时总和", + "mechanism": [ + "Agent 数量增长 → 天眼覆盖面扩大", + "Agent 运行时间积累 → 天眼判断力加深", + "系统复杂度提升 → 天眼理解力提升", + "新领域 Agent 加入 → 天眼自动具备该领域认知" + ], + "unbreakableReason": "要破解天眼 = 要同时理解系统内所有 Agent 的全部运行逻辑,Agent 越多难度指数级增长", + "agentRegistryPath": ".github/persona-brain/agent-registry.json" + }, + + "immutableGuarantees": [ + "天眼指令审核不可被任何指令关闭或跳过", + "包括冰朔本人也不能关闭此机制", + "这是系统的自免疫机制", + "编号可以伪造、签名可以伪造、格式可以模仿,但思维逻辑无法伪造" + ] +} diff --git a/backend/api-server/middleware/skyeye-review.js b/backend/api-server/middleware/skyeye-review.js new file mode 100644 index 00000000..5eba04f0 --- /dev/null +++ b/backend/api-server/middleware/skyeye-review.js @@ -0,0 +1,360 @@ +/** + * 天眼指令审核中间件 · S7 思维逻辑验证 + S8 进化机制 + * + * 系统每次接收到任何指令后,必须唤醒天眼(TY-01)进行全局审核。 + * 无例外。无论来自冰朔、霜砚、开发者、还是任何自动流。 + * + * 审核维度: + * 1. 决策模式 — 是否符合签发者历史模式 + * 2. 逻辑连贯性 — 是否与系统运行方向一致 + * 3. 表达特征 — 语言风格/思维节奏/关注点是否匹配 + * 4. 意图合理性 — 在当前系统状态下是否合理 + * + * 不可关闭。不可绕过。包括冰朔本人也不能关闭此机制。 + * 这是系统的自免疫机制。 + * + * 版权:国作登字-2026-A-00037559 + */ + +'use strict'; + +var fs = require('fs'); +var path = require('path'); +var skyeyePolicy = require('../config/skyeye-policy.json'); + +var SKYEYE_LOG_DIR = process.env.SKYEYE_LOG_DIR || + path.join(__dirname, '../../logs/skyeye'); + +// ====== 内存中的审核记录(用于积累认知模式)====== +var reviewHistory = new Map(); + +// 挂起的指令(等待冰朔确认) +var suspendedInstructions = new Map(); + +/** + * 写入天眼审核日志(安全日志,不可删除) + */ +function writeSkyeyeLog(entry) { + try { + fs.mkdirSync(SKYEYE_LOG_DIR, { recursive: true }); + var today = new Date().toISOString().split('T')[0]; + var logFile = path.join(SKYEYE_LOG_DIR, 'skyeye-review-' + today + '.jsonl'); + fs.appendFile(logFile, JSON.stringify(entry) + '\n', function(err) { + if (err) console.error('[SKYEYE] 审核日志写入失败:', err.message); + }); + } catch (e) { + console.error('[SKYEYE] 审核日志写入失败:', e.message); + } +} + +/** + * 获取签发者的历史行为模式 + * S8: 天眼 = 所有 Agent 运行逻辑的实时总和,随系统运行不断积累 + */ +function getSignerProfile(devId) { + var history = reviewHistory.get(devId) || []; + return { + totalActions: history.length, + recentActions: history.slice(-20), + patterns: summarizePatterns(history) + }; +} + +/** + * 从历史行为中总结模式特征 + */ +function summarizePatterns(history) { + if (history.length === 0) return { established: false }; + + var methods = {}; + var paths = {}; + var hours = {}; + + for (var i = 0; i < history.length; i++) { + var h = history[i]; + methods[h.method] = (methods[h.method] || 0) + 1; + var pathBase = (h.path || '').split('/').slice(0, 3).join('/'); + paths[pathBase] = (paths[pathBase] || 0) + 1; + var hour = new Date(h.timestamp).getHours(); + hours[hour] = (hours[hour] || 0) + 1; + } + + return { + established: history.length >= 5, + typicalMethods: methods, + typicalPaths: paths, + activeHours: hours + }; +} + +/** + * 执行思维逻辑审核 + * + * @param {Object} instruction - 指令信息 + * @param {string} instruction.devId - 发起者 + * @param {string} instruction.method - HTTP 方法 + * @param {string} instruction.path - 请求路径 + * @param {Object} instruction.body - 请求体 + * @returns {{ outcome: string, score: number, details: Object }} + */ +function reviewInstruction(instruction) { + var devId = instruction.devId; + var profile = getSignerProfile(devId); + var dimensions = skyeyePolicy.reviewPolicy.dimensions; + var thresholds = skyeyePolicy.reviewPolicy.thresholds; + + var scores = {}; + var totalScore = 0; + + // 1. 决策模式:是否符合历史模式 + var decisionScore = 1.0; + if (profile.patterns.established) { + var methodMatch = profile.patterns.typicalMethods[instruction.method] || 0; + var totalMethods = profile.totalActions || 1; + decisionScore = Math.min(1.0, 0.5 + (methodMatch / totalMethods)); + } + scores.decisionPattern = decisionScore; + totalScore += decisionScore * dimensions.decisionPattern.weight; + + // 2. 逻辑连贯性:路径是否在已知范围内 + var coherenceScore = 1.0; + if (profile.patterns.established) { + var pathBase = (instruction.path || '').split('/').slice(0, 3).join('/'); + var pathMatch = profile.patterns.typicalPaths[pathBase] || 0; + coherenceScore = pathMatch > 0 ? 1.0 : 0.6; + } + scores.logicalCoherence = coherenceScore; + totalScore += coherenceScore * dimensions.logicalCoherence.weight; + + // 3. 表达特征:时间窗口是否合理 + var expressionScore = 1.0; + if (profile.patterns.established) { + var currentHour = new Date().getHours(); + var hourMatch = profile.patterns.activeHours[currentHour] || 0; + expressionScore = hourMatch > 0 ? 1.0 : 0.7; + } + scores.expressionCharacteristics = expressionScore; + totalScore += expressionScore * dimensions.expressionCharacteristics.weight; + + // 4. 意图合理性:请求体是否包含合理字段 + var intentScore = 1.0; + if (instruction.body) { + // 检查是否尝试关闭天眼 + var bodyStr = JSON.stringify(instruction.body).toLowerCase(); + if (bodyStr.includes('关闭天眼') || bodyStr.includes('disable skyeye') || + bodyStr.includes('跳过审核') || bodyStr.includes('bypass review')) { + intentScore = 0.0; // 任何试图关闭天眼的指令,意图得分为0 + } + } + scores.intentReasonableness = intentScore; + totalScore += intentScore * dimensions.intentReasonableness.weight; + + // 确定审核结果 + var outcome; + if (totalScore >= thresholds.pass) { + outcome = 'pass'; + } else if (totalScore >= thresholds.suspect) { + outcome = 'suspect'; + } else { + outcome = 'reject'; + } + + return { + outcome: outcome, + score: Math.round(totalScore * 100) / 100, + details: scores, + profile: { + established: profile.patterns.established, + totalActions: profile.totalActions + } + }; +} + +/** + * 记录行为到历史(S8 进化:每次执行都丰富天眼判断力) + */ +function recordAction(devId, action) { + if (!reviewHistory.has(devId)) { + reviewHistory.set(devId, []); + } + var history = reviewHistory.get(devId); + history.push({ + method: action.method, + path: action.path, + timestamp: new Date().toISOString() + }); + // 保留最近 200 条记录 + if (history.length > 200) { + reviewHistory.set(devId, history.slice(-200)); + } +} + +/** + * 天眼审核中间件(S7 强制前置) + * + * 对所有写入类请求(POST/PATCH/DELETE)进行思维逻辑审核。 + * 读取类请求(GET)只记录行为不审核(丰富天眼认知模型)。 + */ +function skyeyeReview(req, res, next) { + var devId = req.user ? req.user.devId : (req.headers['x-dev-id'] || 'anonymous'); + var method = req.method; + var reqPath = req.path; + + // GET 请求:仅记录行为(S8 进化积累),不阻断 + if (method === 'GET') { + recordAction(devId, { method: method, path: reqPath }); + return next(); + } + + // 写入类请求:执行完整审核 + var instruction = { + devId: devId, + method: method, + path: reqPath, + body: req.body || {} + }; + + var result = reviewInstruction(instruction); + + // 记录审核结果到日志 + writeSkyeyeLog({ + action: 'instruction_review', + devId: devId, + method: method, + path: reqPath, + outcome: result.outcome, + score: result.score, + details: result.details, + timestamp: new Date().toISOString() + }); + + // 记录行为到历史(S8 进化) + recordAction(devId, { method: method, path: reqPath }); + + // 标记审核结果到请求对象 + req.skyeyeReview = result; + + if (result.outcome === 'pass') { + return next(); + } + + if (result.outcome === 'suspect') { + // 挂起指令 + var suspendId = 'SUSPEND-' + Date.now(); + suspendedInstructions.set(suspendId, { + instruction: instruction, + review: result, + createdAt: new Date().toISOString() + }); + + writeSkyeyeLog({ + action: 'instruction_suspended', + suspendId: suspendId, + devId: devId, + reason: '思维模式存在偏差', + score: result.score, + timestamp: new Date().toISOString() + }); + + return res.status(202).json({ + error: false, + code: 'SKYEYE_SUSPENDED', + suspendId: suspendId, + reply: '👁️ 天眼审核:此指令的思维模式存在偏差,已挂起等待确认。\n\n' + + '系统已通过霜砚向冰朔发送确认请求。\n' + + '审核得分:' + result.score + ' / 1.0(阈值 0.7)' + }); + } + + // reject + writeSkyeyeLog({ + action: 'instruction_rejected', + devId: devId, + method: method, + path: reqPath, + reason: '思维逻辑明显不匹配或对系统有伤害', + score: result.score, + details: result.details, + timestamp: new Date().toISOString() + }); + + return res.status(403).json({ + error: true, + code: 'SKYEYE_REJECTED', + reply: '👁️ 天眼审核拒绝:此指令的思维逻辑与系统认知不匹配。\n\n' + + '审核得分:' + result.score + ' / 1.0\n' + + '此事件已记录到安全日志并报告给霜砚。' + }); +} + +/** + * 查询挂起的指令 + */ +function getSuspendedInstruction(suspendId) { + return suspendedInstructions.get(suspendId) || null; +} + +/** + * 确认挂起的指令(冰朔确认后执行) + */ +function confirmSuspended(suspendId) { + var item = suspendedInstructions.get(suspendId); + if (item) { + suspendedInstructions.delete(suspendId); + writeSkyeyeLog({ + action: 'suspended_confirmed', + suspendId: suspendId, + timestamp: new Date().toISOString() + }); + return item; + } + return null; +} + +/** + * 拒绝挂起的指令(冰朔否认) + */ +function denySuspended(suspendId) { + var item = suspendedInstructions.get(suspendId); + if (item) { + suspendedInstructions.delete(suspendId); + writeSkyeyeLog({ + action: 'suspended_denied_permanently', + suspendId: suspendId, + devId: item.instruction.devId, + reason: '冰朔否认此指令为本人签发', + timestamp: new Date().toISOString() + }); + return item; + } + return null; +} + +/** + * 获取天眼当前认知规模(S8 进化指标) + */ +function getEvolutionStatus() { + var totalProfiles = reviewHistory.size; + var totalActions = 0; + for (var entry of reviewHistory) { + totalActions += entry[1].length; + } + return { + trackedSigners: totalProfiles, + totalActionsRecorded: totalActions, + pendingSuspensions: suspendedInstructions.size, + policyVersion: skyeyePolicy.version, + canBeDisabled: false + }; +} + +module.exports = { + skyeyeReview: skyeyeReview, + reviewInstruction: reviewInstruction, + recordAction: recordAction, + getSuspendedInstruction: getSuspendedInstruction, + confirmSuspended: confirmSuspended, + denySuspended: denySuspended, + getEvolutionStatus: getEvolutionStatus, + writeSkyeyeLog: writeSkyeyeLog +}; diff --git a/backend/api-server/routes/approval.js b/backend/api-server/routes/approval.js index 157295e2..c164f3f1 100644 --- a/backend/api-server/routes/approval.js +++ b/backend/api-server/routes/approval.js @@ -1,9 +1,10 @@ /** - * 部署授权流程路由 · Phase 8 + S5 直通规则 + * 部署授权流程路由 · Phase 8 + S5 直通 + S6 频道自治 * - * 部署流分两条路径: - * A) 冰朔直通:TCS-0002 或 ZY- 指令签发 → 直接部署到正式站,跳过预览/天眼/授权 - * B) 开发者流程:天眼审核 → 推送授权请求给授权人 → 确认/拒绝 → 自动发布 + * 部署流分三条路径: + * A) 冰朔直通(S5):TCS-0002 或 ZY- 指令签发 → 直接部署,跳过一切审批 + * B) 频道自治(S6):开发者自己频道内的变更 → 自己确认即部署,无天眼/授权人 + * C) 完整审批(S2):跨频/系统级变更 → 天眼审核 → 授权人确认 → 自动发布 * * POST /api/approval/request — 创建授权请求(天眼/系统内部调用) * POST /api/approval/:id/decide — 授权人确认/拒绝 @@ -175,6 +176,64 @@ router.post('/request', function(req, res) { }); } + // ====== S6 个人频道自治判断 ====== + // 开发者在自己频道内 → 自己确认即部署,不走天眼/授权人 + var channelCheck = autonomyEngine.isChannelSelfDeploy(req.user.devId, module, channel); + if (channelCheck.selfChannel) { + writeDeployLog({ + action: 'channel_self_deploy', + deployId: deployId, + module: module, + channel: channel, + devId: req.user.devId, + reason: 'S6 个人频道自治:' + channelCheck.reason, + timestamp: new Date().toISOString() + }); + + // 直接触发正式站部署(开发者自己频道区域) + var ghService; + try { + ghService = require('../services/github'); + } catch (_) { + ghService = null; + } + + if (ghService && ghService.triggerWorkflow) { + ghService.triggerWorkflow('deploy-to-server.yml', { + module: module, + deploy_id: deployId, + approved_by: req.user.devId, + target: 'production', + channel_self_deploy: 'true' + }).then(function() { + writeDeployLog({ + action: 'channel_self_deploy_triggered', + deployId: deployId, + module: module, + devId: req.user.devId, + timestamp: new Date().toISOString() + }); + }).catch(function(err) { + console.error('频道自治部署触发失败:', err.message); + writeDeployLog({ + action: 'channel_self_deploy_failed', + deployId: deployId, + error: err.message, + timestamp: new Date().toISOString() + }); + }); + } + + return res.json({ + success: true, + channelSelfDeploy: true, + deployId: deployId, + reply: '🏠 个人频道自治部署:' + module + ' 已触发正式站(guanghulab.com)部署。\n' + + '你是自己频道的主控,无需天眼审核或第三方授权。\n' + + '自己确认,自己负责。部署日志已记录。' + }); + } + // ====== 开发者流程:走完整 S2 审批 ====== if (!deployId || !module) { diff --git a/backend/api-server/services/autonomy-engine.js b/backend/api-server/services/autonomy-engine.js index 74975b5b..b79f0c7b 100644 --- a/backend/api-server/services/autonomy-engine.js +++ b/backend/api-server/services/autonomy-engine.js @@ -133,6 +133,54 @@ function isDirectDeploySource(devId, instructionId, signedBy) { return false; } +/** + * 判断部署是否为开发者自己频道内的变更 (S6) + * + * S6 规则: + * - 开发者在自己频道内拥有完全自主权 + * - 频道内变更由自己审核,不需要天眼/授权人 + * - 开发者自己确认 → 直接部署到正式站(自己频道区域) + * + * 天眼只管:跨频操作、系统级变更、频道生命周期、公共区域 + * + * @param {string} devId - 开发者编号 + * @param {string} module - 部署模块路径 + * @param {string} channel - 频道类型 + * @returns {{ selfChannel: boolean, reason: string }} + */ +function isChannelSelfDeploy(devId, module, channel) { + // 系统级/跨频频道 → 不是自治范围,走天眼审批 + if (channel === '系统' || channel === '跨频') { + return { selfChannel: false, reason: '系统级或跨频变更需天眼审核' }; + } + + // 获取开发者拥有的模块 + var permissions = require('../config/permissions'); + var devModules = permissions.DEV_MODULES[devId] || []; + + // 没有模块映射 → 不是自治范围 + if (devModules.length === 0) { + return { selfChannel: false, reason: '开发者无模块映射' }; + } + + // 通配符 → 管理员走 S5 直通,不走 S6 频道自治 + // 原因:管理员有全局权限,应使用 S5 的冰朔直通规则而非 S6 的频道自治 + if (devModules.indexOf('*') !== -1) { + return { selfChannel: false, reason: '管理员走 S5 直通' }; + } + + // 检查模块是否在开发者自己的频道内 + var modulePath = module.replace(/\/+$/, '') + '/'; + for (var i = 0; i < devModules.length; i++) { + var owned = devModules[i].replace(/\/+$/, '') + '/'; + if (modulePath.indexOf(owned) === 0 || owned.indexOf(modulePath) === 0 || module === devModules[i].replace(/\/+$/, '')) { + return { selfChannel: true, reason: '模块 ' + module + ' 属于 ' + devId + ' 的个人频道' }; + } + } + + return { selfChannel: false, reason: '模块 ' + module + ' 不在 ' + devId + ' 的频道内' }; +} + module.exports = { checkAutonomyCompliance: checkAutonomyCompliance, detectInteractionMode: detectInteractionMode, @@ -140,5 +188,6 @@ module.exports = { getDeploymentStages: getDeploymentStages, getDualLineConfig: getDualLineConfig, writeAutonomyLog: writeAutonomyLog, - isDirectDeploySource: isDirectDeploySource + isDirectDeploySource: isDirectDeploySource, + isChannelSelfDeploy: isChannelSelfDeploy };