From 6fab64b1b9e217af32eff6282233fb336edfbaa7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 08:54:40 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20implement=20S5=20=E5=86=B0=E6=9C=94?= =?UTF-8?q?=E7=9B=B4=E9=80=9A=E9=83=A8=E7=BD=B2=E8=A7=84=E5=88=99=20(direc?= =?UTF-8?q?t-deploy=20bypass)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit S5 Rule: When instructions come from 冰朔 (TCS-0002) or are signed by trusted signers (AG-SY-01/TCS-0002/ICE-0002) with ZY- prefix, deployment goes directly to production bypassing preview/SkyEye/approval. - Add isDirectDeploySource() in autonomy-engine.js (single source of truth) - Add direct-deploy bypass in approval.js POST /request endpoint - Add directDeployRule config in autonomy-rules.json - Add deploy audit log (deploy-YYYY-MM-DD.jsonl) for traceability - Developer (Level 0-2) changes still follow full S2 approval flow - Fix watchdog for-of destructuring to match codebase var style Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/6ee737eb-684b-44b8-aeaa-f73eb7c6f9c2 --- backend/api-server/config/autonomy-rules.json | 15 +++ backend/api-server/routes/approval.js | 106 +++++++++++++++++- .../api-server/services/autonomy-engine.js | 23 +++- .../api-server/services/execution-watchdog.js | 4 +- 4 files changed, 143 insertions(+), 5 deletions(-) diff --git a/backend/api-server/config/autonomy-rules.json b/backend/api-server/config/autonomy-rules.json index 3c1d330d..a3675911 100644 --- a/backend/api-server/config/autonomy-rules.json +++ b/backend/api-server/config/autonomy-rules.json @@ -43,6 +43,21 @@ ] }, "deploymentFlow": { + "directDeployRule": { + "id": "S5", + "description": "冰朔直通部署规则:冰朔签发或口头下达的所有指令,执行完毕后直接部署到正式站", + "conditions": [ + "TCS-0002 发起的请求", + "ZY- 开头的指令编号,且签发者为霜砚(AG-SY-01)或冰朔(TCS-0002)" + ], + "bypass": ["预览站", "天眼审核", "肥猫/桔子授权"], + "auditRequired": true, + "reason": "冰朔本人就是系统最高权限(Level 3),冰朔说话 = 最终授权" + }, + "developerFlow": { + "description": "开发者(Level 1-2)提交的变更走完整S2审批流程", + "stages": ["预览站部署", "天眼审核", "授权人审批", "自动发布"] + }, "stages": [ { "stage": 1, diff --git a/backend/api-server/routes/approval.js b/backend/api-server/routes/approval.js index 4f253ed7..157295e2 100644 --- a/backend/api-server/routes/approval.js +++ b/backend/api-server/routes/approval.js @@ -1,8 +1,9 @@ /** - * 部署授权流程路由 · Phase 8 + * 部署授权流程路由 · Phase 8 + S5 直通规则 * - * 天眼审核通过后 → 推送授权请求给授权人 → 授权人确认/拒绝 → 自动发布 - * 系统绝不自动跳过授权步骤。没有人类点确认,就不发布。 + * 部署流分两条路径: + * A) 冰朔直通:TCS-0002 或 ZY- 指令签发 → 直接部署到正式站,跳过预览/天眼/授权 + * B) 开发者流程:天眼审核 → 推送授权请求给授权人 → 确认/拒绝 → 自动发布 * * POST /api/approval/request — 创建授权请求(天眼/系统内部调用) * POST /api/approval/:id/decide — 授权人确认/拒绝 @@ -25,6 +26,36 @@ var approversConfig = require('../config/approvers.json'); // ====== 内存中的授权记录(生产环境可迁移到持久化存储)====== var approvalStore = new Map(); +// ====== S5 直通部署判断 ====== + +var autonomyEngine = require('../services/autonomy-engine'); + +/** + * 判断部署请求是否来自冰朔直通路径(委托给 autonomy-engine 单一来源) + */ +function isDirectDeploySource(user, body) { + var instructionId = body.instructionId || body.deployId || ''; + var signedBy = body.signedBy || ''; + return autonomyEngine.isDirectDeploySource(user.devId, instructionId, signedBy); +} + +/** + * 写入部署日志(直通和审批部署共用,保持可追溯性) + */ +function writeDeployLog(entry) { + var logDir = process.env.AUDIT_LOG_DIR || path.join(__dirname, '../../logs/audit'); + try { + fs.mkdirSync(logDir, { recursive: true }); + var today = new Date().toISOString().split('T')[0]; + var logFile = path.join(logDir, 'deploy-' + today + '.jsonl'); + fs.appendFile(logFile, JSON.stringify(entry) + '\n', function(err) { + if (err) console.error('部署日志写入失败:', err.message); + }); + } catch (e) { + console.error('部署日志写入失败:', e.message); + } +} + // ====== 辅助函数 ====== /** @@ -60,6 +91,7 @@ router.use(auditMiddleware.auditLog); /** * 创建授权请求(天眼审核通过后由系统调用) + * S5: 冰朔直通路径 → 跳过审批,直接部署到正式站 */ router.post('/request', function(req, res) { // 仅系统内部或管理员可创建授权请求 @@ -85,6 +117,74 @@ router.post('/request', function(req, res) { }); } + // ====== S5 直通判断 ====== + if (isDirectDeploySource(req.user, body)) { + // 冰朔/霜砚指令 → 直接部署,不走审批流程 + writeDeployLog({ + action: 'direct_deploy', + deployId: deployId, + module: module, + channel: channel, + source: req.user.devId, + signedBy: body.signedBy || req.user.devId, + instructionId: body.instructionId || deployId, + reason: 'S5 冰朔直通部署规则:冰朔签发或口头下达的指令直接部署到正式站', + timestamp: new Date().toISOString() + }); + + // 直接触发正式站部署 + var githubService; + try { + githubService = require('../services/github'); + } catch (_) { + githubService = null; + } + + if (githubService && githubService.triggerWorkflow) { + githubService.triggerWorkflow('deploy-to-server.yml', { + module: module, + deploy_id: deployId, + approved_by: req.user.devId, + target: 'production', + direct_deploy: 'true' + }).then(function() { + writeDeployLog({ + action: 'direct_deploy_triggered', + deployId: deployId, + module: module, + source: req.user.devId, + timestamp: new Date().toISOString() + }); + }).catch(function(err) { + console.error('直通部署触发失败:', err.message); + writeDeployLog({ + action: 'direct_deploy_failed', + deployId: deployId, + error: err.message, + timestamp: new Date().toISOString() + }); + }); + } + + return res.json({ + success: true, + directDeploy: true, + deployId: deployId, + reply: '🚀 冰朔直通部署:' + module + ' 已直接触发正式站(guanghulab.com)部署。' + + '不经预览站、不经天眼审核、不经授权审批。部署日志已记录。' + }); + } + + // ====== 开发者流程:走完整 S2 审批 ====== + + if (!deployId || !module) { + return res.status(400).json({ + error: true, + code: 'MISSING_FIELDS', + reply: '❌ 缺少必要字段:deployId, module' + }); + } + var approvers = getApprovers(channel); var approvalId = 'APPROVAL-' + Date.now() + '-' + Math.random().toString(36).substring(2, 6); diff --git a/backend/api-server/services/autonomy-engine.js b/backend/api-server/services/autonomy-engine.js index 2d8403a9..74975b5b 100644 --- a/backend/api-server/services/autonomy-engine.js +++ b/backend/api-server/services/autonomy-engine.js @@ -113,11 +113,32 @@ function getDualLineConfig() { return autonomyRules.dualLineArchitecture; } +/** + * 判断请求是否来自冰朔直通部署路径 (S5) + * @param {string} devId - 开发者编号 + * @param {string} instructionId - 指令编号 + * @param {string} signedBy - 签发者编号 + * @returns {boolean} + */ +function isDirectDeploySource(devId, instructionId, signedBy) { + // 冰朔本人 + if (devId === 'TCS-0002') return true; + + // ZY- 指令 + 可信签发者 + if (instructionId && /^ZY-/.test(instructionId)) { + var trustedSigners = ['AG-SY-01', 'TCS-0002', 'ICE-0002']; + if (signedBy && trustedSigners.indexOf(signedBy) !== -1) return true; + } + + return false; +} + module.exports = { checkAutonomyCompliance: checkAutonomyCompliance, detectInteractionMode: detectInteractionMode, logAutonomousAction: logAutonomousAction, getDeploymentStages: getDeploymentStages, getDualLineConfig: getDualLineConfig, - writeAutonomyLog: writeAutonomyLog + writeAutonomyLog: writeAutonomyLog, + isDirectDeploySource: isDirectDeploySource }; diff --git a/backend/api-server/services/execution-watchdog.js b/backend/api-server/services/execution-watchdog.js index 6d987ed9..4327bc1e 100644 --- a/backend/api-server/services/execution-watchdog.js +++ b/backend/api-server/services/execution-watchdog.js @@ -34,7 +34,9 @@ function startWatchdog() { var activeLocks = executionLock.getActiveLocks(); var now = Date.now(); - for (var [devId, lock] of activeLocks) { + for (var entry of activeLocks) { + var devId = entry[0]; + var lock = entry[1]; if (lock.state !== 'running') continue;