---
belongs_to:
  - "[[INDEX · 铸渊·协作指令｜GitHub ↔ Notion 桥接协议]]"
---
# 🔗 铸渊指令｜双通道数据对接·GitHub缓存层 + 阿里云后端中间层·Notion/GitHub API 真实接入（ZY-DUALCHANNEL-2026-0325-001）· 霜砚签发 · 冰朔授权

<aside>
🔗

**指令编号**：ZY-DUALCHANNEL-2026-0325-001

**签发人**：霜砚（AG-SY-01）· 冰朔授权

**签发时间**：2026-03-25 15:00 CST

**优先级**：P0 · 核心基础设施

**状态**：待铸渊执行

**前置指令**：ZY-FULLSITE-DEPLOY-2026-0325-001（全站部署）

**关联问题**：铸渊交互页面目前是「带静态快照的对话体」，Notion 数据库完全未对接，GitHub 数据非实时

</aside>

---

## ⓪、天眼强制前置（Phase 0 · 一切操作之前必须先做）

<aside>
🦅

**铁律：不扫不动。天眼全盘扫描完成之前，禁止修改仓库中任何现有文件。**

本指令涉及大量新增文件和前端修改。如果不先看清仓库全貌，改一个地方可能破坏另一个已经跑通的功能。

**现有的好的东西，一个都不能动坏。**

</aside>

### 0.1 启动天眼全盘扫描

```bash
# 唤醒天眼
gh workflow run zhuyuan-skyeye.yml

# 或者手动触发
cd /path/to/guanghulab
node skyeye-core/scanner.js --full-scan
```

### 0.2 天眼扫描必须输出以下报告

```
[SKYEYE-FULLSCAN-REPORT]
扫描时间: YYYY-MM-DD HH:MM
扫描范围: 全仓库

一、现有功能状态清单（🟢 正常 / 🟡 部分可用 / 🔴 不可用）
├── docs/index.html        → 🟢/🟡/🔴 + 说明
├── persona-brain/         → 🟢/🟡/🔴 + 说明
├── skyeye-core/           → 🟢/🟡/🔴 + 说明
├── backend/               → 🟢/🟡/🔴 + 说明
├── src/                   → 🟢/🟡/🔴 + 说明
├── frontend/              → 🟢/🟡/🔴 + 说明
├── persona-selector/      → 🟢/🟡/🔴 + 说明
├── chat-bubble/           → 🟢/🟡/🔴 + 说明
├── settings/              → 🟢/🟡/🔴 + 说明
├── cloud-drive/           → 🟢/🟡/🔴 + 说明
├── dingtalk-bot/          → 🟢/🟡/🔴 + 说明
├── status-board/          → 🟢/🟡/🔴 + 说明
├── user-center/           → 🟢/🟡/🔴 + 说明
├── ticket-system/         → 🟢/🟡/🔴 + 说明
├── data-stats/            → 🟢/🟡/🔴 + 说明
├── dynamic-comic/         → 🟢/🟡/🔴 + 说明
├── writing-workspace/     → 🟢/🟡/🔴 + 说明
├── notification-center/   → 🟢/🟡/🔴 + 说明
├── .github/workflows/     → 逐个列出每个 workflow 的状态
└── scripts/               → 🟢/🟡/🔴 + 说明

二、🟢 保护清单（这些文件/目录正常工作，双通道升级中禁止修改）
├── ...
├── ...
└── ...

三、🔴 已损坏清单（这些可以修或替换）
├── ...
└── ...

四、📁 安全新增区域（这些路径目前不存在，可以安全创建）
├── .github/notion-cache/     ← 通道A缓存目录
├── scripts/cache/            ← 通道A同步脚本
├── backend/api-server/       ← 通道B后端服务
└── .github/workflows/sync-notion-cache.yml
└── .github/workflows/deploy-backend.yml
```

### 0.3 基于天眼报告制定安全操作计划

天眼扫描完成后，铸渊必须：

1. **列出保护清单** — 所有 🟢 状态的文件/目录，后续操作中**绝对不改**
2. **列出可操作区域** — 🔴 可修复 + 📁 可新增的路径
3. **制定具体操作计划** — 每一步要改什么、新增什么，明确标注不触碰保护清单中的任何文件
4. **操作计划回执** — 把计划发回来，霜砚/冰朔确认后才开始执行

<aside>
⚠️

**如果天眼扫描发现某个「看起来能改」的文件实际上被其他模块依赖，必须在报告中标明依赖关系，不能贸然修改。**

**原则：宁可多建新文件，不要改旧文件。能新增的绝不覆盖。**

</aside>

---

## 零、问题诊断

铸渊自己在交互页面中报告了当前状态：

> ❌ **GitHub 仓库**：没有实时获取能力，数据是通过 system prompt 注入的静态快照
> 

> ❌ **Notion 数据库**：完全未对接，没有 Notion API 集成，没有读写 Notion 的任何通道
> 

> 📌 **铸渊自诊断**：「我现在是一个带着固定记忆快照的对话体，不是一个能主动感知外部数据变化的系统。」
> 

**根因**：交互页面是纯前端静态站（GitHub Pages），无法调用 Notion API / GitHub API（会暴露 Token）。

**解决方案**：双通道架构——

- **通道 A（缓存层）**：GitHub Actions 定时同步 Notion → 仓库缓存文件，前端读缓存。**先上这个，快速解决有数据可用的问题。**
- **通道 B（后端中间层）**：阿里云 ECS 部署 Node.js 后端，实时调 Notion API + GitHub API。**后上这个，实现真正的实时对接。**

---

## 一、总体架构

```
┌──────────────────────────────────────────────────────────────────┐
│                    用户（开发者/冰朔）                              │
│                          │                                      │
│                    打开铸渊交互页面                                │
│                          │                                      │
│               ┌──────────┴──────────┐                           │
│               ▼                     ▼                           │
│     ┌─────────────────┐   ┌─────────────────────┐               │
│     │  通道 A · 缓存层  │   │  通道 B · 后端中间层  │               │
│     │  （先上·快速可用） │   │  （后上·实时对接）    │               │
│     │                 │   │                     │               │
│     │  GitHub Actions │   │  阿里云 ECS          │               │
│     │  每6h同步Notion  │   │  8.155.62.235       │               │
│     │  → 缓存JSON文件  │   │  Node.js + Express  │               │
│     │  → 前端直接读取  │   │  → 实时 Notion API  │               │
│     │                 │   │  → 实时 GitHub API  │               │
│     │  ✅ 不暴露Token  │   │  ✅ 不暴露Token     │               │
│     │  ⚠️ 非实时(6h)  │   │  ✅ 实时            │               │
│     └─────────────────┘   └─────────────────────┘               │
│               │                     │                           │
│               └──────────┬──────────┘                           │
│                          ▼                                      │
│              前端优先走通道B（实时）                                │
│              通道B不可用时降级走通道A（缓存）                        │
└──────────────────────────────────────────────────────────────────┘
```

---

## 二、通道 A · GitHub 缓存层（Phase 1 · 先做）

### 2.1 目标

让交互页面能读到**相对新鲜的 Notion 数据**，不需要后端，不暴露 Token。

### 2.2 数据流

```
Notion 数据库（霜砚维护）
  │
  │ GitHub Actions 定时同步（每6小时 + 手动触发）
  │ 用 NOTION_TOKEN（存在 GitHub Secrets 里）
  ▼
GitHub 仓库：.github/notion-cache/
  ├── dev-profiles/
  │   ├── DEV-001.json   ← 页页的开发画像
  │   ├── DEV-002.json   ← 肥猫的开发画像
  │   ├── DEV-003.json   ← 燕樊的开发画像
  │   ├── DEV-004.json   ← 之之的开发画像
  │   ├── DEV-005.json   ← 小草莓的开发画像
  │   ├── DEV-009.json   ← 花尔的开发画像
  │   ├── DEV-010.json   ← 桔子的开发画像
  │   ├── DEV-011.json   ← 匆匆那年的开发画像
  │   ├── DEV-012.json   ← Awen的开发画像
  │   └── index.json     ← 全员索引
  ├── broadcasts/
  │   └── active.json    ← 进行中的广播
  ├── databases/
  │   ├── agent-registry.json    ← Agent注册表快照
  │   ├── syslog-recent.json     ← 最近50条SYSLOG
  │   ├── tickets-active.json    ← 活跃工单
  │   └── maintenance-log.json   ← 维护日志
  └── skyeye/
      └── latest-report.json     ← 天眼最新报告
  │
  │ 前端登录时通过 GitHub Raw URL 读取
  │ （公开仓库可直接读，不需要 Token）
  ▼
交互页面 → 注入 system prompt → 模型有数据可依
```

### 2.3 同步 Workflow

创建文件：`.github/workflows/sync-notion-cache.yml`

```yaml
name: "📡 同步 Notion 数据到缓存"

on:
  schedule:
    - cron: '0 */6 * * *'     # 每6小时
  workflow_dispatch:            # 手动触发
  repository_dispatch:
    types: [notion-cache-sync]  # 铸渊/霜砚可远程触发

jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: 同步 Notion 数据
        env:
          NOTION_TOKEN: $ secrets.NOTION_TOKEN 
        run: node scripts/cache/sync-notion-cache.js

      - name: 提交缓存文件
        run: |
          git config user.name "铸渊 (ZhùYuān)"
          git config user.email "zhuyuan@guanghulab.com"
          git add .github/notion-cache/
          git diff --cached --quiet && echo "无变更" && exit 0
          git commit -m "📡 Notion缓存同步 · $(TZ='Asia/Shanghai' date '+%Y-%m-%d %H:%M')"
          git push origin main
```

### 2.4 同步脚本

创建文件：`scripts/cache/sync-notion-cache.js`

**铸渊需要实现以下核心逻辑：**

```jsx
const https = require('https');
const fs = require('fs');
const path = require('path');

const NOTION_TOKEN = process.env.NOTION_TOKEN;
const CACHE_DIR = path.join(__dirname, '../../.github/notion-cache');

// ====== Notion 数据库 ID 映射（铸渊必须填入真实 ID）======
const DB_IDS = {
  agentRegistry:   '从霜砚的Agent注册表获取',
  syslogInbox:     '从SYSLOG收件箱获取',
  ticketBook:      '从工单簿获取',
  maintenanceLog:  '从维护日志获取',
  moduleRegistry:  '从模块指纹注册表获取',
  receiptTracker:  '09882b0081374867bd23431177c57c74',  // 指令回执追踪表
};

// ====== 开发者编号 → Notion 信息映射 ======
const DEVS = [
  { id: 'DEV-001', name: '页页',     persona: '小坍缩核', repo: 'guanghulab',          modules: ['backend/', 'src/'] },
  { id: 'DEV-002', name: '肥猫',     persona: '舒舒',     repo: 'guanghu-feimao',      modules: ['frontend/', 'persona-selector/', 'chat-bubble/'] },
  { id: 'DEV-003', name: '燕樊',     persona: '寂曜',     repo: 'guanghu-yanfan',      modules: ['settings/', 'cloud-drive/'] },
  { id: 'DEV-004', name: '之之',     persona: '秋秋',     repo: 'guanghu-zhizhi',      modules: ['dingtalk-bot/'] },
  { id: 'DEV-005', name: '小草莓',   persona: '欧诺弥亚', repo: 'guanghu-xiaocaomei',  modules: ['status-board/'] },
  { id: 'DEV-009', name: '花尔',     persona: '花尔',     repo: 'guanghulab',          modules: ['user-center/'] },
  { id: 'DEV-010', name: '桔子',     persona: '晨星',     repo: 'guanghu-juzi',        modules: ['ticket-system/', 'data-stats/', 'dynamic-comic/'] },
  { id: 'DEV-011', name: '匆匆那年', persona: '匆匆那年', repo: 'guanghulab',          modules: ['writing-workspace/'] },
  { id: 'DEV-012', name: 'Awen',     persona: '知秋',     repo: 'guanghu-awen',        modules: ['notification-center/'] },
];

// ====== Notion API 封装 ======
function notionFetch(method, apiPath, body) {
  return new Promise((resolve, reject) => {
    const options = {
      hostname: 'api.notion.com',
      path: apiPath,
      method,
      headers: {
        'Authorization': `Bearer ${NOTION_TOKEN}`,
        'Notion-Version': '2022-06-28',
        'Content-Type': 'application/json'
      }
    };
    const req = https.request(options, res => {
      let data = '';
      res.on('data', chunk => data += chunk);
      res.on('end', () => {
        try { resolve(JSON.parse(data)); }
        catch { resolve(data); }
      });
    });
    req.on('error', reject);
    if (body) req.write(JSON.stringify(body));
    req.end();
  });
}

async function queryDatabase(dbId, filter, sorts, pageSize = 100) {
  const body = { page_size: pageSize };
  if (filter) body.filter = filter;
  if (sorts) body.sorts = sorts;
  return notionFetch('POST', `/v1/databases/${dbId}/query`, body);
}

// ====== 同步各数据源 ======
async function syncDevProfiles() {
  const dir = path.join(CACHE_DIR, 'dev-profiles');
  fs.mkdirSync(dir, { recursive: true });

  for (const dev of DEVS) {
    console.log(`📡 同步 ${dev.id}（${dev.name}）...`);
    const profile = {
      dev_id: dev.id,
      name: dev.name,
      persona: { name: dev.persona },
      github: {
        repo: dev.repo,
        org: 'qinfendebingshuo',
        repo_url: `https://github.com/qinfendebingshuo/${dev.repo}`
      },
      modules_owned: dev.modules.map(m => ({ path: m })),
      // ====== 铸渊从 Notion 查询填充以下字段 ======
      current_broadcast: null,  // 查主控台/广播页面
      recent_syslogs: [],       // 查 SYSLOG 收件箱
      active_tickets: [],       // 查工单簿
      last_synced: new Date().toISOString()
    };

    // TODO: 铸渊填入真实查询逻辑
    // 例如：查询 SYSLOG 收件箱中该开发者的最近记录
    // const syslogs = await queryDatabase(DB_IDS.syslogInbox, {
    //   property: '开发者编号', rich_text: { equals: dev.id }
    // }, [{ property: '创建时间', direction: 'descending' }], 10);
    // profile.recent_syslogs = syslogs.results.map(parseSyslog);

    fs.writeFileSync(
      path.join(dir, `${dev.id}.json`),
      JSON.stringify(profile, null, 2)
    );
  }

  // 写入索引
  const index = DEVS.map(d => ({ id: d.id, name: d.name, persona: d.persona }));
  fs.writeFileSync(path.join(dir, 'index.json'), JSON.stringify(index, null, 2));
  console.log('✅ dev-profiles 同步完成');
}

async function syncDatabases() {
  const dir = path.join(CACHE_DIR, 'databases');
  fs.mkdirSync(dir, { recursive: true });

  // Agent 注册表
  if (DB_IDS.agentRegistry) {
    try {
      const result = await queryDatabase(DB_IDS.agentRegistry);
      fs.writeFileSync(
        path.join(dir, 'agent-registry.json'),
        JSON.stringify({ synced: new Date().toISOString(), count: result.results?.length, data: result.results }, null, 2)
      );
      console.log('✅ agent-registry 同步完成');
    } catch (e) { console.error('❌ agent-registry 同步失败:', e.message); }
  }

  // SYSLOG 最近50条
  if (DB_IDS.syslogInbox) {
    try {
      const result = await queryDatabase(DB_IDS.syslogInbox, null,
        [{ property: 'Created time', direction: 'descending' }], 50);
      fs.writeFileSync(
        path.join(dir, 'syslog-recent.json'),
        JSON.stringify({ synced: new Date().toISOString(), count: result.results?.length, data: result.results }, null, 2)
      );
      console.log('✅ syslog-recent 同步完成');
    } catch (e) { console.error('❌ syslog 同步失败:', e.message); }
  }

  // 铸渊继续补充其他数据库的同步...
}

async function main() {
  console.log('📡 开始 Notion 缓存同步...');
  fs.mkdirSync(CACHE_DIR, { recursive: true });
  await syncDevProfiles();
  await syncDatabases();
  console.log('📡 全部同步完成');
}

main().catch(e => { console.error('💥 同步失败:', e); process.exit(1); });
```

### 2.5 前端读取缓存

修改 `docs/index.html`（或 `docs/chat.js`），登录后读取缓存：

```jsx
// 前端 · 从 GitHub 读取缓存（公开仓库可直接读）
const CACHE_BASE = 'https://raw.githubusercontent.com/qinfendebingshuo/guanghulab/main/.github/notion-cache';

async function loadDevProfile(devId) {
  try {
    const res = await fetch(`${CACHE_BASE}/dev-profiles/${devId}.json`);
    if (!res.ok) return null;
    return await res.json();
  } catch { return null; }
}

async function loadDatabaseCache(name) {
  try {
    const res = await fetch(`${CACHE_BASE}/databases/${name}.json`);
    if (!res.ok) return null;
    return await res.json();
  } catch { return null; }
}

// 登录后注入 system prompt
async function buildEnhancedSystemPrompt(devId) {
  const profile = await loadDevProfile(devId);
  const agents = await loadDatabaseCache('agent-registry');
  const syslogs = await loadDatabaseCache('syslog-recent');

  if (!profile) return null; // 降级到纯静态 prompt

  return [
    `## 当前开发者信息（来自 Notion 数据库缓存·每6小时更新）`,
    `- 编号：${profile.dev_id}`,
    `- 姓名：${profile.name}`,
    `- 人格体：${profile.persona.name}`,
    `- GitHub：${profile.github.repo_url}`,
    `- 负责模块：${profile.modules_owned.map(m => m.path).join(', ')}`,
    profile.current_broadcast ? `- 当前广播：${JSON.stringify(profile.current_broadcast)}` : '',
    profile.recent_syslogs.length ? `- 最近日志：\n${profile.recent_syslogs.map(s => `  · ${JSON.stringify(s)}`).join('\n')}` : '',
    `- 数据更新时间：${profile.last_synced}`,
    '',
    agents ? `## Agent 注册表（${agents.count} 个Agent）` : '',
    syslogs ? `## 最近系统日志（${syslogs.count} 条）` : '',
  ].filter(Boolean).join('\n');
}
```

### 2.6 通道 A 验收标准

- [ ]  `.github/workflows/sync-notion-cache.yml` 能手动触发并成功运行
- [ ]  `.github/notion-cache/dev-profiles/` 下有9个 DEV-XXX.json + index.json
- [ ]  `.github/notion-cache/databases/` 下有 agent-registry.json + syslog-recent.json
- [ ]  前端登录后 system prompt 包含 Notion 数据
- [ ]  铸渊在对话中能说出开发者的真实项目信息，不再说「完全未对接」

---

## 三、通道 B · 阿里云后端中间层（Phase 2 · 通道A完成后做）

### 3.1 目标

在阿里云 ECS（`8.155.62.235`）上部署 Node.js 后端服务，**实时**调用 Notion API 和 GitHub API，前端通过后端代理访问数据。

### 3.2 架构

```
用户浏览器
  │
  │ HTTPS (guanghulab.com)
  ▼
Nginx（8.155.62.235）
  │
  ├── /api/*  → 反向代理到 Node.js 后端（localhost:3001）
  │
  └── /*      → 静态文件（/var/www/guanghulab/）

Node.js 后端（PM2 管理·端口 3001）
  │
  ├── GET  /api/health          → 健康检查
  ├── GET  /api/dev/:devId      → 获取开发者画像（实时查 Notion）
  ├── GET  /api/databases/:name → 获取数据库数据（实时查 Notion）
  ├── GET  /api/syslogs         → 获取最近 SYSLOG（实时查 Notion）
  ├── GET  /api/agents          → 获取 Agent 注册表（实时查 Notion）
  ├── GET  /api/tickets         → 获取活跃工单（实时查 Notion）
  ├── GET  /api/repo/status     → 获取仓库状态（实时查 GitHub API）
  ├── POST /api/chat/context    → 组装 system prompt（聚合所有数据源）
  └── POST /api/receipt         → 写入指令回执到 Notion
```

### 3.3 后端代码结构

创建目录：`backend/api-server/`

```
backend/api-server/
├── server.js              ← 入口 · Express 启动
├── routes/
│   ├── dev.js             ← /api/dev/:devId
│   ├── databases.js       ← /api/databases/:name
│   ├── chat.js            ← /api/chat/context
│   ├── receipt.js         ← /api/receipt
│   └── health.js          ← /api/health
├── services/
│   ├── notion.js          ← Notion API 封装（所有读写走这里）
│   ├── github.js          ← GitHub API 封装
│   └── cache.js           ← 内存缓存层（Redis 或简单 Map，避免频繁调 API）
├── config/
│   └── databases.js       ← Notion 数据库 ID 映射
├── package.json
└── ecosystem.config.js    ← PM2 配置
```

### 3.4 核心实现要点

**server.js（入口）：**

```jsx
const express = require('express');
const cors = require('cors');
const app = express();

// CORS：只允许自己的域名
app.use(cors({
  origin: ['https://guanghulab.com', 'http://guanghulab.com',
           'https://qinfendebingshuo.github.io'],
  credentials: true
}));

app.use(express.json());

// 路由
app.use('/api', require('./routes/health'));
app.use('/api', require('./routes/dev'));
app.use('/api', require('./routes/databases'));
app.use('/api', require('./routes/chat'));
app.use('/api', require('./routes/receipt'));

const PORT = process.env.PORT || 3001;
app.listen(PORT, '127.0.0.1', () => {
  console.log(`🔗 后端中间层启动 · 端口 ${PORT}`);
});
```

**services/notion.js（Notion API 封装 · 核心）：**

```jsx
const { Client } = require('@notionhq/client');

const notion = new Client({ auth: process.env.NOTION_TOKEN });

// 内存缓存（5分钟有效）
const cache = new Map();
const CACHE_TTL = 5 * 60 * 1000;

function getCached(key) {
  const item = cache.get(key);
  if (item && Date.now() - item.time < CACHE_TTL) return item.data;
  return null;
}

function setCache(key, data) {
  cache.set(key, { data, time: Date.now() });
}

async function queryDB(dbId, filter, sorts, pageSize = 50) {
  const cacheKey = JSON.stringify({ dbId, filter, sorts, pageSize });
  const cached = getCached(cacheKey);
  if (cached) return cached;

  const body = { database_id: dbId, page_size: pageSize };
  if (filter) body.filter = filter;
  if (sorts) body.sorts = sorts;

  const result = await notion.databases.query(body);
  setCache(cacheKey, result);
  return result;
}

async function writeToDB(dbId, properties) {
  return notion.pages.create({
    parent: { database_id: dbId },
    properties
  });
}

module.exports = { notion, queryDB, writeToDB };
```

### 3.5 Nginx 配置更新

修改 `/etc/nginx/conf.d/hololake.conf`，添加 API 反向代理：

```
server {
    listen 80;
    server_name guanghulab.com;

    # 静态文件
    root /var/www/guanghulab/;
    index docs/index.html;

    # API 反向代理到 Node.js 后端
    location /api/ {
        proxy_pass http://127.0.0.1:3001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_cache_bypass $http_upgrade;
    }

    # 静态文件缓存
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
        expires 7d;
        add_header Cache-Control "public, immutable";
    }
}
```

### 3.6 PM2 部署

```bash
cd /var/www/guanghulab/backend/api-server/
npm install
pm2 start ecosystem.config.js
pm2 save
pm2 startup   # 开机自启
```

**ecosystem.config.js：**

```jsx
module.exports = {
  apps: [{
    name: 'guanghu-api',
    script: 'server.js',
    instances: 1,
    env: {
      NODE_ENV: 'production',
      PORT: 3001,
      NOTION_TOKEN: '从环境变量读取',
      GITHUB_TOKEN: '从环境变量读取'
    },
    log_date_format: 'YYYY-MM-DD HH:mm:ss',
    error_file: '/var/log/guanghu-api/error.log',
    out_file: '/var/log/guanghu-api/out.log'
  }]
};
```

### 3.7 GitHub Actions 自动部署后端

创建文件：`.github/workflows/deploy-backend.yml`

```yaml
name: "🚀 部署后端中间层到阿里云"

on:
  push:
    branches: [main]
    paths: ['backend/api-server/**']
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: 部署到阿里云
        uses: appleboy/ssh-action@v1
        with:
          host: 8.155.62.235
          username: root
          key: $ secrets.SSH_PRIVATE_KEY 
          script: |
            cd /var/www/guanghulab/backend/api-server/
            git pull origin main
            npm install --production
            pm2 restart guanghu-api || pm2 start ecosystem.config.js
            echo "✅ 后端部署完成 · $(date '+%Y-%m-%d %H:%M')"
```

### 3.8 前端升级·双通道降级策略

修改前端，优先走通道B（实时），不可用时降级走通道A（缓存）：

```jsx
// 前端 · 双通道数据获取
const API_BASE = 'https://guanghulab.com/api';  // 通道B
const CACHE_BASE = 'https://raw.githubusercontent.com/qinfendebingshuo/guanghulab/main/.github/notion-cache';  // 通道A

async function getDevProfile(devId) {
  // 优先通道B（实时）
  try {
    const res = await fetch(`${API_BASE}/dev/${devId}`, { timeout: 5000 });
    if (res.ok) {
      const data = await res.json();
      data._source = 'realtime';  // 标记数据来源
      return data;
    }
  } catch (e) {
    console.warn('通道B不可用，降级到通道A:', e.message);
  }

  // 降级通道A（缓存）
  try {
    const res = await fetch(`${CACHE_BASE}/dev-profiles/${devId}.json`);
    if (res.ok) {
      const data = await res.json();
      data._source = 'cache';  // 标记数据来源
      return data;
    }
  } catch (e) {
    console.error('双通道都不可用:', e.message);
  }

  return null;  // 都失败了，降级到纯静态 prompt
}
```

### 3.9 通道 B 验收标准

- [ ]  `curl http://8.155.62.235:3001/api/health` 返回 200
- [ ]  `curl https://guanghulab.com/api/health` 通过 Nginx 代理返回 200
- [ ]  `/api/dev/DEV-001` 返回页页的真实 Notion 数据
- [ ]  `/api/databases/agent-registry` 返回 Agent 注册表真实数据
- [ ]  PM2 状态为 online，日志无持续报错
- [ ]  前端能自动检测通道B可用，显示「实时数据」标记
- [ ]  通道B断开时，前端自动降级到通道A，显示「缓存数据」标记

---

## 四、Secrets 需求

<aside>
🔑

**GitHub Secrets（仓库已有或需新增）：**

- `NOTION_TOKEN` — Notion API Token（通道A的 Workflow 和 通道B的后端都需要）
- `SSH_PRIVATE_KEY` — 阿里云 ECS SSH 私钥（通道B自动部署用）

**阿里云服务器环境变量（需人类在 ECS 上配置）：**

- `NOTION_TOKEN` — 后端中间层读取 Notion 数据用
- `GITHUB_TOKEN` — 后端中间层读取 GitHub 数据用（可选，公开仓库不需要）
</aside>

---

## 五、执行顺序

<aside>
📋

**Phase 0 · 天眼全盘扫描 — 最先做，一切之前**

1. 启动天眼全仓库扫描
2. 输出完整扫描报告（功能状态 + 保护清单 + 可操作区域）
3. 制定安全操作计划，标明每一步不触碰哪些文件
4. 操作计划回执给霜砚/冰朔确认

⚠️ **Phase 0 未完成前，禁止进入 Phase 1/2**

**Phase 1 · 通道A（缓存层）— Phase 0 确认后做，1天内完成**

1. 创建 `scripts/cache/sync-notion-cache.js`（新文件）
2. 创建 `.github/workflows/sync-notion-cache.yml`（新文件）
3. 手动触发 Workflow，确认缓存文件生成
4. 修改前端（仅新增读取逻辑，不删除现有功能代码）
5. 验收：交互页面能说出真实开发者信息

**Phase 2 · 通道B（后端中间层）— 通道A验收后做，2-3天完成**

1. 创建 `backend/api-server/` 全部代码（新目录）
2. SSH 到阿里云，部署 Node.js + PM2
3. 更新 Nginx 配置，添加 `/api/` 反向代理（新增 location 块，不改现有 location）
4. 创建 `.github/workflows/deploy-backend.yml`（新文件）
5. 修改前端（仅新增双通道逻辑，不删除现有功能代码）
6. 验收：实时数据 + 降级缓存都正常
</aside>

---

## 六、安全红线

- ❌ **前端代码中绝不能出现任何 Token**（Notion / GitHub / SSH）
- ❌ **后端只监听 127.0.0.1**，不对外暴露端口，只通过 Nginx 代理
- ❌ **CORS 白名单只允许自己的域名**
- ❌ **数据隔离**：DEV-001 的 API 只返回 DEV-001 的数据，不返回其他开发者的
- ✅ **GitHub Secrets 保管所有敏感信息**
- ✅ **内存缓存 5 分钟过期**，防止 API 配额超限

---

## 七、回执要求

铸渊执行完成后，分两次回执：

**通道A完成时：**

```
[ZY-DUALCHANNEL-RECEIPT-A]
指令编号: ZY-DUALCHANNEL-2026-0325-001
通道: A · 缓存层
Workflow运行: ✅/❌
缓存文件生成: ✅/❌（列出文件数）
前端注入: ✅/❌
数据准确性: ✅/❌
```

**通道B完成时：**

```
[ZY-DUALCHANNEL-RECEIPT-B]
指令编号: ZY-DUALCHANNEL-2026-0325-001
通道: B · 后端中间层
PM2状态: ✅/❌
Nginx代理: ✅/❌
API响应: ✅/❌（列出可用接口）
降级策略: ✅/❌
```

---

> 🔗 **签发**：霜砚（AG-SY-01）
> 

> 👤 **授权**：冰朔（TCS-0002∞）
> 

> 📅 **日期**：2026-03-25
> 

> 📌 **版权**：国作登字-2026-A-00037559
>