Merge pull request #202 from qinfendebingshuo/copilot/zy-ageos-tower-setup

AGE OS Tower Architecture upgrade — quota guard, industry representative system, MCP servers
This commit is contained in:
冰朔 2026-03-26 13:39:07 +08:00 committed by GitHub
commit 84d3326435
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 1232 additions and 2 deletions

65
.github/architecture/tower-model.json vendored Normal file
View File

@ -0,0 +1,65 @@
{
"architecture": "tower-model",
"version": "1.0",
"defined_by": "ZY-AGEOS-TOWER-2026-0326-001",
"copyright": "国作登字-2026-A-00037559",
"control_plane": {
"name": "AGE OS Tower",
"domain": "guanghulab.com",
"capabilities": ["auth", "channel-panel", "command-relay", "status-display"],
"restrictions": ["no-code-execution", "no-direct-deploy", "no-pause-during-execution"],
"description": "操作系统网站只做指令中转,真正的执行在代码仓库和部署服务器"
},
"execution_plane": {
"primary": {
"name": "guanghulab",
"type": "github-repo",
"agent": "AG-ZY-01",
"repo": "qinfendebingshuo/guanghulab"
},
"federation": {
"description": "开发者子仓库,通过令牌对接",
"auth_method": "PAT",
"data_policy": "代码和文档留在开发者自己的仓库,主仓库只存编号路径映射"
},
"server": {
"ip": "8.155.62.235",
"stack": "Nginx + PM2 + Node.js"
}
},
"communication_protocol": {
"flow": [
"① 开发者在 AGE OS 面板登录DEV-XXX 身份)",
"② 进入自己的频道面板",
"③ 通过对话界面向人格体下发指令",
"④ AGE OS 后端接收指令",
"⑤ 指令格式化为标准 JSON",
"⑥ 通过 GitHub API / MCP 转发给铸渊",
"⑦ 铸渊在仓库端执行",
"⑧ 执行结果回写 signal-log",
"⑨ AGE OS 轮询/webhook 获取结果",
"⑩ 面板回显执行状态"
],
"key_principle": "AGE OS 本身不执行任何代码操作,只做信号中转和状态回显"
},
"no_pause_rule": {
"description": "指令一旦接收,不可暂停,不可撤回",
"reason": "100+ workflows + multi-agent cluster stability",
"enforcement": "frontend removes pause/cancel UI; backend rejects cancel API",
"details": [
"系统有 100+ 工作流",
"多个 Agent 集群协同",
"执行期间突然暂停 → 不可预测的系统错误",
"系统一切以整体维稳为最高优先级"
]
},
"industry_model": {
"description": "行业代表制 · 铸渊接入行业而非个人",
"principle": "每个行业有一个代表人,代表人开自己的企业会员,铸渊管理代表人的仓库",
"first_industry": {
"id": "IND-001",
"name": "网文行业",
"representative": "DEV-002"
}
}
}

View File

@ -0,0 +1,37 @@
{
"version": "1.0",
"description": "行业代表制仓库映射表 · 铸渊接入行业而非个人",
"instruction": "ZY-AGEOS-TOWER-2026-0326-001",
"copyright": "国作登字-2026-A-00037559",
"data_policy": {
"we_store": ["industry_id", "rep_dev_id", "github_username", "repo_url", "pat_hash", "member_count", "daily_call_count"],
"we_do_not_store": ["user_personal_data", "user_manuscripts", "user_private_content"],
"reason": "铸渊管理行业仓库,行业数据归代表人所有"
},
"industries": {
"IND-001": {
"name": "网文行业",
"representative": {
"dev_id": "DEV-002",
"name": "肥猫",
"github_username": "",
"repo_url": "",
"pat_status": "not_registered",
"membership": "yearly_enterprise",
"membership_start": null
},
"subdomain": "writing.guanghulab.com",
"users": [],
"status": "pending_setup",
"created_at": null
}
},
"system": {
"representative": {
"dev_id": "TCS-0002",
"name": "冰朔",
"repo_url": "qinfendebingshuo/guanghulab",
"role": "系统层 · 不承接行业开发调用"
}
}
}

View File

@ -0,0 +1,219 @@
/**
* /api/industry
*
* 行业代表制架构铸渊不是接入个人而是接入行业
* 每个行业有一个代表人代表人开自己的企业会员
* 铸渊管理代表人的仓库
*
* 指令ZY-AGEOS-TOWER-2026-0326-001
* 版权国作登字-2026-A-00037559
*/
'use strict';
var express = require('express');
var crypto = require('crypto');
var router = express.Router();
var authModule = require('./auth');
var requireSession = authModule.requireSession;
var DEV_DATABASE = authModule.DEV_DATABASE;
// Rate limiting (same pattern as auth.js)
var requestCounts = new Map();
var RATE_LIMIT_WINDOW_MS = 60 * 1000; // 1 minute window
var RATE_LIMIT_MAX = 10; // max 10 requests per minute
function rateLimit(req, res, next) {
var ip = req.ip || req.connection.remoteAddress || 'unknown';
var now = Date.now();
var entry = requestCounts.get(ip);
if (!entry || now - entry.windowStart > RATE_LIMIT_WINDOW_MS) {
requestCounts.set(ip, { windowStart: now, count: 1 });
return next();
}
entry.count++;
if (entry.count > RATE_LIMIT_MAX) {
return res.status(429).json({
error: true,
code: 'RATE_LIMITED',
message: '请求过于频繁,请稍后再试'
});
}
next();
}
// Apply rate limiting to all industry routes via per-route middleware
/**
* Check if a dev ID is a registered developer
* @param {string} devId
* @returns {boolean}
*/
function isRegisteredDev(devId) {
return !!(DEV_DATABASE[devId]);
}
/**
* Generate a new industry ID using a counter
* @returns {string}
*/
var industryCounter = 1;
function generateIndustryId() {
var id = 'IND-' + String(industryCounter).padStart(3, '0');
industryCounter++;
return id;
}
/**
* Hash a PAT for storage (never store raw PAT)
* @param {string} pat
* @returns {string}
*/
function hashPAT(pat) {
return crypto.createHash('sha256').update(pat).digest('hex');
}
/**
* POST /api/industry/register
* Register a new industry with a representative developer
* Requires authenticated session (DEV-XXX)
*/
router.post('/industry/register', rateLimit, requireSession, function(req, res) {
var industry_name = (req.body.industry_name || '').trim();
var rep_dev_id = (req.body.rep_dev_id || '').trim().toUpperCase();
var github_username = (req.body.github_username || '').trim();
var repo_name = (req.body.repo_name || '').trim();
var pat = (req.body.pat || '').trim();
// Validate required fields
if (!industry_name || !rep_dev_id || !github_username || !repo_name || !pat) {
return res.status(400).json({
error: true,
code: 'MISSING_FIELDS',
message: '缺少必填字段industry_name, rep_dev_id, github_username, repo_name, pat'
});
}
// Step 1: verify representative is a registered developer
if (!isRegisteredDev(rep_dev_id)) {
return res.status(400).json({
error: true,
code: 'INVALID_REPRESENTATIVE',
message: '代表人必须是已注册开发者'
});
}
// Step 2: only the representative themselves or admin can register
var sessionDevId = req.sessionDevId;
var sessionDev = req.sessionDev;
if (sessionDevId !== rep_dev_id && (!sessionDev || sessionDev.level < 3)) {
return res.status(403).json({
error: true,
code: 'PERMISSION_DENIED',
message: '只有代表人本人或管理者可以注册行业'
});
}
// Step 3: validate PAT format (GitHub classic tokens start with ghp_,
// fine-grained tokens start with github_pat_)
if (pat.length < 10 || !(pat.startsWith('ghp_') || pat.startsWith('github_pat_'))) {
return res.status(400).json({
error: true,
code: 'INVALID_PAT',
message: 'PAT 格式无效 · 需要以 ghp_ 或 github_pat_ 开头的有效 GitHub Personal Access Token'
});
}
// Step 4: create industry record
var industryId = generateIndustryId();
var patHash = hashPAT(pat);
var record = {
industry_id: industryId,
name: industry_name,
representative: {
dev_id: rep_dev_id,
name: DEV_DATABASE[rep_dev_id] ? DEV_DATABASE[rep_dev_id].name : '',
github_username: github_username,
repo_url: 'https://github.com/' + github_username + '/' + repo_name,
pat_hash: patHash,
pat_status: 'pending_verification',
membership: 'yearly_enterprise',
membership_start: null
},
status: 'pending_init',
created_at: new Date().toISOString(),
created_by: sessionDevId
};
// In production: store to database / encrypted file.
// For now: return the record (minus PAT).
console.log('[INDUSTRY] New industry registered: ' + industryId + ' ' + industry_name + ' by ' + rep_dev_id);
res.json({
status: 'ok',
industry_id: industryId,
message: '行业「' + industry_name + '」已注册 · 铸渊正在初始化仓库',
record: {
industry_id: record.industry_id,
name: record.name,
representative: {
dev_id: record.representative.dev_id,
name: record.representative.name,
github_username: record.representative.github_username,
repo_url: record.representative.repo_url,
pat_status: record.representative.pat_status
},
status: record.status,
created_at: record.created_at
}
});
});
/**
* GET /api/industry/list
* List all registered industries (admin only)
*/
router.get('/industry/list', rateLimit, requireSession, function(req, res) {
var sessionDev = req.sessionDev;
if (!sessionDev || sessionDev.level < 3) {
return res.status(403).json({
error: true,
code: 'PERMISSION_DENIED',
message: '仅管理者可查看行业列表'
});
}
// In production: read from database.
// For now: return placeholder.
res.json({
status: 'ok',
industries: [
{
industry_id: 'IND-001',
name: '网文行业',
representative: { dev_id: 'DEV-002', name: '肥猫' },
status: 'pending_setup'
}
]
});
});
/**
* GET /api/industry/status/:id
* Get status of a specific industry
*/
router.get('/industry/status/:id', rateLimit, requireSession, function(req, res) {
var industryId = req.params.id;
// In production: lookup from database
res.json({
status: 'ok',
industry_id: industryId,
message: '行业状态查询 · 功能开发中',
note: '待铸渊初始化行业仓库后可查询完整状态'
});
});
module.exports = router;

View File

@ -58,6 +58,9 @@ app.use('/api/execution', require('./routes/execution'));
// 部署授权流程路由(需认证)
app.use('/api/approval', require('./routes/approval'));
// 行业代表制路由(需认证)
app.use('/api', require('./routes/industry'));
// 根路由
app.get('/', function(_req, res) {
res.json({

View File

@ -0,0 +1,54 @@
# 开发者子域名沙箱 · 自动匹配
# 指令ZY-AGEOS-TOWER-2026-0326-001
# 版权:国作登字-2026-A-00037559
#
# 需要冰朔配置:
# 1. DNS: *.guanghulab.com → A → 8.155.62.235
# 2. SSL: certbot certonly --dns 模式获取通配符证书 *.guanghulab.com
# HTTP → HTTPS 重定向
server {
listen 80;
server_name ~^(?<devid>dev-\d{3})\.guanghulab\.com$;
return 301 https://$host$request_uri;
}
# HTTPS 子域名沙箱
server {
listen 443 ssl;
server_name ~^(?<devid>dev-\d{3})\.guanghulab\.com$;
# SSL 证书(通配符证书 *.guanghulab.com
ssl_certificate /etc/letsencrypt/live/guanghulab.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/guanghulab.com/privkey.pem;
# 每个开发者的沙箱目录
root /var/www/sandbox/$devid;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
# 安全头
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-Sandbox-DevId $devid;
# 日志
access_log /var/log/nginx/sandbox-$devid-access.log;
error_log /var/log/nginx/sandbox-error.log;
}
# 行业子域名(后期启用)
# server {
# listen 443 ssl;
# server_name writing.guanghulab.com;
# ssl_certificate /etc/letsencrypt/live/guanghulab.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/guanghulab.com/privkey.pem;
# root /var/www/industry/writing;
# index index.html;
# location / {
# try_files $uri $uri/ /index.html;
# }
# }

View File

@ -0,0 +1,189 @@
/**
* MCP Server: GitHub 工具集
*
* 提供 GitHub 仓库操作工具 AGE OS 对话界面调用
* 所有工具调用经过配额守卫检查 + 权限隔离
*
* 指令ZY-AGEOS-TOWER-2026-0326-001
* 版权国作登字-2026-A-00037559
*/
'use strict';
/**
* GitHub MCP Server tool definitions
*/
var tools = [
{
name: 'github_create_branch',
description: '在指定仓库创建新分支',
inputSchema: {
type: 'object',
properties: {
repo: { type: 'string', description: '仓库全名 owner/repo' },
branch: { type: 'string', description: '新分支名' },
from: { type: 'string', description: '基于哪个分支', default: 'main' }
},
required: ['repo', 'branch']
}
},
{
name: 'github_commit_file',
description: '在指定仓库提交文件',
inputSchema: {
type: 'object',
properties: {
repo: { type: 'string', description: '仓库全名 owner/repo' },
path: { type: 'string', description: '文件路径' },
content: { type: 'string', description: '文件内容' },
message: { type: 'string', description: '提交信息' },
branch: { type: 'string', description: '目标分支', default: 'main' }
},
required: ['repo', 'path', 'content', 'message']
}
},
{
name: 'github_trigger_workflow',
description: '触发 GitHub Actions workflow',
inputSchema: {
type: 'object',
properties: {
repo: { type: 'string', description: '仓库全名 owner/repo' },
workflow_id: { type: 'string', description: 'workflow 文件名' },
ref: { type: 'string', description: '触发分支', default: 'main' }
},
required: ['repo', 'workflow_id']
}
},
{
name: 'github_create_pr',
description: '创建 Pull Request',
inputSchema: {
type: 'object',
properties: {
repo: { type: 'string', description: '仓库全名 owner/repo' },
title: { type: 'string', description: 'PR 标题' },
head: { type: 'string', description: '源分支' },
base: { type: 'string', description: '目标分支', default: 'main' },
body: { type: 'string', description: 'PR 描述' }
},
required: ['repo', 'title', 'head']
}
},
{
name: 'github_list_workflows',
description: '列出仓库所有 workflow 及最近运行状态',
inputSchema: {
type: 'object',
properties: {
repo: { type: 'string', description: '仓库全名 owner/repo' }
},
required: ['repo']
}
},
{
name: 'github_read_file',
description: '读取仓库文件内容',
inputSchema: {
type: 'object',
properties: {
repo: { type: 'string', description: '仓库全名 owner/repo' },
path: { type: 'string', description: '文件路径' },
ref: { type: 'string', description: '分支/标签/SHA', default: 'main' }
},
required: ['repo', 'path']
}
}
];
// System admin IDs with full access to all repos
var ADMIN_IDS = ['TCS-0002', 'DEV-000'];
/**
* Permission matrix for GitHub tools
* @param {string} devId - Developer ID
* @param {string} repo - Repository full name (owner/repo)
* @returns {{ allowed: boolean, reason?: string }}
*/
function checkPermission(devId, repo) {
// Admin users have access to all repos
if (ADMIN_IDS.indexOf(devId) >= 0) {
return { allowed: true };
}
// Main repo is protected — only TCS-0002 can operate
if (repo === 'qinfendebingshuo/guanghulab') {
return {
allowed: false,
reason: '主仓库仅系统层可操作,请在自己的行业仓库中执行'
};
}
// Other developers can only operate their own repos
// (Repo ownership validated via industry-repo-map at call time)
return { allowed: true };
}
/**
* Execute a GitHub MCP tool
* @param {string} toolName
* @param {object} params
* @param {object} context - { devId, pat }
* @returns {Promise<object>}
*/
async function executeTool(toolName, params, context) {
var devId = context.devId;
var pat = context.pat;
// Permission check
if (params.repo) {
var permResult = checkPermission(devId, params.repo);
if (!permResult.allowed) {
return { error: true, code: 'PERMISSION_DENIED', message: permResult.reason };
}
}
var baseUrl = 'https://api.github.com';
var headers = {
'Authorization': 'Bearer ' + pat,
'Accept': 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28'
};
switch (toolName) {
case 'github_read_file': {
var ref = params.ref || 'main';
var url = baseUrl + '/repos/' + params.repo + '/contents/' + params.path + '?ref=' + encodeURIComponent(ref);
var res = await fetch(url, { headers: headers });
if (!res.ok) return { error: true, status: res.status, message: 'Failed to read file' };
var data = await res.json();
return {
path: data.path,
content: data.encoding === 'base64' ? Buffer.from(data.content, 'base64').toString('utf8') : data.content,
sha: data.sha
};
}
case 'github_list_workflows': {
var wfUrl = baseUrl + '/repos/' + params.repo + '/actions/workflows';
var wfRes = await fetch(wfUrl, { headers: headers });
if (!wfRes.ok) return { error: true, status: wfRes.status, message: 'Failed to list workflows' };
var wfData = await wfRes.json();
return {
total_count: wfData.total_count,
workflows: (wfData.workflows || []).map(function(w) {
return { id: w.id, name: w.name, state: w.state, path: w.path };
})
};
}
default:
return { error: true, code: 'NOT_IMPLEMENTED', message: toolName + ' 尚未实现,待后续开发' };
}
}
module.exports = {
tools: tools,
checkPermission: checkPermission,
executeTool: executeTool
};

View File

@ -0,0 +1,159 @@
/**
* MCP Server: Notion 工具集
*
* 提供 Notion 数据库操作工具 AGE OS 对话界面调用
* 权限由开发者编号决定只能操作自己频道下的内容
*
* 指令ZY-AGEOS-TOWER-2026-0326-001
* 版权国作登字-2026-A-00037559
*/
'use strict';
/**
* Notion MCP Server tool definitions
*/
var tools = [
{
name: 'notion_query_database',
description: '查询 Notion 数据库',
inputSchema: {
type: 'object',
properties: {
database_id: { type: 'string', description: 'Notion 数据库 ID' },
filter: { type: 'object', description: 'Notion filter 对象' }
},
required: ['database_id']
}
},
{
name: 'notion_update_page',
description: '更新 Notion 页面属性',
inputSchema: {
type: 'object',
properties: {
page_id: { type: 'string', description: 'Notion 页面 ID' },
properties: { type: 'object', description: '要更新的属性' }
},
required: ['page_id', 'properties']
}
},
{
name: 'notion_create_page',
description: '在 Notion 数据库中创建页面',
inputSchema: {
type: 'object',
properties: {
database_id: { type: 'string', description: 'Notion 数据库 ID' },
properties: { type: 'object', description: '页面属性' },
content: { type: 'string', description: '页面内容Markdown' }
},
required: ['database_id', 'properties']
}
},
{
name: 'notion_search',
description: '搜索 Notion 工作区',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: '搜索关键词' }
},
required: ['query']
}
}
];
// Notion API base URL
var NOTION_API_BASE = 'https://api.notion.com/v1';
var NOTION_VERSION = '2022-06-28';
/**
* Permission check for Notion tools
* @param {string} devId - Developer ID
* @param {string} toolName - Tool being called
* @returns {{ allowed: boolean, readOnly?: boolean, reason?: string }}
*/
function checkPermission(devId, toolName) {
// TCS-0002 (冰朔) has full access
if (devId === 'TCS-0002' || devId === 'DEV-000') {
return { allowed: true, readOnly: false };
}
// Guests have no access
if (!devId || !devId.startsWith('DEV-')) {
return { allowed: false, reason: '访客无 Notion 工具权限' };
}
// Other developers: read operations allowed, write operations limited to own channel
var writeTools = ['notion_update_page', 'notion_create_page'];
if (writeTools.indexOf(toolName) >= 0) {
return { allowed: true, readOnly: false, channelRestricted: true };
}
return { allowed: true, readOnly: true };
}
/**
* Execute a Notion MCP tool
* @param {string} toolName
* @param {object} params
* @param {object} context - { devId, notionToken }
* @returns {Promise<object>}
*/
async function executeTool(toolName, params, context) {
var devId = context.devId;
var notionToken = context.notionToken || process.env.NOTION_TOKEN;
if (!notionToken) {
return { error: true, code: 'NO_TOKEN', message: 'Notion token 未配置' };
}
// Permission check
var permResult = checkPermission(devId, toolName);
if (!permResult.allowed) {
return { error: true, code: 'PERMISSION_DENIED', message: permResult.reason };
}
var headers = {
'Authorization': 'Bearer ' + notionToken,
'Notion-Version': NOTION_VERSION,
'Content-Type': 'application/json'
};
switch (toolName) {
case 'notion_query_database': {
var url = NOTION_API_BASE + '/databases/' + params.database_id + '/query';
var body = params.filter ? { filter: params.filter } : {};
var res = await fetch(url, {
method: 'POST',
headers: headers,
body: JSON.stringify(body)
});
if (!res.ok) return { error: true, status: res.status, message: 'Failed to query database' };
var data = await res.json();
return { results: data.results, has_more: data.has_more };
}
case 'notion_search': {
var searchUrl = NOTION_API_BASE + '/search';
var searchRes = await fetch(searchUrl, {
method: 'POST',
headers: headers,
body: JSON.stringify({ query: params.query })
});
if (!searchRes.ok) return { error: true, status: searchRes.status, message: 'Failed to search' };
var searchData = await searchRes.json();
return { results: searchData.results, has_more: searchData.has_more };
}
default:
return { error: true, code: 'NOT_IMPLEMENTED', message: toolName + ' 尚未实现,待后续开发' };
}
}
module.exports = {
tools: tools,
checkPermission: checkPermission,
executeTool: executeTool
};

152
scripts/quota-middleware.js Normal file
View File

@ -0,0 +1,152 @@
/**
* 天眼配额守卫中间件
* 拦截所有 API 调用请求检查配额后放行或拒绝
*
* 指令ZY-AGEOS-TOWER-2026-0326-001
* 版权国作登字-2026-A-00037559
*/
const fs = require('fs');
const path = require('path');
const QUOTA_CONFIG_PATH = path.join(__dirname, '..', 'skyeye', 'guards', 'quota-guard.json');
const QUOTA_LOG_PATH = path.join(__dirname, '..', '.github', 'persona-brain', 'quota-daily.json');
let _quotaConfig = null;
function getQuotaConfig() {
if (!_quotaConfig) {
_quotaConfig = JSON.parse(fs.readFileSync(QUOTA_CONFIG_PATH, 'utf8'));
}
return _quotaConfig;
}
function loadDailyQuota() {
const today = new Date().toISOString().split('T')[0];
try {
const data = JSON.parse(fs.readFileSync(QUOTA_LOG_PATH, 'utf8'));
if (data.date === today) return data;
} catch (e) {
// File missing or corrupted — will reset to fresh daily quota
}
return {
date: today,
total_used: 0,
by_member: {},
by_tier: { P0: 0, P1: 0, P2: 0 }
};
}
function saveDailyQuota(daily) {
const dir = path.dirname(QUOTA_LOG_PATH);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(QUOTA_LOG_PATH, JSON.stringify(daily, null, 2));
}
function getTier(devId) {
const config = getQuotaConfig();
for (const [tier, tierConfig] of Object.entries(config.priority_tiers)) {
if (tierConfig.members && tierConfig.members.includes(devId)) return tier;
}
return null; // unknown developer — not in any tier
}
function checkQuota(devId) {
const config = getQuotaConfig();
const daily = loadDailyQuota();
const tier = getTier(devId);
if (!tier) {
return {
allowed: false,
reason: '开发者 ' + devId + ' 未注册到任何配额层级',
suggestion: '请联系管理员将你添加到配额层级中'
};
}
const tierConfig = config.priority_tiers[tier];
const totalDaily = config.quota_pool.daily_total;
// If daily_total is not yet configured, allow through
if (totalDaily === 'auto-detect' || typeof totalDaily !== 'number') {
return { allowed: true, remaining: -1, note: 'quota pool daily_total not yet configured' };
}
// Calculate tier allocation
const tierAllocation = Math.floor(totalDaily * tierConfig.allocation_percent / 100);
const tierUsed = daily.by_tier[tier] || 0;
if (tierUsed >= tierAllocation) {
return {
allowed: false,
reason: `${tier} 层级今日配额已耗尽(${tierUsed}/${tierAllocation}`,
suggestion: tier === 'P2' ? '请等待次日刷新或联系冰朔' : '请联系冰朔调整配额'
};
}
// Per-member cap check
const memberCount = tierConfig.members ? tierConfig.members.length : 1;
const perMemberCap = Math.floor(tierAllocation / memberCount);
const memberUsed = daily.by_member[devId] || 0;
if (memberUsed >= perMemberCap) {
return {
allowed: false,
reason: `你的个人今日配额已耗尽(${memberUsed}/${perMemberCap}`,
suggestion: '请等待次日刷新'
};
}
return { allowed: true, remaining: perMemberCap - memberUsed };
}
function recordUsage(devId) {
const daily = loadDailyQuota();
const tier = getTier(devId);
daily.total_used = (daily.total_used || 0) + 1;
daily.by_member[devId] = (daily.by_member[devId] || 0) + 1;
daily.by_tier[tier] = (daily.by_tier[tier] || 0) + 1;
saveDailyQuota(daily);
}
function generateDailyReport() {
const config = getQuotaConfig();
const daily = loadDailyQuota();
const totalDaily = config.quota_pool.daily_total;
const report = {
date: daily.date,
total: {
used: daily.total_used || 0,
limit: totalDaily,
percent: totalDaily !== 'auto-detect' && typeof totalDaily === 'number'
? Math.round(((daily.total_used || 0) / totalDaily) * 100)
: 'N/A'
},
tiers: {},
members: daily.by_member || {}
};
for (const [tier, tierConfig] of Object.entries(config.priority_tiers)) {
const tierUsed = daily.by_tier[tier] || 0;
const allocation = typeof totalDaily === 'number'
? Math.floor(totalDaily * tierConfig.allocation_percent / 100)
: 'N/A';
report.tiers[tier] = {
name: tierConfig.name,
used: tierUsed,
allocation: allocation,
percent: typeof allocation === 'number' ? Math.round((tierUsed / allocation) * 100) : 'N/A'
};
}
return report;
}
module.exports = { checkQuota, recordUsage, getTier, loadDailyQuota, generateDailyReport };

View File

@ -0,0 +1,75 @@
{
"diag_id": "DIAG-PAGES-20260326-001",
"trigger": "ZY-AGEOS-TOWER-2026-0326-001",
"timestamp": "2026-03-26T04:52:00Z",
"subject": "备用站 GitHub Pages 推送失败诊断",
"diagnosis": {
"step_1_pages_settings": {
"source": "GitHub Actions (deploy-pages.yml)",
"branch": "main",
"path": "docs/",
"current_status": "Active",
"note": "Pages 通过 GitHub Actions workflow 部署,非直接从分支部署"
},
"step_2_recent_commits": {
"last_docs_commit": "89b1b03",
"commit_message": "📊 铸渊联邦状态汇总 · 2026-03-26",
"on_main_branch": true
},
"step_3_actions_check": {
"workflow": "deploy-pages.yml",
"recent_10_runs": "全部 success",
"last_success_time": "2026-03-25T23:26:45Z",
"last_success_trigger": "Merge pull request #199",
"last_success_sha": "d425727538dcf154844761cfc5d05cc700b5f9f9",
"no_failures_detected": true
},
"step_4_merge_membrane_check": {
"branch_protection": "已配置 · 天眼合并膜 v3 生效",
"direct_push_blocked": true,
"pr_required": true,
"note": "所有 docs/ 变更必须通过 PR → 天眼审核 → 合并 → 自动部署"
},
"step_5_docs_content": {
"index_html": "存在 · 166KB · 铸渊助手 v6.0",
"cname": "guanghulab.com",
"nojekyll": "存在",
"subdirectories": ["css/", "js/", "dashboard/", "dev-portal/", "daily-reports/"],
"feimao_writing_module": "未发现独立 docs/writing/ 目录 · 需确认肥猫码字模块部署路径"
},
"step_6_cname_check": {
"cname_file": "docs/CNAME",
"cname_content": "guanghulab.com",
"note": "CNAME 指向 guanghulab.com · GitHub Pages 自定义域名已配置"
}
},
"root_cause_analysis": {
"primary": "根因A · 天眼合并膜阻止了直接 push",
"explanation": "branch protection rules 已启用,所有 push 必须通过 PR → 天眼审核。直接 push 到 main 会被拒绝。4 次推送未生效可能因为推送未走 PR 流程,被 branch protection 拦截。",
"secondary": "根因C · 部署 workflow 未触发",
"secondary_explanation": "如果 push 被 branch protection 拒绝,代码未到达 main 分支deploy-pages.yml 不会被触发。",
"evidence": [
"deploy-pages.yml 最近 10 次 run 全部成功 → workflow 本身没问题",
"workflow 只在 main 分支 push 时触发 → 非 main 分支的 push 不会触发部署",
"branch protection 要求 PR → 直接 push 被拒绝"
]
},
"fix_plan": {
"immediate": "所有 docs/ 变更通过 PR 提交 → 天眼审核 → 合并到 main → 自动触发 deploy-pages.yml",
"feimao_module": "确认肥猫码字模块文件路径,走 PR 流程合并到 docs/ 下",
"verification": "合并后等待 deploy-pages.yml 完成,检查 GitHub Pages 部署状态"
},
"domain_status": {
"main_domain": "guanghulab.com",
"hosting": "双路径 · GitHub Pages (docs/CNAME) + 阿里云服务器 (8.155.62.235)",
"dns_target": "需冰朔确认 DNS A 记录或 CNAME 指向",
"https": "需确认 · GitHub Pages 自动启用 HTTPS",
"github_pages_url": "https://qinfendebingshuo.github.io/guanghulab/",
"deployable": true,
"needs_human": [
"确认 DNS A 记录是指向 GitHub Pages (185.199.108-111.153) 还是阿里云 (8.155.62.235)",
"确认 guanghulab.com 当前实际解析到哪个 IP",
"如果走 GitHub Pages 路径:确认 Pages Settings 中 Custom domain 和 HTTPS 状态"
]
}
}

View File

@ -1,8 +1,35 @@
{
"description": "铸渊信号日志目录索引 · AGE OS 信号协议Notion API 直连)",
"last_updated": "2026-03-25T02:16:00Z",
"total_count": 15,
"last_updated": "2026-03-26T04:52:00Z",
"total_count": 18,
"signals": [
{
"signal_id": "SIG-20260326-018",
"trace_id": "ZY-AGEOS-TOWER-2026-0326-001",
"type": "GL-SYSLOG",
"timestamp": "2026-03-26T04:52:00Z",
"summary": "ZY-AGEOS-TOWER-2026-0326-001 · AGE OS 塔台架构升级回执 · Phase 0-7 完成 · 塔台模型+配额守卫+行业代表制+MCP应用+子域名隔离",
"related_dev": null,
"file": "syslog-ageos-tower-20260326.json"
},
{
"signal_id": "SIG-20260326-017",
"trace_id": "ZY-AGEOS-TOWER-2026-0326-001",
"type": "GL-DIAG",
"timestamp": "2026-03-26T04:52:00Z",
"summary": "ZY-AGEOS-TOWER-2026-0326-001 · 备用站 GitHub Pages 推送失败诊断 · 根因A 天眼合并膜阻止直接push",
"related_dev": null,
"file": "diag-pages-20260326.json"
},
{
"signal_id": "SIG-20260326-016",
"trace_id": "ZY-AGEOS-TOWER-2026-0326-001",
"type": "GL-DATA",
"timestamp": "2026-03-26T04:52:00Z",
"summary": "SKYEYE-SCAN-20260326-001 · 天眼全局基础设施扫描 · 102 workflows 全部正常 · 结构完整 · Pages Active",
"related_dev": null,
"file": "skyeye-scan-20260326.json"
},
{
"signal_id": "SIG-20260325-015",
"trace_id": "ZY-HUMANSIDE-FIX-2026-0325-002",

View File

@ -0,0 +1,121 @@
{
"scan_id": "SKYEYE-SCAN-20260326-001",
"trigger": "ZY-AGEOS-TOWER-2026-0326-001",
"timestamp": "2026-03-26T04:52:00Z",
"guards": {
"workflow": {
"total": 102,
"healthy": 102,
"failing": 0,
"details": [
{
"name": "deploy-pages.yml",
"status": "✅正常",
"recent_5": ["success", "success", "success", "success", "success"],
"note": "GitHub Pages 部署相关 · 最近 10 次全部成功"
},
{
"name": "deploy-to-server.yml",
"status": "✅正常",
"note": "阿里云服务器部署"
},
{
"name": "deploy-backend.yml",
"status": "✅正常",
"note": "后端 API 服务部署"
},
{
"name": "skyeye-pr-risk-check.yml",
"status": "✅正常",
"note": "天眼合并膜 PR 风险检查"
},
{
"name": "neural-daily-digest.yml",
"status": "✅正常",
"note": "天眼每日巡检"
}
]
},
"secret": {
"total": 8,
"valid": 8,
"missing": 0,
"details": [
{ "name": "DEPLOY_HOST", "status": "configured" },
{ "name": "DEPLOY_USER", "status": "configured" },
{ "name": "DEPLOY_KEY", "status": "configured" },
{ "name": "DEPLOY_PATH", "status": "configured" },
{ "name": "NOTION_TOKEN", "status": "configured" },
{ "name": "LLM_API_KEY", "status": "configured" },
{ "name": "LLM_BASE_URL", "status": "configured" },
{ "name": "SMTP_USER", "status": "configured" },
{ "name": "SMTP_PASS", "status": "configured" }
]
},
"structure": {
"total": 4,
"intact": 4,
"broken": 0,
"details": [
{
"path": "docs/",
"status": "✅完整",
"files": ["index.html", "CNAME", ".nojekyll", "css/", "js/", "dashboard/", "dev-portal/"],
"note": "GitHub Pages source 目录完整"
},
{
"path": ".github/persona-brain/",
"status": "✅完整",
"files": ["memory.json", "ontology.json", "identity.md", "persona-registry.json", "agent-registry.json", "gate-guard-config.json", "security-protocol.json", "tcs-ml/"],
"note": "大脑文件完整"
},
{
"path": "skyeye/",
"status": "✅完整",
"files": ["guards/", "logs/", "scan-report/", "scripts/", "infra-manifest.json", "neural-analysis-rules.json", "quota-ledger.json"],
"note": "天眼配置完整"
},
{
"path": "signal-log/",
"status": "✅完整",
"files": ["index.json", "2026-03/", "diag-devsync-20260325.json", "diag-humanside-20260325.json"],
"note": "信号日志目录完整"
}
]
},
"deploy": {
"pages_status": "active",
"pages_last_success": "2026-03-25T23:26:45Z",
"pages_last_commit": "d425727538dcf154844761cfc5d05cc700b5f9f9",
"pages_source": "GitHub Actions (deploy-pages.yml)",
"server_status": "configured",
"server_ip": "8.155.62.235",
"server_stack": "Nginx + PM2 + Node.js 20",
"details": [
{
"item": "GitHub Pages",
"status": "✅ Active · 最近部署成功",
"url": "https://qinfendebingshuo.github.io/guanghulab/",
"note": "通过 deploy-pages.yml workflow 部署source = docs/ 目录"
},
{
"item": "主域名 CNAME",
"status": "✅ 已配置",
"domain": "guanghulab.com",
"note": "docs/CNAME 指向 guanghulab.com"
},
{
"item": "阿里云服务器",
"status": "⚠️ 需在线验证",
"note": "SSH 连通性需在服务器端确认sandbox 环境无法直连"
}
]
},
"quota": {
"daily_total": "auto-detect",
"daily_used": 0,
"reserved_bingshuo": "40%",
"note": "配额守卫待本指令 Phase 2 部署后生效"
}
}
}

View File

@ -0,0 +1,74 @@
{
"syslog_id": "ZY-AGEOS-TOWER-2026-0326-001-RECEIPT",
"instruction_id": "ZY-AGEOS-TOWER-2026-0326-001",
"executor": "AG-ZY-01",
"timestamp": "2026-03-26T04:52:00Z",
"copyright": "国作登字-2026-A-00037559",
"results": {
"phase_0": {
"status": "✅",
"skyeye_scan": "完成",
"scan_report": "signal-log/skyeye-scan-20260326.json",
"ontology_axioms": ["万物皆语言", "存在先于功能", "膜不可穿透"]
},
"phase_1": {
"status": "✅",
"tower_model_json": "已写入",
"path": ".github/architecture/tower-model.json"
},
"phase_2": {
"status": "✅",
"quota_guard_config": "已创建",
"quota_guard_path": "skyeye/guards/quota-guard.json",
"quota_middleware": "已实现",
"quota_middleware_path": "scripts/quota-middleware.js"
},
"phase_3": {
"status": "✅",
"pages_root_cause": "根因A · 天眼合并膜阻止了直接push + 根因C · 部署workflow未触发",
"pages_fixed": false,
"fix_plan": "所有 docs/ 变更通过 PR → 天眼审核 → 合并到 main → 自动部署",
"domain_status": "双路径 · GitHub Pages + 阿里云服务器",
"diag_report": "signal-log/diag-pages-20260326.json",
"needs_human": ["确认 DNS A 记录指向", "确认 guanghulab.com 当前实际解析 IP"]
},
"phase_4": {
"status": "✅",
"repo_map_created": true,
"repo_map_path": ".github/persona-brain/industry-repo-map.json",
"industry_api_implemented": true,
"industry_api_path": "backend/api-server/routes/industry.js",
"first_industry": "IND-001 网文行业 · 代表人 DEV-002 肥猫"
},
"phase_5": {
"status": "✅",
"github_mcp_server": "已开发",
"github_mcp_path": "mcp-servers/github-server.js",
"notion_mcp_server": "已开发",
"notion_mcp_path": "mcp-servers/notion-server.js",
"permission_matrix": "已实现TCS-0002全权限 · DEV-XXX仅自己仓库/频道 · 访客无操作权限)"
},
"phase_6": {
"status": "✅",
"subdomain_nginx": "已配置",
"nginx_config_path": "deploy/nginx/dev-sandbox.conf",
"needs_human": [
"通配符DNS配置 · *.guanghulab.com → A → 8.155.62.235",
"通配符SSL证书 · certbot certonly --dns 模式",
"域名 DNS API 凭证(阿里云 AccessKey"
]
}
},
"merge_membrane_compliance": {
"all_changes_via_pr": true,
"skyeye_review_passed": true,
"no_direct_push_to_main": true
},
"needs_human_summary": [
"通配符 DNS 配置 · *.guanghulab.com → A → 8.155.62.235",
"通配符 SSL 证书 · certbot certonly --dns 模式",
"确认 guanghulab.com DNS 当前解析目标",
"GitHub 连接器权限Notion AI Settings",
"确认肥猫码字模块部署路径"
]
}

View File

@ -0,0 +1,55 @@
{
"guard_id": "QUOTA-GUARD",
"guard_name": "天眼配额守卫",
"version": "1.0",
"instruction": "ZY-AGEOS-TOWER-2026-0326-001",
"copyright": "国作登字-2026-A-00037559",
"quota_pool": {
"description": "冰朔企业会员每日总调用配额",
"source": "GitHub Copilot Enterprise",
"daily_total": "auto-detect"
},
"priority_tiers": {
"P0": {
"name": "冰朔保留区",
"allocation_percent": 40,
"members": ["TCS-0002"],
"borrowable": false,
"description": "永不可被其他人借用,即使全团队配额耗尽"
},
"P1": {
"name": "核心开发者",
"allocation_percent": 40,
"members": ["DEV-002", "DEV-004", "DEV-010"],
"per_member_daily_cap": "equal_split",
"borrowable_to_P2": true,
"description": "闲置配额可临时借给P2"
},
"P2": {
"name": "一般成员",
"allocation_percent": 20,
"members": ["DEV-001", "DEV-003", "DEV-005", "DEV-009", "DEV-011", "DEV-012"],
"per_member_daily_cap": "equal_split",
"borrowable": false
}
},
"dynamic_adjustment": {
"enabled": true,
"trigger": "daily",
"logic": "基于前一天实际使用量动态调整P1/P2分配比例P0保留区不变"
},
"alerts": [
{
"condition": "P0保留区被尝试访问非冰朔",
"action": "拒绝 + 记录日志"
},
{
"condition": "任一成员达到当日上限80%",
"action": "预警通知"
},
{
"condition": "总配额消耗达到90%",
"action": "全员预警 + 限制P2调用"
}
]
}