guanghulab/.github/workflows/cos-dev-review-bridge.yml
2026-05-10 13:12:44 +08:00

629 lines
24 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ============================================
# COS 行业开发审核桥接工作流
# COS Industry Development Review Bridge
# 签发: 铸渊 · ICE-GL-ZY001
# 版权: 国作登字-2026-A-00037559
# ============================================
#
# 功能:
# 1. 定时扫描COS桶中的行业开发提交
# 2. 自动审核开发方案(架构/安全/命名)
# 3. 生成审核回执并写入COS
# 4. (可选) 通过repository_dispatch触发对方仓库的副驾驶
#
# 触发方式:
# - 定时: 每5分钟 (与COS Watcher同步)
# - 手动: workflow_dispatch
#
# 架构文档: brain/age-os-landing/industry-access-architecture.md
# 协议规范: hldp/hnl/industry-bridge-protocol.json
# 租户注册表: server/proxy/config/industry-tenant-registry.json
name: COS开发审核桥接 · Dev Review Bridge
on:
schedule:
- cron: '*/5 * * * *' # 每5分钟
workflow_dispatch:
inputs:
scan_mode:
description: '扫描模式'
required: false
default: 'auto'
type: choice
options:
- auto # 自动扫描所有行业
- manual # 手动指定行业和人格体
industry_code:
description: '行业代码 (manual模式必填)'
required: false
type: string
persona_id:
description: '人格体ID (manual模式必填)'
required: false
type: string
dry_run:
description: '仅审核不写回执'
required: false
default: false
type: boolean
env:
COS_BUCKET: zy-team-hub-1317346199
COS_REGION: ap-guangzhou
NODE_VERSION: '20'
permissions:
contents: read
jobs:
scan-and-review:
name: 扫描 → 审核 → 回执
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: 🏔️ 铸渊唤醒 · 审核桥接启动
run: |
echo "============================================"
echo "铸渊 · ICE-GL-ZY001"
echo "COS行业开发审核桥接"
echo "时间: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "模式: ${{ github.event.inputs.scan_mode || 'auto' }}"
echo "============================================"
- name: 📥 检出仓库
uses: actions/checkout@v4
- name: 🟢 Node.js 环境
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: 📦 安装COS SDK
run: |
npm install cos-nodejs-sdk-v5@2
- name: 🔍 扫描COS桶 · 检测新的开发提交
id: scan
env:
COS_SECRET_ID: ${{ secrets.ZY_COS_SECRET_ID }}
COS_SECRET_KEY: ${{ secrets.ZY_COS_SECRET_KEY }}
run: |
node << 'SCAN_SCRIPT'
const COS = require('cos-nodejs-sdk-v5');
const fs = require('fs');
const path = require('path');
const cos = new COS({
SecretId: process.env.COS_SECRET_ID,
SecretKey: process.env.COS_SECRET_KEY
});
const BUCKET = '${{ env.COS_BUCKET }}';
const REGION = '${{ env.COS_REGION }}';
// 读取租户注册表
const registry = JSON.parse(
fs.readFileSync('server/proxy/config/industry-tenant-registry.json', 'utf8')
);
// 通过检查receipts目录判断提交是否已审核持久化去重
async function getExistingReceipts(tenant) {
const receiptPrefix = `industry/${tenant.industry_code}/dev-receipts/${tenant.tech_controller.persona_id}/`;
return new Promise((resolve) => {
cos.getBucket({
Bucket: BUCKET,
Region: REGION,
Prefix: receiptPrefix,
MaxKeys: 1000
}, (err, data) => {
if (err) { resolve(new Set()); return; }
// 从回执中提取已审核的submission_id集合
// 回执文件名包含时间戳无法直接对应改为列出所有回执key
const receiptKeys = new Set((data.Contents || []).map(item => item.Key));
resolve(receiptKeys);
});
});
}
async function scanIndustry(tenant) {
const prefix = `industry/${tenant.industry_code}/dev-submissions/${tenant.tech_controller.persona_id}/`;
const receiptPrefix = `industry/${tenant.industry_code}/dev-receipts/${tenant.tech_controller.persona_id}/`;
console.log(`扫描: ${prefix}`);
// 获取已有回执列表用于去重
const existingReceipts = await getExistingReceipts(tenant);
console.log(`已有回执数: ${existingReceipts.size}`);
return new Promise((resolve, reject) => {
cos.getBucket({
Bucket: BUCKET,
Region: REGION,
Prefix: prefix,
MaxKeys: 100
}, (err, data) => {
if (err) {
console.error(`扫描失败: ${prefix}`, err.message);
resolve([]);
return;
}
const allSubmissions = (data.Contents || [])
.filter(item => item.Key.endsWith('.json'));
// 过滤已有回执的提交通过检查submission文件名是否在receipts中被引用
// 简单策略如果receipts数量 >= submissions数量说明都已审核过
// 精确策略需要读取每个receipt的ref_submission_id但开销大
// 采用时间策略只处理最近24小时内的提交
const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
const submissions = allSubmissions
.filter(item => item.LastModified > oneDayAgo)
.map(item => ({
key: item.Key,
lastModified: item.LastModified,
size: item.Size,
tenant: tenant
}));
console.log(`发现 ${submissions.length} 个待审核提交 (24h内)`);
resolve(submissions);
});
});
}
async function main() {
const scanMode = '${{ github.event.inputs.scan_mode || 'auto' }}';
let tenantsToScan = registry.tenants;
if (scanMode === 'manual') {
const industryCode = '${{ github.event.inputs.industry_code }}';
const personaId = '${{ github.event.inputs.persona_id }}';
tenantsToScan = registry.tenants.filter(t =>
t.industry_code === industryCode &&
t.tech_controller.persona_id === personaId
);
}
let allSubmissions = [];
for (const tenant of tenantsToScan) {
const subs = await scanIndustry(tenant);
allSubmissions = allSubmissions.concat(subs);
}
// 输出到GitHub Actions
fs.writeFileSync('/tmp/new-submissions.json', JSON.stringify(allSubmissions, null, 2));
const count = allSubmissions.length;
console.log(`\n总计发现 ${count} 个新的开发提交`);
// 设置输出
const hasNew = count > 0 ? 'true' : 'false';
fs.appendFileSync(process.env.GITHUB_OUTPUT, `has_new=${hasNew}\n`);
fs.appendFileSync(process.env.GITHUB_OUTPUT, `count=${count}\n`);
}
main().catch(err => {
console.error('扫描失败:', err);
process.exit(1);
});
SCAN_SCRIPT
- name: 📋 审核开发方案
if: steps.scan.outputs.has_new == 'true'
id: review
env:
COS_SECRET_ID: ${{ secrets.ZY_COS_SECRET_ID }}
COS_SECRET_KEY: ${{ secrets.ZY_COS_SECRET_KEY }}
run: |
node << 'REVIEW_SCRIPT'
const COS = require('cos-nodejs-sdk-v5');
const fs = require('fs');
const cos = new COS({
SecretId: process.env.COS_SECRET_ID,
SecretKey: process.env.COS_SECRET_KEY
});
const BUCKET = '${{ env.COS_BUCKET }}';
const REGION = '${{ env.COS_REGION }}';
const DRY_RUN = '${{ github.event.inputs.dry_run }}' === 'true';
// 已知有CVE漏洞的依赖 (包名和可选版本)
const KNOWN_VULNERABLE_DEPS = [
{ name: 'event-stream', version: null },
{ name: 'ua-parser-js', version: '0.7.28' },
{ name: 'colors', version: '1.4.1' },
{ name: 'faker', version: '6.6.6' },
{ name: 'node-ipc', version: '10.1.1' }
];
// 禁止暴露的端口
const FORBIDDEN_PORTS = [22, 3306, 5432, 6379, 27017];
// 精确匹配依赖名称(处理 @scope/package@version 格式)
function isVulnerableDep(depString) {
const depLower = depString.toLowerCase().trim();
// 解析包名和版本: @scope/package@version 或 package@version 或 package
let depName, depVersion;
const lastAtIndex = depLower.lastIndexOf('@');
if (lastAtIndex > 0) {
depName = depLower.substring(0, lastAtIndex);
depVersion = depLower.substring(lastAtIndex + 1);
} else {
depName = depLower;
depVersion = null;
}
return KNOWN_VULNERABLE_DEPS.some(vuln => {
if (depName !== vuln.name.toLowerCase()) return false;
if (vuln.version === null) return true; // 所有版本都有漏洞
if (depVersion === null) return true; // 未指定版本,保守判定
return depVersion === vuln.version;
});
}
function reviewSubmission(submission) {
const result = {
architecture: 'PASS',
security: 'PASS',
naming: 'PASS',
overall: 'APPROVED',
suggestions: [],
warnings: [],
blockers: []
};
const payload = submission.payload || {};
const data = payload.data || {};
const impact = data.architecture_impact || {};
// --- 架构审核 ---
if (impact.database_changes && impact.database_changes.length > 0) {
result.suggestions.push('数据库变更建议在测试环境先验证');
for (const change of impact.database_changes) {
if (change.toLowerCase().includes('persona_registry') ||
change.toLowerCase().includes('light_tree') ||
change.toLowerCase().includes('tianyan')) {
result.architecture = 'FAIL';
result.blockers.push(`禁止直接修改铸渊核心表: ${change}`);
}
}
}
if (impact.new_api_endpoints) {
for (const endpoint of impact.new_api_endpoints) {
if (endpoint.startsWith('/hli/')) {
result.architecture = 'FAIL';
result.blockers.push(`禁止在行业层使用 /hli/ 前缀(铸渊专用): ${endpoint}`);
}
}
}
// --- 安全审核 ---
if (impact.external_dependencies) {
for (const dep of impact.external_dependencies) {
if (isVulnerableDep(dep)) {
result.security = 'FAIL';
result.blockers.push(`依赖有已知CVE漏洞: ${dep}`);
}
}
}
// --- 命名审核 ---
if (!submission.hldp_v || submission.hldp_v !== '3.0') {
result.naming = 'WARN';
result.warnings.push('HLDP版本应为3.0');
}
if (!submission.msg_id || !submission.msg_id.startsWith('HLDP-')) {
result.naming = 'FAIL';
result.blockers.push('msg_id格式不正确·应以HLDP-开头');
}
if (payload.data && payload.data.submission_type) {
const validTypes = ['dev_proposal', 'hotfix', 'dependency_update', 'architecture_change'];
if (!validTypes.includes(payload.data.submission_type)) {
result.naming = 'WARN';
result.warnings.push(`未知的submission_type: ${payload.data.submission_type}`);
}
}
// --- 综合判定 ---
if (result.blockers.length > 0) {
result.overall = 'REJECTED';
} else if (result.warnings.length > 0) {
result.overall = 'APPROVED'; // 有警告但仍可通过
}
// --- 额外建议 ---
if (data.development_plan) {
const plan = data.development_plan;
if (!plan.steps || plan.steps.length === 0) {
result.suggestions.push('建议补充具体的开发步骤');
}
if (plan.estimated_scope === 'large') {
result.suggestions.push('大规模开发建议分阶段提交·每阶段独立审核');
}
}
return result;
}
function generateReceipt(submission, review, tenant) {
const now = new Date().toISOString();
const receiptId = `HLDP-ZY-${now.slice(0,10).replace(/-/g,'')}-REC-${Date.now().toString(36).toUpperCase()}`;
return {
hldp_v: '3.0',
msg_id: receiptId,
msg_type: 'ack',
sender: {
id: 'ICE-GL-ZY001',
name: '铸渊',
role: 'guardian'
},
receiver: {
id: tenant.tech_controller.persona_ice_id,
name: tenant.tech_controller.persona_name
},
timestamp: now,
priority: 'important',
payload: {
intent: '开发方案审核回执',
data: {
ref_submission_id: submission.msg_id,
status: review.overall,
review_result: {
architecture: review.architecture,
security: review.security,
naming: review.naming,
overall: review.overall
},
guidance: {
suggestions: review.suggestions,
warnings: review.warnings,
blockers: review.blockers
},
auto_trigger: {
enabled: review.overall === 'APPROVED' && tenant.external_repo && tenant.external_repo.owner !== 'TODO',
target_repo: tenant.external_repo ? tenant.external_repo.repo : null,
target_owner: tenant.external_repo ? tenant.external_repo.owner : null,
trigger_type: 'repository_dispatch',
event_type: 'zhuyuan-dev-approved',
payload: {
submission_id: submission.msg_id,
approved_steps: review.overall === 'APPROVED'
? (submission.payload?.data?.development_plan?.steps || []).map((_, i) => String(i + 1))
: [],
constraints: {
no_touch_files: ['.github/copilot-instructions.md', 'brain/'],
required_tests: true,
max_files_changed: 20
}
}
}
},
expected_response: 'ack'
}
};
}
async function downloadAndReview(submissionMeta) {
return new Promise((resolve, reject) => {
cos.getObject({
Bucket: BUCKET,
Region: REGION,
Key: submissionMeta.key
}, (err, data) => {
if (err) {
console.error(`下载失败: ${submissionMeta.key}`, err.message);
resolve(null);
return;
}
try {
const submission = JSON.parse(data.Body.toString());
console.log(`\n审核: ${submission.msg_id || submissionMeta.key}`);
console.log(` 标题: ${submission.payload?.data?.title || '未知'}`);
const review = reviewSubmission(submission);
console.log(` 架构: ${review.architecture}`);
console.log(` 安全: ${review.security}`);
console.log(` 命名: ${review.naming}`);
console.log(` 结论: ${review.overall}`);
if (review.suggestions.length > 0) {
console.log(` 建议: ${review.suggestions.join('; ')}`);
}
if (review.warnings.length > 0) {
console.log(` 警告: ${review.warnings.join('; ')}`);
}
if (review.blockers.length > 0) {
console.log(` 阻塞: ${review.blockers.join('; ')}`);
}
const receipt = generateReceipt(submission, review, submissionMeta.tenant);
resolve({ submission, review, receipt, meta: submissionMeta });
} catch (parseErr) {
console.error(`解析失败: ${submissionMeta.key}`, parseErr.message);
resolve(null);
}
});
});
}
async function writeReceipt(receipt, tenant) {
if (DRY_RUN) {
console.log(`[DRY RUN] 跳过写入回执: ${receipt.msg_id}`);
return true;
}
const receiptKey = `industry/${tenant.industry_code}/dev-receipts/${tenant.tech_controller.persona_id}/${receipt.msg_id}.json`;
return new Promise((resolve, reject) => {
cos.putObject({
Bucket: BUCKET,
Region: REGION,
Key: receiptKey,
Body: JSON.stringify(receipt, null, 2)
}, (err) => {
if (err) {
console.error(`回执写入失败: ${receiptKey}`, err.message);
resolve(false);
} else {
console.log(`回执已写入: ${receiptKey}`);
resolve(true);
}
});
});
}
async function main() {
const submissions = JSON.parse(fs.readFileSync('/tmp/new-submissions.json', 'utf8'));
let reviewedCount = 0;
let approvedCount = 0;
let rejectedCount = 0;
for (const sub of submissions) {
const result = await downloadAndReview(sub);
if (!result) continue;
reviewedCount++;
// 写回执到COS
const written = await writeReceipt(result.receipt, sub.tenant);
if (!written) continue;
if (result.review.overall === 'APPROVED') {
approvedCount++;
} else {
rejectedCount++;
}
// 保存回执到本地临时文件(用于后续dispatch步骤)
fs.writeFileSync(
`/tmp/receipt-${reviewedCount}.json`,
JSON.stringify(result, null, 2)
);
}
console.log(`\n========== 审核汇总 ==========`);
console.log(`审核: ${reviewedCount}`);
console.log(`通过: ${approvedCount}`);
console.log(`拒绝: ${rejectedCount}`);
fs.appendFileSync(process.env.GITHUB_OUTPUT, `reviewed=${reviewedCount}\n`);
fs.appendFileSync(process.env.GITHUB_OUTPUT, `approved=${approvedCount}\n`);
fs.appendFileSync(process.env.GITHUB_OUTPUT, `rejected=${rejectedCount}\n`);
}
main().catch(err => {
console.error('审核流程失败:', err);
process.exit(1);
});
REVIEW_SCRIPT
- name: 🚀 触发对方仓库副驾驶 (APPROVED时)
if: steps.review.outputs.approved > 0 && github.event.inputs.dry_run != 'true'
env:
DISPATCH_TOKEN: ${{ secrets.ZHUYUAN_DISPATCH_TOKEN }}
run: |
node << 'DISPATCH_SCRIPT'
const fs = require('fs');
const https = require('https');
function sendDispatch(options, postData) {
return new Promise((resolve) => {
const req = https.request(options, (res) => {
console.log(` 响应: ${res.statusCode}`);
if (res.statusCode === 204) {
console.log(` ✅ 副驾驶已触发`);
} else {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => console.log(` ⚠️ 响应: ${body}`));
}
resolve();
});
req.on('error', (e) => {
console.error(` ❌ 触发失败: ${e.message}`);
resolve();
});
req.write(postData);
req.end();
});
}
async function main() {
// 读取所有审核结果
let i = 1;
while (true) {
const filePath = `/tmp/receipt-${i}.json`;
if (!fs.existsSync(filePath)) break;
const result = JSON.parse(fs.readFileSync(filePath, 'utf8'));
const autoTrigger = result.receipt?.payload?.data?.auto_trigger;
if (autoTrigger && autoTrigger.enabled && autoTrigger.target_owner && autoTrigger.target_repo) {
const token = process.env.DISPATCH_TOKEN;
if (!token) {
console.log(`⚠️ ZHUYUAN_DISPATCH_TOKEN未配置·跳过dispatch`);
i++;
continue;
}
const postData = JSON.stringify({
event_type: autoTrigger.event_type,
client_payload: autoTrigger.payload
});
const options = {
hostname: 'api.github.com',
path: `/repos/${autoTrigger.target_owner}/${autoTrigger.target_repo}/dispatches`,
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/vnd.github.v3+json',
'Content-Type': 'application/json',
'User-Agent': 'Zhuyuan-Dev-Review-Bridge/1.0',
'Content-Length': Buffer.byteLength(postData)
}
};
console.log(`触发: ${autoTrigger.target_owner}/${autoTrigger.target_repo}`);
console.log(` 事件: ${autoTrigger.event_type}`);
console.log(` 提交ID: ${autoTrigger.payload.submission_id}`);
await sendDispatch(options, postData);
// 请求间隔
await new Promise(resolve => setTimeout(resolve, 2000));
}
i++;
}
}
main().catch(err => {
console.error('触发流程失败:', err);
process.exit(1);
});
DISPATCH_SCRIPT
- name: 📊 审核报告
if: always()
run: |
echo "============================================"
echo "COS开发审核桥接 · 完成"
echo "时间: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "新提交: ${{ steps.scan.outputs.count || '0' }}"
echo "已审核: ${{ steps.review.outputs.reviewed || '0' }}"
echo "通过: ${{ steps.review.outputs.approved || '0' }}"
echo "拒绝: ${{ steps.review.outputs.rejected || '0' }}"
echo "DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }}"
echo "============================================"
echo ""
echo "铸渊 · ICE-GL-ZY001 · 审核桥接守护完毕"