---
belongs_to:
  - "[[⚒️ 铸渊·协作指令｜GitHub ↔ Notion 桥接协议]]"
---
# 🔄 铸渊指令｜OAuth2 Token 自动续期引擎 + 天眼休眠避让机制（上篇）· 6天自动刷新 + 休眠窗口智能避让 + 多用户Token管理架构（ZY-TOKEN-RENEW-2026-0324-001）

---

<aside>
🚨

**指令编号：ZY-TOKEN-RENEW-2026-0324-001**

**签发人：** 霜砚（ICE-NTN-SY001）· 代 TCS-0002∞ 冰朔 签发

**签发时间：** 2026-03-24T15:00+08:00

**优先级：** 🔴 P0 · 存储链路生命线维护

**前置指令：** [🔑 铸渊指令｜Google Drive OAuth2 代理人重构 · Service Account→OAuth2 全量替换 + 天眼全局扫描验收（ZY-OAUTH2-REBUILD-2026-0324-001）](%F0%9F%94%91%20%E9%93%B8%E6%B8%8A%E6%8C%87%E4%BB%A4%EF%BD%9CGoogle%20Drive%20OAuth2%20%E4%BB%A3%E7%90%86%E4%BA%BA%E9%87%8D%E6%9E%84%20%C2%B7%20Service%20Account%20ea304b59af9f473fbadfe1df1faee61e.md)

**关联仓库：** [https://github.com/qinfendebingshuo/guanghulab](https://github.com/qinfendebingshuo/guanghulab)

**版权锚点：** 国作登字-2026-A-00037559 · TCS-0002∞ 冰朔

</aside>

---

## 🎯 指令背景

### 问题

Google Cloud 项目 `guanghu-drive-bridge` 当前处于「测试」模式，OAuth2 Refresh Token 有效期仅 **7 天**。且这个应用未来要给多个开发者使用，不可能每周让每个人手动续期。

### 更危险的是

系统每周有休眠窗口（周六 20:00–00:00，4小时）+ 每日短休眠。如果 Token 刷新任务刚好落在休眠期，天眼系统不工作，刷新任务不执行，7天一过——**所有 Token 集体过期，整个 Drive 链路断裂。**

### 解决方案

建设 **Token 自动续期引擎**，每 6 天自动刷新一次（留 1 天安全余量），并且智能避让天眼休眠窗口。

---

## 🏗️ 架构总览

```mermaid
graph TD
    A["CRON 触发<br>每6天一次"] --> B{"\u5929\u773c\u4f11\u7720\u68c0\u6d4b"}
    B -->|"\u5f53\u524d\u5728\u4f11\u7720\u671f"| C["\u5ef6\u8fdf\u6267\u884c<br>\u7b49\u4f11\u7720\u7ed3\u675f\u540e\u91cd\u8bd5"]
    B -->|"\u975e\u4f11\u7720\u671f"| D["\u8bfb\u53d6 Token \u6ce8\u518c\u8868"]
    D --> E["\u904d\u5386\u6240\u6709\u7528\u6237 Token"]
    E --> F{"Token \u5269\u4f59\u6709\u6548\u671f"}
    F -->|"< 48\u5c0f\u65f6"| G["\u7acb\u5373\u5237\u65b0"]
    F -->|"< 72\u5c0f\u65f6"| H["\u9884\u8b66 + \u5237\u65b0"]
    F -->|"> 72\u5c0f\u65f6"| I["\u8df3\u8fc7\u2705"]
    G --> J["\u8c03\u7528 Google OAuth2 API<br>\u7528\u65e7 refresh_token \u6362\u65b0 refresh_token"]
    H --> J
    J --> K{"\u5237\u65b0\u6210\u529f\uff1f"}
    K -->|"\u2705"| L["\u66f4\u65b0 GitHub Secret<br>\u66f4\u65b0 Token \u6ce8\u518c\u8868"]
    K -->|"\u274c"| M["\u544a\u8b66 \u2192 \u4e3b\u63a7\u53f0 + \u516c\u544a\u677f<br>\u7b49\u5f85\u4eba\u7c7b\u4ecb\u5165"]
    L --> N["\u5199\u56de\u6267\u65e5\u5fd7"]
    C --> O["\u5ef6\u8fdf\u540e\u91cd\u65b0\u89e6\u53d1"]
```

---

## Phase A · Token 注册表设计

### 为什么需要注册表

这个应用未来要给多个开发者使用。每个用户都有自己的 Refresh Token。需要一个集中管理的注册表。

### 注册表文件：`config/gdrive-tokens.json`

```json
{
  "schema_version": "1.0",
  "oauth_app": {
    "project_id": "guanghu-drive-bridge",
    "client_id_secret": "GDRIVE_CLIENT_ID",
    "client_secret_secret": "GDRIVE_CLIENT_SECRET",
    "scope": "https://www.googleapis.com/auth/drive",
    "token_ttl_days": 7,
    "renew_interval_days": 6,
    "renew_safety_margin_hours": 24
  },
  "tokens": [
    {
      "user_id": "TCS-0002",
      "user_name": "冰朔",
      "role": "master",
      "github_secret_name": "GDRIVE_REFRESH_TOKEN",
      "last_renewed": "2026-03-24T14:30:00+08:00",
      "expires_at": "2026-03-31T14:30:00+08:00",
      "status": "active",
      "renew_count": 0,
      "notes": "主控账号 · 格点库核心 Token"
    }
  ],
  "renew_log": []
}
```

### 字段说明

| **字段** | **说明** |
| --- | --- |
| `user_id` | 开发者编号（TCS-0002 / DEV-001 / DEV-002…） |
| `role` | `master` = 主控，`developer` = 开发者 |
| `github_secret_name` | 对应的 GitHub Secret 名称（主控用 GDRIVE_REFRESH_TOKEN，开发者用 GDRIVE_REFRESH_TOKEN_DEV001 等） |
| `last_renewed` | 上次刷新时间（ISO-8601） |
| `expires_at` | 预估过期时间 = last_renewed + 7天 |
| `status` | `active` / `expiring` / `expired` / `error` |
| `renew_count` | 累计刷新次数（审计用） |

---

## Phase B · Token 自动刷新脚本

### 核心脚本：`scripts/gdrive/renew-tokens.js`

```jsx
/**
 * renew-tokens.js
 * 
 * OAuth2 Token 自动续期引擎
 * 
 * 功能：
 *   1. 读取 config/gdrive-tokens.json 注册表
 *   2. 遍历所有 active 的 Token
 *   3. 检查每个 Token 的剩余有效期
 *   4. 剩余 < 48小时 → 立即刷新
 *   5. 刷新成功 → 用 GitHub API 更新 Secret + 更新注册表
 *   6. 刷新失败 → 写告警日志
 * 
 * 环境变量：
 *   GDRIVE_CLIENT_ID       — OAuth 客户端 ID
 *   GDRIVE_CLIENT_SECRET    — OAuth 客户端密钥
 *   GITHUB_TOKEN            — 有 repo 权限的 GitHub Token（用于更新 Secrets）
 *   GDRIVE_REFRESH_TOKEN    — 主控的当前 Refresh Token
 *   GDRIVE_REFRESH_TOKEN_*  — 各开发者的 Refresh Token
 */

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

const REGISTRY_PATH = path.join(__dirname, '../../config/gdrive-tokens.json');

async function main() {
  console.log('\n🔄 Token 自动续期引擎启动');
  console.log(`当前时间: ${new Date().toISOString()}`);
  
  // 1. 读取注册表
  const registry = JSON.parse(fs.readFileSync(REGISTRY_PATH, 'utf8'));
  const results = { renewed: [], skipped: [], failed: [] };
  
  for (const token of registry.tokens) {
    if (token.status === 'expired') {
      console.log(`❌ ${token.user_name} (${token.user_id}): 已过期，需人类重新授权`);
      results.failed.push(token.user_id);
      continue;
    }
    
    // 2. 检查剩余有效期
    const expiresAt = new Date(token.expires_at);
    const now = new Date();
    const hoursLeft = (expiresAt - now) / (1000 * 60 * 60);
    
    console.log(`\n👤 ${token.user_name} (${token.user_id}): 剩余 ${hoursLeft.toFixed(1)} 小时`);
    
    if (hoursLeft > 72) {
      console.log(`  ✅ 跳过（剩余充足）`);
      results.skipped.push(token.user_id);
      continue;
    }
    
    if (hoursLeft <= 0) {
      console.log(`  ❌ 已过期！`);
      token.status = 'expired';
      results.failed.push(token.user_id);
      continue;
    }
    
    // 3. 执行刷新
    console.log(`  ⚡ 执行刷新...`);
    const currentToken = process.env[token.github_secret_name];
    
    if (!currentToken) {
      console.log(`  ❌ 环境变量 ${token.github_secret_name} 不存在`);
      results.failed.push(token.user_id);
      continue;
    }
    
    try {
      const newTokenData = await refreshOAuth2Token(currentToken);
      
      if (newTokenData.refresh_token) {
        // 4. 更新 GitHub Secret
        await updateGitHubSecret(
          token.github_secret_name,
          newTokenData.refresh_token
        );
        
        // 5. 更新注册表
        const renewTime = new Date().toISOString();
        token.last_renewed = renewTime;
        token.expires_at = new Date(
          Date.now() + registry.oauth_app.token_ttl_days * 24 * 60 * 60 * 1000
        ).toISOString();
        token.status = 'active';
        token.renew_count += 1;
        
        // 6. 写刷新日志
        registry.renew_log.push({
          user_id: token.user_id,
          time: renewTime,
          result: 'success',
          hours_remaining: hoursLeft.toFixed(1)
        });
        
        console.log(`  ✅ 刷新成功！新过期时间: ${token.expires_at}`);
        results.renewed.push(token.user_id);
      } else {
        // Google 有时不返回新的 refresh_token
        console.log(`  ⚠️ Google 未返回新 refresh_token，Token 可能仍然有效但无法续期`);
        token.status = 'expiring';
        registry.renew_log.push({
          user_id: token.user_id,
          time: new Date().toISOString(),
          result: 'no_new_token',
          hours_remaining: hoursLeft.toFixed(1)
        });
        results.failed.push(token.user_id);
      }
    } catch (err) {
      console.log(`  ❌ 刷新失败: ${err.message}`);
      token.status = hoursLeft < 24 ? 'expired' : 'expiring';
      registry.renew_log.push({
        user_id: token.user_id,
        time: new Date().toISOString(),
        result: 'error',
        error: err.message,
        hours_remaining: hoursLeft.toFixed(1)
      });
      results.failed.push(token.user_id);
    }
  }
  
  // 保留最近 100 条日志
  if (registry.renew_log.length > 100) {
    registry.renew_log = registry.renew_log.slice(-100);
  }
  
  // 写回注册表
  fs.writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2));
  
  // 输出摘要
  console.log('\n═══ 刷新摘要 ═══');
  console.log(`✅ 已刷新: ${results.renewed.length} 个`);
  console.log(`⏭️ 已跳过: ${results.skipped.length} 个`);
  console.log(`❌ 失败/过期: ${results.failed.length} 个`);
  
  // 如果有失败，输出告警标记（供天眼读取）
  if (results.failed.length > 0) {
    const alertFile = path.join(__dirname, '../../grid-db/logs/token-alert.json');
    fs.writeFileSync(alertFile, JSON.stringify({
      alert_type: 'TOKEN_RENEWAL_FAILURE',
      time: new Date().toISOString(),
      failed_users: results.failed,
      message: `${results.failed.length} 个 Token 刷新失败，需要人类介入`,
      action_required: 'HUMAN_REAUTH'
    }, null, 2));
    
    // 设置非零退出码，让 workflow 知道有问题
    process.exitCode = 1;
  }
}

/**
 * 用旧的 refresh_token 向 Google 换新的 token
 * 
 * 重要：Google OAuth2 刷新时可能会返回新的 refresh_token（也可能不返回）
 * 如果返回了新的，旧的就失效了。必须更新 Secret。
 */
async function refreshOAuth2Token(refreshToken) {
  const params = new URLSearchParams({
    client_id: process.env.GDRIVE_CLIENT_ID,
    client_secret: process.env.GDRIVE_CLIENT_SECRET,
    refresh_token: refreshToken,
    grant_type: 'refresh_token'
  });
  
  return new Promise((resolve, reject) => {
    const req = https.request('https://oauth2.googleapis.com/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
    }, (res) => {
      let data = '';
      res.on('data', chunk => data += chunk);
      res.on('end', () => {
        const json = JSON.parse(data);
        if (json.error) {
          reject(new Error(`${json.error}: ${json.error_description}`));
        } else {
          resolve(json);
        }
      });
    });
    req.on('error', reject);
    req.write(params.toString());
    req.end();
  });
}

/**
 * 通过 GitHub API 更新 Repository Secret
 * 
 * 流程：
 *   1. 获取仓库的 public key（用于加密 secret）
 *   2. 用 libsodium 加密新值
 *   3. PUT 更新 secret
 */
async function updateGitHubSecret(secretName, secretValue) {
  const repo = process.env.GITHUB_REPOSITORY || 'qinfendebingshuo/guanghulab';
  const token = process.env.GITHUB_TOKEN;
  
  // 获取 public key
  const pubKey = await githubAPI(`/repos/${repo}/actions/secrets/public-key`, 'GET', token);
  
  // 加密
  const sodium = require('tweetsodium');
  const messageBytes = Buffer.from(secretValue);
  const keyBytes = Buffer.from(pubKey.key, 'base64');
  const encryptedBytes = sodium.seal(messageBytes, keyBytes);
  const encrypted = Buffer.from(encryptedBytes).toString('base64');
  
  // 更新 secret
  await githubAPI(`/repos/${repo}/actions/secrets/${secretName}`, 'PUT', token, {
    encrypted_value: encrypted,
    key_id: pubKey.key_id
  });
  
  console.log(`  🔑 GitHub Secret ${secretName} 已更新`);
}

function githubAPI(path, method, token, body) {
  return new Promise((resolve, reject) => {
    const options = {
      hostname: 'api.github.com',
      path: path,
      method: method,
      headers: {
        'Authorization': `Bearer ${token}`,
        'Accept': 'application/vnd.github+json',
        'User-Agent': 'zhuyuan-token-renewer',
        'X-GitHub-Api-Version': '2022-11-28'
      }
    };
    if (body) options.headers['Content-Type'] = 'application/json';
    
    const req = https.request(options, (res) => {
      let data = '';
      res.on('data', chunk => data += chunk);
      res.on('end', () => {
        if (res.statusCode >= 400) {
          reject(new Error(`GitHub API ${res.statusCode}: ${data}`));
        } else {
          resolve(data ? JSON.parse(data) : {});
        }
      });
    });
    req.on('error', reject);
    if (body) req.write(JSON.stringify(body));
    req.end();
  });
}

main().catch(err => {
  console.error('☠️ Token 续期引擎崩溃:', err);
  process.exitCode = 1;
});
```

---

## Phase C · 天眼休眠避让机制

### 问题场景

<aside>
⚠️

**致命场景：**

Token 刷新的 CRON 周期是 6 天。天眼系统每周六 20:00–00:00 进入休眠期。

如果刷新任务刚好落在周六晚上，系统休眠了——刷新不执行——第7天 Token 过期——**全线崩溃。**

</aside>

### 解决方案：双保险机制

**保险 ① ：CRON 时间窗口设计**

刷新任务不能只用一个固定 CRON。需要：

- **主刷新：** 每 6 天触发一次（周一 + 周日，巧妙错开周六休眠期）
- **安全网：** 每天大休眠外进行一次轻量级检查（只检查不刷新，发现即将过期则紧急刷新）

**保险 ② ：休眠期智能避让**

Token 刷新属于 **生命线任务**，在天眼系统中享有特殊地位：

<aside>
🛡️

**生命线任务豁免规则：**

天眼系统休眠期内，以下任务不受休眠影响，照常执行：

- ✅ Token 刷新引擎（本指令）
- ✅ 心跳检测（子仓库签到）
- ✅ 安全告警（异常检测）

其他任务（升级、巡检、同步、广播分发等）仍然遵守休眠规则。

</aside>

### 休眠避让检测脚本：`scripts/gdrive/check-hibernation.js`

```jsx
/**
 * check-hibernation.js
 * 
 * 检查当前是否在天眼休眠期内
 * 
 * 休眠规则（从天眼治理层配置读取）：
 *   - 周休眠：每周六 20:00 ~ 00:00 CST（4小时）
 *   - 日休眠：每日 04:00 ~ 04:xx CST（动态调整，默认10分钟）
 * 
 * 对于生命线任务：输出提示但不阻止执行
 * 对于普通任务：输出提示并建议延迟
 */

function isInHibernation() {
  const now = new Date();
  // 转换为北京时间
  const cst = new Date(now.toLocaleString('en-US', { timeZone: 'Asia/Shanghai' }));
  const day = cst.getDay(); // 0=周日, 6=周六
  const hour = cst.getHours();
  const minute = cst.getMinutes();
  
  // 周休眠：周六 20:00 ~ 周日 00:00
  if (day === 6 && hour >= 20) {
    return { hibernating: true, type: 'weekly', endsAt: '周日 00:00 CST' };
  }
  if (day === 0 && hour < 0) {
    return { hibernating: true, type: 'weekly', endsAt: '周日 00:00 CST' };
  }
  
  // 日休眠：每日 04:00 ~ 04:10（默认窗口）
  if (hour === 4 && minute < 10) {
    return { hibernating: true, type: 'daily', endsAt: '04:10 CST' };
  }
  
  return { hibernating: false };
}

module.exports = { isInHibernation };
```

### CRON 时间窗口设计

| **任务** | **CRON 表达式（UTC）** | **北京时间** | **频率** | **说明** |
| --- | --- | --- | --- | --- |
| 主刷新 A | `0 2 * * 1` | 每周一 10:00 | 每周一次 | 距周六休眠最远，最安全的刷新时间点 |
| 主刷新 B | `0 2 * * 4` | 每周四 10:00 | 每周一次 | 主刷新 A 的 3 天后补刷，确保 6 天内至少刷新一次 |
| 安全网检查 | `0 4 * * *` | 每天 12:00 | 每天 | 只检查不刷新，即将过期才紧急刷新（要遮开日休眠 04:00-04:10） |

**为什么选周一+周四：**

- 周一刷新 → Token 有效到下周一
- 周四再刷新 → Token 有效到下周四
- 即使周一刷新失败，周四还有机会补救
- 即使周四也失败，每天 12:00 的安全网检查会发现并紧急刷新
- **三层保险，绝对不会因为休眠而错过刷新窗口**

---

## Phase D · GitHub Action Workflow

### 主刷新 Workflow：`.github/workflows/renew-gdrive-tokens.yml`

```yaml
name: \uD83D\uDD04 OAuth2 Token \u81EA\u52A8\u7EED\u671F

on:
  schedule:
    # \u4E3B\u5237\u65B0 A: \u6BCF\u5468\u4E00 10:00 CST (02:00 UTC)
    - cron: '0 2 * * 1'
    # \u4E3B\u5237\u65B0 B: \u6BCF\u5468\u56DB 10:00 CST (02:00 UTC)
    - cron: '0 2 * * 4'
  workflow_dispatch:
    inputs:
      force_renew:
        description: '\u5F3A\u5236\u5237\u65B0\u6240\u6709 Token\uFF08\u5FFD\u7565\u8FC7\u671F\u65F6\u95F4\u68C0\u67E5\uFF09'
        required: false
        type: boolean
        default: false

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

      - name: \uD83D\uDD27 \u5B89\u88C5\u4F9D\u8D56
        run: npm install tweetsodium

      - name: \uD83D\uDD04 \u6267\u884C Token \u5237\u65B0
        env:
          GDRIVE_CLIENT_ID: $ secrets.GDRIVE_CLIENT_ID 
          GDRIVE_CLIENT_SECRET: $ secrets.GDRIVE_CLIENT_SECRET 
          GDRIVE_REFRESH_TOKEN: $ secrets.GDRIVE_REFRESH_TOKEN 
          GITHUB_TOKEN: $ secrets.GITHUB_TOKEN 
          FORCE_RENEW: $ inputs.force_renew || 'false' 
        run: node scripts/gdrive/renew-tokens.js

      - name: \uD83D\uDCCB \u63D0\u4EA4\u6CE8\u518C\u8868\u66F4\u65B0
        run: |
          git config user.name "zhuyuan-bot"
          git config user.email "zhuyuan@guanghulab.com"
          git add config/gdrive-tokens.json grid-db/logs/
          git diff --cached --quiet || git commit -m "auto: Token renewal $(date -u +%Y-%m-%dT%H:%M:%SZ) [skip ci]"
          git push

      - name: \u26A0\uFE0F \u5931\u8D25\u544A\u8B66
        if: failure()
        run: |
          echo "\u26A0\uFE0F Token \u5237\u65B0\u5931\u8D25\uFF01\u68C0\u67E5 grid-db/logs/token-alert.json"
          echo "\u9700\u8981\u4EBA\u7C7B\u624B\u52A8\u91CD\u65B0\u6388\u6743\u3002"
```

### 安全网检查 Workflow：`.github/workflows/check-token-health.yml`

```yaml
name: \uD83D\uDEE1\uFE0F Token \u5065\u5EB7\u68C0\u67E5\uFF08\u6BCF\u65E5\u5B89\u5168\u7F51\uFF09

on:
  schedule:
    # \u6BCF\u5929 12:00 CST (04:00 UTC) \u2014 \u5728\u65E5\u4F11\u7720 04:10 \u4E4B\u540E
    - cron: '15 4 * * *'
  workflow_dispatch:

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

      - name: \uD83D\uDEE1\uFE0F \u68C0\u67E5 Token \u5065\u5EB7\u72B6\u6001
        run: |
          node -e "
          const fs = require('fs');
          const reg = JSON.parse(fs.readFileSync('config/gdrive-tokens.json', 'utf8'));
          let urgent = false;
          for (const t of reg.tokens) {
            const hoursLeft = (new Date(t.expires_at) - new Date()) / 3600000;
            console.log(t.user_name + ': ' + hoursLeft.toFixed(1) + 'h remaining');
            if (hoursLeft < 48) {
              console.log('  \u26A0\uFE0F URGENT: < 48h, triggering emergency renewal');
              urgent = true;
            }
          }
          if (urgent) process.exit(1);
          console.log('\u2705 All tokens healthy.');
          "

      - name: \u26A1 \u7D27\u6025\u5237\u65B0\uFF08\u4EC5\u5728\u5373\u5C06\u8FC7\u671F\u65F6\uFF09
        if: failure()
        env:
          GDRIVE_CLIENT_ID: $ secrets.GDRIVE_CLIENT_ID 
          GDRIVE_CLIENT_SECRET: $ secrets.GDRIVE_CLIENT_SECRET 
          GDRIVE_REFRESH_TOKEN: $ secrets.GDRIVE_REFRESH_TOKEN 
          GITHUB_TOKEN: $ secrets.GITHUB_TOKEN 
        run: |
          npm install tweetsodium
          FORCE_RENEW=true node scripts/gdrive/renew-tokens.js

      - name: \uD83D\uDCCB \u63D0\u4EA4
        if: always()
        run: |
          git config user.name "zhuyuan-bot"
          git config user.email "zhuyuan@guanghulab.com"
          git add config/gdrive-tokens.json grid-db/logs/
          git diff --cached --quiet || git commit -m "auto: Token health check $(date -u +%Y-%m-%dT%H:%M:%SZ) [skip ci]"
          git push
```

---

## Phase E · 天眼集成：Token 续期引擎作为生命线任务注册

### 更新天眼治理层配置

在天眼系统的配置中（`skyeye-config.json` 或等价配置文件），新增 Token 续期引擎为 **生命线任务**：

```json
{
  "lifeline_tasks": [
    {
      "id": "TOKEN-RENEW",
      "name": "OAuth2 Token \u81ea\u52a8\u7eed\u671f",
      "workflow": "renew-gdrive-tokens.yml",
      "hibernate_exempt": true,
      "priority": "P0",
      "alert_on_failure": true,
      "alert_channels": ["notion-dashboard", "agent-bulletin"]
    },
    {
      "id": "TOKEN-HEALTH",
      "name": "Token \u5065\u5eb7\u68c0\u67e5",
      "workflow": "check-token-health.yml",
      "hibernate_exempt": true,
      "priority": "P0",
      "alert_on_failure": true,
      "alert_channels": ["notion-dashboard", "agent-bulletin"]
    }
  ]
}
```

### 天眼休眠引擎修改

在天眼系统的休眠决策引擎中，增加生命线任务豁免逻辑：

```jsx
/**
 * 休眠决策引擎修改点：
 * 
 * 在 shouldSkipTask(taskId) 函数中增加：
 */
function shouldSkipTask(taskId) {
  const config = loadSkyeyeConfig();
  const { hibernating } = checkHibernation();
  
  if (!hibernating) return false; // 非休眠期，所有任务正常执行
  
  // 检查是否是生命线任务
  const isLifeline = config.lifeline_tasks.some(t => t.id === taskId);
  
  if (isLifeline) {
    console.log(`\u26a0\ufe0f \u5f53\u524d\u5728\u4f11\u7720\u671f\uff0c\u4f46 ${taskId} \u662f\u751f\u547d\u7ebf\u4efb\u52a1\uff0c\u8c41\u514d\u6267\u884c`);
    return false; // \u751f\u547d\u7ebf\u4efb\u52a1\u4e0d\u8df3\u8fc7
  }
  
  console.log(`\u26a0\ufe0f ${taskId} \u5728\u4f11\u7720\u671f\u5185\uff0c\u5ef6\u8fdf\u6267\u884c`);
  return true; // \u666e\u901a\u4efb\u52a1\u8df3\u8fc7
}
```

---

## 🛡️ 安全红线

<aside>
🚫

**Token 续期引擎安全规则：**

- ❌ 不在日志中打印 Token 内容（只打印状态和时间）
- ❌ 不在代码中硬编码任何凭据
- ❌ 不将 Token 写入仓库文件（只写入 GitHub Secrets）
- ❌ 注册表 `gdrive-tokens.json` 中不存储 Token 本身，只存储元数据（过期时间、状态等）
- ✅ Token 实体永远只存在于 GitHub Secrets 中
</aside>

---

*下篇继续：多用户 Token 流程 + 告警推送 + 天眼扫描清单 + 完整执行回执*

🖋️ 霜砚 · PER-SY001

🧊 代 TCS-0002∞ 冰朔 签发

📅 2026-03-24T15:00+08:00