feat: implement Phase 8 + Phase 9 (S2/S3/S4 deployment approval + system autonomy)
Phase 8 - Dual-Environment Deploy Flow + SkyEye Final Review Authorization: - Add backend/api-server/routes/approval.js: request/decide/status/list endpoints - Add backend/api-server/config/approvers.json: 肥猫(DEV-002) + 桔子(DEV-010) config - Add docs/js/approval-ui.js: frontend approval badge + panel + decision UI - Add docs/css/approval.css: approval panel styles - Update permissions.js: add approval:decide, approval:create, system:internal - Update server.js to v4.0.0: register approval routes Phase 9 - System Autonomy Framework: - Add backend/api-server/config/autonomy-rules.json: dual-line architecture + deploy stages - Add backend/api-server/services/autonomy-engine.js: compliance check + mode detection 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
ade9c8dfc3
commit
721974ca76
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"version": "1.0.0",
|
||||
"description": "天眼终审授权人配置 · 部署到正式站前的人类终审",
|
||||
"rules": {
|
||||
"男频": {
|
||||
"approvers": [
|
||||
{ "devId": "DEV-002", "name": "肥猫", "role": "男频内容授权人" }
|
||||
],
|
||||
"requireAll": true
|
||||
},
|
||||
"女频": {
|
||||
"approvers": [
|
||||
{ "devId": "DEV-010", "name": "桔子", "role": "女频内容授权人" }
|
||||
],
|
||||
"requireAll": true
|
||||
},
|
||||
"系统": {
|
||||
"approvers": [
|
||||
{ "devId": "DEV-002", "name": "肥猫", "role": "男频内容授权人" },
|
||||
{ "devId": "DEV-010", "name": "桔子", "role": "女频内容授权人" }
|
||||
],
|
||||
"requireAll": true
|
||||
},
|
||||
"跨频": {
|
||||
"approvers": [
|
||||
{ "devId": "DEV-002", "name": "肥猫", "role": "男频内容授权人" },
|
||||
{ "devId": "DEV-010", "name": "桔子", "role": "女频内容授权人" }
|
||||
],
|
||||
"requireAll": true
|
||||
}
|
||||
},
|
||||
"defaults": {
|
||||
"channel": "系统",
|
||||
"timeoutHours": 72,
|
||||
"noResponseAction": "不通过(不回应视为不通过)"
|
||||
},
|
||||
"audit": {
|
||||
"logEveryDecision": true,
|
||||
"fields": ["timestamp", "approvalId", "devId", "decision", "channel", "module"]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
{
|
||||
"version": "1.0.0",
|
||||
"description": "系统自治运行规则 · Phase 9",
|
||||
"dualLineArchitecture": {
|
||||
"lineA": {
|
||||
"name": "操作系统底座线",
|
||||
"owner": "TCS-0002",
|
||||
"scope": ["权限系统", "部署流水线", "神经网络升级", "执行保护层", "架构变更"],
|
||||
"interactionModes": {
|
||||
"instructionMode": {
|
||||
"description": "冰朔说方向 → 霜砚拆指令 → 铸渊执行",
|
||||
"trigger": ["签发指令", "写个指令", "下发指令"],
|
||||
"suitableFor": "架构级、系统级变更"
|
||||
},
|
||||
"naturalLanguageMode": {
|
||||
"description": "冰朔直接和系统说话,系统即时响应",
|
||||
"trigger": "默认模式(直接说需求/问问题)",
|
||||
"suitableFor": "日常操作、快速调整"
|
||||
}
|
||||
},
|
||||
"priority": "最高 · 冰朔说话 = 最高优先级,无论哪种模式"
|
||||
},
|
||||
"lineB": {
|
||||
"name": "具体功能线",
|
||||
"owners": "开发者团队",
|
||||
"scope": ["Persona Studio", "工单系统", "数据看板", "动态漫画", "各模块功能"],
|
||||
"interactionMode": "日常交互(和铸渊说话 → 系统执行)"
|
||||
}
|
||||
},
|
||||
"autonomyRules": {
|
||||
"systemCanDo": [
|
||||
"自主调整内部运行逻辑(不违反铁律)",
|
||||
"自主升级能力模块(记录到审计日志)",
|
||||
"自主优化瓶颈路径",
|
||||
"定期生成系统健康报告",
|
||||
"异常时主动向霜砚报告"
|
||||
],
|
||||
"systemMustNot": [
|
||||
"违反语言系统两条铁律",
|
||||
"绕过天眼审核",
|
||||
"绕过人类授权步骤",
|
||||
"修改权限分层(权限变更只有冰朔能触发)"
|
||||
]
|
||||
},
|
||||
"deploymentFlow": {
|
||||
"stages": [
|
||||
{
|
||||
"stage": 1,
|
||||
"name": "部署到预览站",
|
||||
"target": "GitHub Pages (qinfendebingshuo.github.io/guanghulab/docs/)",
|
||||
"description": "真实可访问的预览环境,让人类确认执行结果"
|
||||
},
|
||||
{
|
||||
"stage": 2,
|
||||
"name": "天眼审核",
|
||||
"checks": ["代码安全", "结构完整性", "功能回归"],
|
||||
"passAction": "进入 Stage 3",
|
||||
"failAction": "标记问题,开发者修复后重新提交"
|
||||
},
|
||||
{
|
||||
"stage": 3,
|
||||
"name": "人类终审授权",
|
||||
"description": "授权人必须手动点击确认,不回应视为不通过",
|
||||
"configFile": "approvers.json"
|
||||
},
|
||||
{
|
||||
"stage": 4,
|
||||
"name": "自动发布到正式站",
|
||||
"target": "guanghulab.com",
|
||||
"trigger": "deploy-to-server.yml",
|
||||
"description": "授权即发布,无需人类再次操作"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -44,9 +44,10 @@ var PERMISSION_LEVELS = {
|
|||
'agent:read',
|
||||
'repo:read',
|
||||
'deploy:preview', 'deploy:production',
|
||||
'maintenance:log'
|
||||
'maintenance:log',
|
||||
'approval:decide'
|
||||
],
|
||||
description: '可操作自己的模块,含正式站部署'
|
||||
description: '可操作自己的模块,含正式站部署和授权决策'
|
||||
},
|
||||
3: {
|
||||
name: '管理者',
|
||||
|
|
@ -60,7 +61,9 @@ var PERMISSION_LEVELS = {
|
|||
'deploy:preview', 'deploy:production',
|
||||
'broadcast:create',
|
||||
'maintenance:log',
|
||||
'permission:manage'
|
||||
'permission:manage',
|
||||
'approval:decide', 'approval:create',
|
||||
'system:internal'
|
||||
],
|
||||
description: '系统管理员,全部权限'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,312 @@
|
|||
/**
|
||||
* 部署授权流程路由 · Phase 8
|
||||
*
|
||||
* 天眼审核通过后 → 推送授权请求给授权人 → 授权人确认/拒绝 → 自动发布
|
||||
* 系统绝不自动跳过授权步骤。没有人类点确认,就不发布。
|
||||
*
|
||||
* POST /api/approval/request — 创建授权请求(天眼/系统内部调用)
|
||||
* POST /api/approval/:id/decide — 授权人确认/拒绝
|
||||
* GET /api/approval/:id — 查询单个授权状态
|
||||
* GET /api/approval/pending — 查询待处理的授权列表
|
||||
*
|
||||
* 版权:国作登字-2026-A-00037559
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var authMiddleware = require('../middleware/auth');
|
||||
var auditMiddleware = require('../middleware/audit');
|
||||
var approversConfig = require('../config/approvers.json');
|
||||
|
||||
// ====== 内存中的授权记录(生产环境可迁移到持久化存储)======
|
||||
var approvalStore = new Map();
|
||||
|
||||
// ====== 辅助函数 ======
|
||||
|
||||
/**
|
||||
* 根据频道获取授权人列表
|
||||
*/
|
||||
function getApprovers(channel) {
|
||||
var rule = approversConfig.rules[channel] || approversConfig.rules[approversConfig.defaults.channel];
|
||||
return rule ? rule.approvers : approversConfig.rules['系统'].approvers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入审计日志(授权专用)
|
||||
*/
|
||||
function writeApprovalAudit(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, 'approval-' + today + '.jsonl');
|
||||
fs.appendFile(logFile, JSON.stringify(entry) + '\n', function(err) {
|
||||
if (err) console.error('授权审计日志写入失败:', err.message);
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('授权审计日志写入失败:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// ====== 路由 ======
|
||||
|
||||
// 所有授权路由需要认证
|
||||
router.use(authMiddleware.requireAuth);
|
||||
router.use(auditMiddleware.auditLog);
|
||||
|
||||
/**
|
||||
* 创建授权请求(天眼审核通过后由系统调用)
|
||||
*/
|
||||
router.post('/request', function(req, res) {
|
||||
// 仅系统内部或管理员可创建授权请求
|
||||
if (req.user.permissionLevel < 3 && req.user.devId !== 'SYSTEM') {
|
||||
return res.status(403).json({
|
||||
error: true,
|
||||
code: 'PERMISSION_DENIED',
|
||||
reply: '🔒 仅系统内部或管理员可创建授权请求。'
|
||||
});
|
||||
}
|
||||
|
||||
var body = req.body || {};
|
||||
var deployId = body.deployId;
|
||||
var module = body.module;
|
||||
var channel = body.channel || '系统';
|
||||
var reviewReport = body.reviewReport || {};
|
||||
|
||||
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);
|
||||
|
||||
var approval = {
|
||||
id: approvalId,
|
||||
deployId: deployId,
|
||||
module: module,
|
||||
channel: channel,
|
||||
reviewReport: reviewReport,
|
||||
approvers: approvers.map(function(a) { return a.devId; }),
|
||||
approverDetails: approvers,
|
||||
decisions: {},
|
||||
status: 'pending',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
approvalStore.set(approvalId, approval);
|
||||
|
||||
writeApprovalAudit({
|
||||
action: 'approval_created',
|
||||
approvalId: approvalId,
|
||||
deployId: deployId,
|
||||
module: module,
|
||||
channel: channel,
|
||||
approvers: approvers.map(function(a) { return a.devId; }),
|
||||
createdBy: req.user.devId,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
approvalId: approvalId,
|
||||
approvers: approvers,
|
||||
reply: '📋 授权请求已创建(' + approvalId + ')。已推送给授权人:' +
|
||||
approvers.map(function(a) { return a.name + '(' + a.devId + ')'; }).join('、') + '。'
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 授权人确认/拒绝
|
||||
*/
|
||||
router.post('/:approvalId/decide', function(req, res) {
|
||||
var approvalId = req.params.approvalId;
|
||||
var decision = (req.body || {}).decision; // 'approved' | 'rejected'
|
||||
var reason = (req.body || {}).reason || '';
|
||||
var devId = req.user.devId;
|
||||
|
||||
var approval = approvalStore.get(approvalId);
|
||||
if (!approval) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
code: 'NOT_FOUND',
|
||||
reply: '❌ 授权请求 ' + approvalId + ' 不存在。'
|
||||
});
|
||||
}
|
||||
|
||||
if (approval.status !== 'pending') {
|
||||
return res.status(400).json({
|
||||
error: true,
|
||||
code: 'ALREADY_DECIDED',
|
||||
reply: '❌ 该授权请求已处理完毕(状态:' + approval.status + ')。'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证是否为授权人
|
||||
if (!approval.approvers.includes(devId)) {
|
||||
return res.status(403).json({
|
||||
error: true,
|
||||
code: 'NOT_APPROVER',
|
||||
reply: '🔒 你不是这个部署的授权人。授权人:' +
|
||||
approval.approverDetails.map(function(a) { return a.name; }).join('、')
|
||||
});
|
||||
}
|
||||
|
||||
if (decision !== 'approved' && decision !== 'rejected') {
|
||||
return res.status(400).json({
|
||||
error: true,
|
||||
code: 'INVALID_DECISION',
|
||||
reply: '❌ decision 必须是 "approved" 或 "rejected"。'
|
||||
});
|
||||
}
|
||||
|
||||
// 记录决定
|
||||
approval.decisions[devId] = {
|
||||
decision: decision,
|
||||
reason: reason,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
approval.updatedAt = new Date().toISOString();
|
||||
|
||||
// 写入审计日志(不可逆)
|
||||
writeApprovalAudit({
|
||||
action: 'approval_decision',
|
||||
approvalId: approvalId,
|
||||
devId: devId,
|
||||
decision: decision,
|
||||
reason: reason,
|
||||
module: approval.module,
|
||||
channel: approval.channel,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
if (decision === 'rejected') {
|
||||
approval.status = 'rejected';
|
||||
return res.json({
|
||||
success: true,
|
||||
reply: '❌ 已拒绝。部署不会执行。原因已记录到审计日志。'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查是否所有授权人都已通过
|
||||
var allApproved = approval.approvers.every(function(id) {
|
||||
return approval.decisions[id] && approval.decisions[id].decision === 'approved';
|
||||
});
|
||||
|
||||
if (allApproved) {
|
||||
approval.status = 'approved';
|
||||
|
||||
// 触发正式站部署
|
||||
var githubService;
|
||||
try {
|
||||
githubService = require('../services/github');
|
||||
} catch (_) {
|
||||
githubService = null;
|
||||
}
|
||||
|
||||
if (githubService && githubService.triggerWorkflow) {
|
||||
githubService.triggerWorkflow('deploy-to-server.yml', {
|
||||
module: approval.module,
|
||||
deploy_id: approval.deployId,
|
||||
approved_by: approval.approvers.join(','),
|
||||
target: 'production'
|
||||
}).then(function() {
|
||||
writeApprovalAudit({
|
||||
action: 'deploy_triggered',
|
||||
approvalId: approvalId,
|
||||
module: approval.module,
|
||||
approvedBy: approval.approvers,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}).catch(function(err) {
|
||||
console.error('部署触发失败:', err.message);
|
||||
writeApprovalAudit({
|
||||
action: 'deploy_trigger_failed',
|
||||
approvalId: approvalId,
|
||||
error: err.message,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
reply: '✅ 所有授权人已通过。铸渊已自动触发正式站(guanghulab.com)部署。'
|
||||
});
|
||||
}
|
||||
|
||||
// 还有授权人未决定
|
||||
var pending = approval.approvers.filter(function(id) { return !approval.decisions[id]; });
|
||||
res.json({
|
||||
success: true,
|
||||
reply: '✅ 你已授权通过。等待其他授权人确认:' + pending.join('、')
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 查询单个授权状态
|
||||
*/
|
||||
router.get('/:approvalId', function(req, res) {
|
||||
var approval = approvalStore.get(req.params.approvalId);
|
||||
if (!approval) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
code: 'NOT_FOUND',
|
||||
reply: '❌ 授权请求不存在。'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
approval: {
|
||||
id: approval.id,
|
||||
deployId: approval.deployId,
|
||||
module: approval.module,
|
||||
channel: approval.channel,
|
||||
status: approval.status,
|
||||
approvers: approval.approverDetails,
|
||||
decisions: approval.decisions,
|
||||
createdAt: approval.createdAt,
|
||||
updatedAt: approval.updatedAt
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 查询待处理的授权列表(授权人查自己待处理的)
|
||||
*/
|
||||
router.get('/', function(req, res) {
|
||||
var devId = req.user.devId;
|
||||
var pending = [];
|
||||
|
||||
for (var entry of approvalStore) {
|
||||
var approval = entry[1];
|
||||
if (approval.status === 'pending' && approval.approvers.includes(devId) && !approval.decisions[devId]) {
|
||||
pending.push({
|
||||
id: approval.id,
|
||||
module: approval.module,
|
||||
channel: approval.channel,
|
||||
createdAt: approval.createdAt
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
pending: pending,
|
||||
count: pending.length,
|
||||
reply: pending.length > 0
|
||||
? '📋 你有 ' + pending.length + ' 个待授权请求。'
|
||||
: '✅ 没有待处理的授权请求。'
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -48,23 +48,29 @@ app.use('/api', require('./routes/onboarding'));
|
|||
// 执行状态查询路由(需认证)
|
||||
app.use('/api/execution', require('./routes/execution'));
|
||||
|
||||
// 部署授权流程路由(需认证)
|
||||
app.use('/api/approval', require('./routes/approval'));
|
||||
|
||||
// 根路由
|
||||
app.get('/', function(_req, res) {
|
||||
res.json({
|
||||
status: 'ok',
|
||||
service: 'guanghu-api-server',
|
||||
version: '3.0.0',
|
||||
version: '4.0.0',
|
||||
channel: 'B',
|
||||
description: '光湖后端中间层 · 语言驱动操作系统',
|
||||
capabilities: ['read', 'write', 'intent-routing', 'permission-sandbox', 'onboarding', 'execution-guard']
|
||||
capabilities: [
|
||||
'read', 'write', 'intent-routing', 'permission-sandbox',
|
||||
'onboarding', 'execution-guard', 'approval-flow', 'autonomy'
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 3001;
|
||||
app.listen(PORT, '127.0.0.1', function() {
|
||||
console.log('🔗 光湖后端中间层启动 · 端口 ' + PORT);
|
||||
console.log(' 通道B · 语言驱动操作系统');
|
||||
console.log(' 能力:读取 + 写入 + 意图路由 + 权限沙箱 + 认知引导 + 执行保护');
|
||||
console.log(' 通道B · 语言驱动操作系统 v4.0.0');
|
||||
console.log(' 能力:读取 + 写入 + 意图路由 + 权限沙箱 + 认知引导 + 执行保护 + 授权流程 + 系统自治');
|
||||
console.log(' 监听地址:127.0.0.1:' + PORT + '(仅本机访问)');
|
||||
|
||||
// 启动执行看门狗
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
/**
|
||||
* 系统自治运行引擎 · Phase 9
|
||||
*
|
||||
* 系统自主监控运行状态,自动优化,异常时主动报告。
|
||||
* 遵循自治规则:不违反铁律、不绕过天眼审核、不绕过人类授权。
|
||||
*
|
||||
* 版权:国作登字-2026-A-00037559
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var autonomyRules = require('../config/autonomy-rules.json');
|
||||
|
||||
var AUTONOMY_LOG_DIR = process.env.AUTONOMY_LOG_DIR ||
|
||||
path.join(__dirname, '../../logs/autonomy');
|
||||
|
||||
/**
|
||||
* 写入自治日志(所有自主操作记录到审计日志)
|
||||
*/
|
||||
function writeAutonomyLog(entry) {
|
||||
try {
|
||||
fs.mkdirSync(AUTONOMY_LOG_DIR, { recursive: true });
|
||||
var today = new Date().toISOString().split('T')[0];
|
||||
var logFile = path.join(AUTONOMY_LOG_DIR, 'autonomy-' + today + '.jsonl');
|
||||
fs.appendFile(logFile, JSON.stringify(entry) + '\n', function(err) {
|
||||
if (err) console.error('自治日志写入失败:', err.message);
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('自治日志写入失败:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查自治操作是否合规
|
||||
* @param {string} action - 操作描述
|
||||
* @returns {Object} { allowed: boolean, reason: string }
|
||||
*/
|
||||
function checkAutonomyCompliance(action) {
|
||||
var forbidden = autonomyRules.autonomyRules.systemMustNot;
|
||||
var actionLower = action.toLowerCase();
|
||||
|
||||
// 检查是否触及禁止项
|
||||
if (actionLower.includes('权限') && actionLower.includes('修改')) {
|
||||
return { allowed: false, reason: '权限变更只有冰朔能触发' };
|
||||
}
|
||||
if (actionLower.includes('绕过') && actionLower.includes('天眼')) {
|
||||
return { allowed: false, reason: '不得绕过天眼审核' };
|
||||
}
|
||||
if (actionLower.includes('绕过') && actionLower.includes('授权')) {
|
||||
return { allowed: false, reason: '不得绕过人类授权步骤' };
|
||||
}
|
||||
|
||||
return { allowed: true, reason: 'ok' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测冰朔交互模式
|
||||
* @param {string} message - 用户消息
|
||||
* @returns {string} 'instruction' | 'natural'
|
||||
*/
|
||||
function detectInteractionMode(message) {
|
||||
var instructionTriggers = autonomyRules.dualLineArchitecture.lineA.interactionModes.instructionMode.trigger;
|
||||
var msgLower = message.toLowerCase();
|
||||
|
||||
for (var i = 0; i < instructionTriggers.length; i++) {
|
||||
if (msgLower.includes(instructionTriggers[i].toLowerCase())) {
|
||||
return 'instruction';
|
||||
}
|
||||
}
|
||||
return 'natural';
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录自主优化操作
|
||||
* @param {string} action - 操作描述
|
||||
* @param {Object} details - 操作详情
|
||||
*/
|
||||
function logAutonomousAction(action, details) {
|
||||
var compliance = checkAutonomyCompliance(action);
|
||||
if (!compliance.allowed) {
|
||||
console.error('[AUTONOMY] 自治操作被阻止: ' + action + ' — ' + compliance.reason);
|
||||
writeAutonomyLog({
|
||||
type: 'blocked',
|
||||
action: action,
|
||||
reason: compliance.reason,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
writeAutonomyLog({
|
||||
type: 'autonomous_action',
|
||||
action: action,
|
||||
details: details || {},
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部署流水线阶段定义
|
||||
*/
|
||||
function getDeploymentStages() {
|
||||
return autonomyRules.deploymentFlow.stages;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取双线架构配置
|
||||
*/
|
||||
function getDualLineConfig() {
|
||||
return autonomyRules.dualLineArchitecture;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
checkAutonomyCompliance: checkAutonomyCompliance,
|
||||
detectInteractionMode: detectInteractionMode,
|
||||
logAutonomousAction: logAutonomousAction,
|
||||
getDeploymentStages: getDeploymentStages,
|
||||
getDualLineConfig: getDualLineConfig,
|
||||
writeAutonomyLog: writeAutonomyLog
|
||||
};
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
/**
|
||||
* 部署授权样式 · Phase 8
|
||||
* 版权:国作登字-2026-A-00037559
|
||||
*/
|
||||
|
||||
/* 授权提醒徽章 */
|
||||
.approval-badge {
|
||||
position: fixed;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
background: #ff6b35;
|
||||
color: #fff;
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
z-index: 9999;
|
||||
box-shadow: 0 2px 12px rgba(255, 107, 53, 0.4);
|
||||
animation: badge-pulse 2s infinite;
|
||||
}
|
||||
|
||||
.approval-badge:hover {
|
||||
background: #e65a2a;
|
||||
}
|
||||
|
||||
@keyframes badge-pulse {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
}
|
||||
|
||||
/* 授权容器 */
|
||||
.approval-container {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
right: 16px;
|
||||
z-index: 9998;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 授权面板 */
|
||||
.approval-panel {
|
||||
background: #1e1e2e;
|
||||
border: 1px solid #333;
|
||||
border-radius: 12px;
|
||||
width: 380px;
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.approval-panel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 14px 16px;
|
||||
border-bottom: 1px solid #333;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.approval-close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #888;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.approval-close-btn:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.approval-panel-body {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* 授权条目 */
|
||||
.approval-item {
|
||||
background: #252535;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.approval-item-info {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.approval-item-info strong {
|
||||
color: #e0e0e0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.approval-channel {
|
||||
background: #3a3a5a;
|
||||
color: #aaa;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.approval-time {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 授权按钮 */
|
||||
.approval-item-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.approval-btn {
|
||||
flex: 1;
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.approval-btn.approve {
|
||||
background: rgba(15, 123, 108, 0.2);
|
||||
color: #0F7B6C;
|
||||
}
|
||||
|
||||
.approval-btn.approve:hover {
|
||||
background: rgba(15, 123, 108, 0.4);
|
||||
}
|
||||
|
||||
.approval-btn.reject {
|
||||
background: rgba(212, 76, 71, 0.2);
|
||||
color: #D44C47;
|
||||
}
|
||||
|
||||
.approval-btn.reject:hover {
|
||||
background: rgba(212, 76, 71, 0.4);
|
||||
}
|
||||
|
||||
/* 通知消息 */
|
||||
.approval-notification {
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
background: #1e1e2e;
|
||||
border: 1px solid #333;
|
||||
color: #e0e0e0;
|
||||
padding: 12px 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
z-index: 10000;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
|
||||
.approval-notification.fade-out {
|
||||
opacity: 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
/**
|
||||
* 部署授权 UI · Phase 8
|
||||
*
|
||||
* 授权人在交互页面收到授权请求推送时,展示授权弹窗。
|
||||
* 授权人必须主动点击确认,不回应视为不通过。
|
||||
* 系统绝不自动跳过授权步骤。
|
||||
*
|
||||
* 版权:国作登字-2026-A-00037559
|
||||
*/
|
||||
|
||||
/* global HOLOLAKE_ENV */
|
||||
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
var API_BASE = (typeof HOLOLAKE_ENV !== 'undefined' && HOLOLAKE_ENV === 'production')
|
||||
? 'https://guanghulab.com/api' : '';
|
||||
|
||||
/**
|
||||
* ApprovalUI 构造函数
|
||||
*/
|
||||
function ApprovalUI() {
|
||||
this._pollTimer = null;
|
||||
this._container = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化:开始轮询待授权请求
|
||||
* @param {string} devId - 当前登录的开发者编号
|
||||
* @param {string} token - 认证 token
|
||||
*/
|
||||
ApprovalUI.prototype.init = function(devId, token) {
|
||||
this.devId = devId;
|
||||
this.token = token;
|
||||
this._ensureContainer();
|
||||
this._startPolling();
|
||||
};
|
||||
|
||||
/**
|
||||
* 确保授权容器存在
|
||||
*/
|
||||
ApprovalUI.prototype._ensureContainer = function() {
|
||||
if (this._container) return;
|
||||
this._container = document.createElement('div');
|
||||
this._container.id = 'approval-container';
|
||||
this._container.className = 'approval-container';
|
||||
document.body.appendChild(this._container);
|
||||
};
|
||||
|
||||
/**
|
||||
* 开始轮询待授权请求(每 30 秒)
|
||||
*/
|
||||
ApprovalUI.prototype._startPolling = function() {
|
||||
var self = this;
|
||||
if (!API_BASE) return;
|
||||
|
||||
var poll = function() {
|
||||
self._fetchPendingApprovals();
|
||||
};
|
||||
|
||||
poll();
|
||||
this._pollTimer = setInterval(poll, 30000);
|
||||
};
|
||||
|
||||
/**
|
||||
* 停止轮询
|
||||
*/
|
||||
ApprovalUI.prototype.destroy = function() {
|
||||
if (this._pollTimer) {
|
||||
clearInterval(this._pollTimer);
|
||||
this._pollTimer = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取待处理的授权请求
|
||||
*/
|
||||
ApprovalUI.prototype._fetchPendingApprovals = function() {
|
||||
var self = this;
|
||||
if (!API_BASE) return;
|
||||
|
||||
fetch(API_BASE + '/approval', {
|
||||
headers: {
|
||||
'x-dev-id': this.devId,
|
||||
'Authorization': 'Bearer ' + this.token
|
||||
}
|
||||
})
|
||||
.then(function(res) { return res.json(); })
|
||||
.then(function(data) {
|
||||
if (data.success && data.pending && data.pending.length > 0) {
|
||||
self._showApprovalBadge(data.pending.length);
|
||||
self._renderPendingList(data.pending);
|
||||
} else {
|
||||
self._hideApprovalBadge();
|
||||
}
|
||||
})
|
||||
.catch(function() { /* 静默失败 */ });
|
||||
};
|
||||
|
||||
/**
|
||||
* 显示授权提醒徽章
|
||||
*/
|
||||
ApprovalUI.prototype._showApprovalBadge = function(count) {
|
||||
var badge = document.getElementById('approval-badge');
|
||||
if (!badge) {
|
||||
badge = document.createElement('div');
|
||||
badge.id = 'approval-badge';
|
||||
badge.className = 'approval-badge';
|
||||
document.body.appendChild(badge);
|
||||
}
|
||||
badge.textContent = '🔔 ' + count + ' 个待授权';
|
||||
badge.style.display = 'block';
|
||||
var self = this;
|
||||
badge.onclick = function() { self._togglePanel(); };
|
||||
};
|
||||
|
||||
/**
|
||||
* 隐藏授权徽章
|
||||
*/
|
||||
ApprovalUI.prototype._hideApprovalBadge = function() {
|
||||
var badge = document.getElementById('approval-badge');
|
||||
if (badge) badge.style.display = 'none';
|
||||
if (this._container) this._container.style.display = 'none';
|
||||
};
|
||||
|
||||
/**
|
||||
* 切换授权面板显示
|
||||
*/
|
||||
ApprovalUI.prototype._togglePanel = function() {
|
||||
if (this._container) {
|
||||
this._container.style.display =
|
||||
this._container.style.display === 'none' ? 'block' : 'none';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 渲染待授权列表
|
||||
*/
|
||||
ApprovalUI.prototype._renderPendingList = function(pending) {
|
||||
if (!this._container) return;
|
||||
|
||||
var html = '<div class="approval-panel">' +
|
||||
'<div class="approval-panel-header">' +
|
||||
'<span>📋 待授权请求</span>' +
|
||||
'<button class="approval-close-btn" id="approval-close">×</button>' +
|
||||
'</div>' +
|
||||
'<div class="approval-panel-body">';
|
||||
|
||||
for (var i = 0; i < pending.length; i++) {
|
||||
var item = pending[i];
|
||||
html += '<div class="approval-item" data-id="' + item.id + '">' +
|
||||
'<div class="approval-item-info">' +
|
||||
'<strong>' + item.module + '</strong>' +
|
||||
'<span class="approval-channel">' + item.channel + '</span>' +
|
||||
'<span class="approval-time">' + new Date(item.createdAt).toLocaleString('zh-CN') + '</span>' +
|
||||
'</div>' +
|
||||
'<div class="approval-item-actions">' +
|
||||
'<button class="approval-btn approve" data-id="' + item.id + '" data-action="approved">✅ 授权通过</button>' +
|
||||
'<button class="approval-btn reject" data-id="' + item.id + '" data-action="rejected">❌ 拒绝</button>' +
|
||||
'</div></div>';
|
||||
}
|
||||
|
||||
html += '</div></div>';
|
||||
this._container.innerHTML = html;
|
||||
this._container.style.display = 'block';
|
||||
|
||||
// 绑定事件
|
||||
var self = this;
|
||||
var closeBtn = document.getElementById('approval-close');
|
||||
if (closeBtn) {
|
||||
closeBtn.onclick = function() { self._container.style.display = 'none'; };
|
||||
}
|
||||
|
||||
var buttons = this._container.querySelectorAll('.approval-btn');
|
||||
for (var j = 0; j < buttons.length; j++) {
|
||||
buttons[j].addEventListener('click', function() {
|
||||
var approvalId = this.getAttribute('data-id');
|
||||
var action = this.getAttribute('data-action');
|
||||
self._submitDecision(approvalId, action);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 提交授权决定
|
||||
*/
|
||||
ApprovalUI.prototype._submitDecision = function(approvalId, decision) {
|
||||
var self = this;
|
||||
if (!API_BASE) return;
|
||||
|
||||
fetch(API_BASE + '/approval/' + approvalId + '/decide', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-dev-id': this.devId,
|
||||
'Authorization': 'Bearer ' + this.token
|
||||
},
|
||||
body: JSON.stringify({ decision: decision })
|
||||
})
|
||||
.then(function(res) { return res.json(); })
|
||||
.then(function(data) {
|
||||
if (data.reply) {
|
||||
self._showNotification(data.reply);
|
||||
}
|
||||
// 刷新列表
|
||||
self._fetchPendingApprovals();
|
||||
})
|
||||
.catch(function(err) {
|
||||
self._showNotification('❌ 操作失败:' + err.message);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 显示通知消息
|
||||
*/
|
||||
ApprovalUI.prototype._showNotification = function(message) {
|
||||
var notif = document.createElement('div');
|
||||
notif.className = 'approval-notification';
|
||||
notif.textContent = message;
|
||||
document.body.appendChild(notif);
|
||||
setTimeout(function() {
|
||||
notif.classList.add('fade-out');
|
||||
setTimeout(function() { notif.remove(); }, 500);
|
||||
}, 4000);
|
||||
};
|
||||
|
||||
// 导出到全局
|
||||
window.ApprovalUI = ApprovalUI;
|
||||
|
||||
})(window);
|
||||
Loading…
Reference in New Issue