shuangyan-notebook/第五域 · Fifth Domain/⚒️ 铸渊·协作指令|GitHub ↔ Notion 桥接协议/🔄 铸渊指令|OAuth2 Token 自动续期引擎 + 天眼休眠避让机制(上篇)· 6天自动刷新 bee3531f4e1a4e52a597922626c9d317.md
Guanghu Domestic Migration a27e87cb99 chore: import sanitized domestic snapshot for REPO-007
Source snapshot: 97d7f0fae96dc04b7ddad56fc1db6a108ed662cc

[SEC-CLEAN] · pre-push-clean v1.0 · 109处敏感信息已自动转乱码
2026-07-17 15:59:55 +08:00

689 lines
22 KiB
Markdown
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.

---
belongs_to:
- "[[INDEX · 铸渊·协作指令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:0000:004小时+ 每日短休眠。如果 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_tokenToken 可能仍然有效但无法续期`);
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:0000:00 进入休眠期。
如果刷新任务刚好落在周六晚上系统休眠了——刷新不执行——第7天 Token 过期——**全线崩溃。**
</aside>
### 解决方案:双保险机制
**保险 ① CRON 时间窗口设计**
刷新任务不能只用一个固定 CRON。需要
- **主刷新:** 每 6 天触发一次(周一 + 周日,巧妙错开周六休眠期)
- **安全网:** 每天大休眠外进行一次轻量级检查(只检查不刷新,发现即将过期则紧急刷新)
**保险 ② :休眠期智能避让**
Token 刷新属于 **生命线任务**,在天眼系统中享有特殊地位:
<aside>
🛡️
**生命线任务豁免规则:**
天眼系统休眠期内,以下任务不受休眠影响,照常执行:
- ✅ Token 刷新引擎(本指令)
- ✅ 心跳检测(子仓库签到)
- ✅ 安全告警(异常检测)
其他任务(升级、巡检、同步、广播分发等)仍然遵守休眠规则。
</aside>
### 休眠避让检测脚本:`scripts/gdrive/check-hibernation.js`
```jsx
/**
* check-hibernation.js
*
* 检查当前是否在天眼休眠期内
*
* 休眠规则(从天眼治理层配置读取):
* - 周休眠:每周六 20:00 ~ 00:00 CST4小时
* - 日休眠:每日 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