feat: implement S6 personal channel full autonomy rule
S6 Rule: Developers have full autonomy within their own channels. - Add isChannelSelfDeploy() in autonomy-engine.js (single source of truth) - Add channel self-deploy bypass in approval.js POST /request - Add channelAutonomyRule S6 config in autonomy-rules.json - Own-channel deploys skip SkyEye/approver authorization - Cross-channel/system changes still go through full S2 approval Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/6ee737eb-684b-44b8-aeaa-f73eb7c6f9c2
This commit is contained in:
parent
6fab64b1b9
commit
0a63716226
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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": [
|
||||
"天眼指令审核不可被任何指令关闭或跳过",
|
||||
"包括冰朔本人也不能关闭此机制",
|
||||
"这是系统的自免疫机制",
|
||||
"编号可以伪造、签名可以伪造、格式可以模仿,但思维逻辑无法伪造"
|
||||
]
|
||||
}
|
||||
|
|
@ -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
|
||||
};
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue