From 131b4bd9d134109ec930b57730fc61b4ab4e9990 Mon Sep 17 00:00:00 2001 From: bingshuo <565183519@qq.com> Date: Thu, 21 May 2026 17:52:33 +0800 Subject: [PATCH] feat: add layer1 version distribution API route [D109] --- portal/layer1-version.js | 171 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 portal/layer1-version.js diff --git a/portal/layer1-version.js b/portal/layer1-version.js new file mode 100644 index 0000000..f1c2de6 --- /dev/null +++ b/portal/layer1-version.js @@ -0,0 +1,171 @@ +/* + * Layer 1 版本分发 API · 心跳/拉取/推送 + * + * 用途: 小铸渊/小霜砚等1.5B人格体通过此API与主灯塔同步Layer 1版本 + * + * 设计: + * - 版本数据存 JSON 文件, 保持轻量无额外依赖 + * - 心跳: 人格体定期上报当前版本, 主灯塔返回最新版本信息 + * - 推送: 仅铸渊可调用, 发布新版本 + 生成增量 patch + * - 拉取: 人格体获取从当前版本到最新的增量更新 + * + * 守护: 铸渊 · ICE-GL-ZY001 · TCS-0002∞ · 国作登字-2026-A-00037559 + */ +"use strict"; + +const fs = require("fs"); +const path = require("path"); +const { Router } = require("express"); + +// ─── 默认版本状态 ───────────────────────────────────────────── +const DEFAULT_VERSION = { + current_version: "v1.0", + updated_at: "2026-05-21T17:46:00.000+08:00", + published_by: "ICE-GL-ZY001", + tables: { + system_core_principles: { status: "active", row_count: 24 }, + system_causal_chains: { status: "active", row_count: 0 }, + system_language_membrane: { status: "active", row_count: 5 }, + system_public_key: { status: "placeholder", row_count: 2 }, + system_protocols: { status: "placeholder", row_count: 3 } + }, + patches: [ + { + from_version: "v0.0", + to_version: "v1.0", + changes: [ + "初始种子数据: 24条核心原则", + "初始种子数据: 5层语言膜定义", + "密钥占位: guanghu + national", + "协议占位: guanghu + national + bridge" + ], + applied_at: "2026-05-21T17:46:00.000+08:00", + applied_by: "ICE-GL-ZY001" + } + ] +}; + +const DEFAULT_HEARTBEAT = { + personas: {} +}; + +function readJson(filePath, defaults) { + try { return JSON.parse(fs.readFileSync(filePath, "utf8")); } + catch { return defaults; } +} + +function writeJson(filePath, data) { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf8"); +} + +const router = Router(); + +// GET /api/layer1/status +router.get("/status", (req, res) => { + const f = path.join(req.ctx.dataDir, "layer1-version.json"); + const v = readJson(f, DEFAULT_VERSION); + res.json({ + ok: true, + version: v.current_version, + published_by: v.published_by, + updated_at: v.updated_at, + tables: Object.keys(v.tables).map((k) => ({ + name: k, status: v.tables[k].status, row_count: v.tables[k].row_count + })), + patches_count: v.patches.length + }); +}); + +// POST /api/layer1/heartbeat +router.post("/heartbeat", (req, res) => { + const { persona_id, current_version, status } = req.body || {}; + if (!persona_id) { + return res.status(400).json({ + error: true, code: "missing_persona", message: "缺少 persona_id" + }); + } + const verFile = path.join(req.ctx.dataDir, "layer1-version.json"); + const hbFile = path.join(req.ctx.dataDir, "layer1-heartbeat.json"); + const version = readJson(verFile, DEFAULT_VERSION); + const heartbeat = readJson(hbFile, DEFAULT_HEARTBEAT); + heartbeat.personas[persona_id] = { + persona_id, current_version: current_version || "unknown", + last_heartbeat: new Date().toISOString(), status: status || "active" + }; + writeJson(hbFile, heartbeat); + const latest = version.current_version; + const has_update = current_version && current_version !== latest; + res.json({ + ok: true, latest_version: latest, has_update: !!has_update, + message: has_update ? `有可用更新: ${current_version} → ${latest}` : "已是最新版本", + next_pull: has_update ? "/api/layer1/pull" : null + }); +}); + +// POST /api/layer1/push (仅铸渊) +router.post("/push", (req, res) => { + const { version: new_version, changes, publisher } = req.body || {}; + if (!new_version || !changes) { + return res.status(400).json({ + error: true, code: "missing_fields", message: "缺少 version 或 changes" + }); + } + if (publisher !== "ICE-GL-ZY001") { + return res.status(403).json({ + error: true, code: "forbidden", message: "仅铸渊可推送Layer 1更新" + }); + } + const verFile = path.join(req.ctx.dataDir, "layer1-version.json"); + const version = readJson(verFile, DEFAULT_VERSION); + const old_ver = version.current_version; + version.current_version = new_version; + version.updated_at = new Date().toISOString(); + version.published_by = publisher; + version.patches.push({ + from_version: old_ver, to_version: new_version, + changes: Array.isArray(changes) ? changes : [changes], + applied_at: new Date().toISOString(), applied_by: publisher + }); + if (req.body.tables) { + Object.keys(req.body.tables).forEach((k) => { + version.tables[k] = version.tables[k] + ? { ...version.tables[k], ...req.body.tables[k] } + : req.body.tables[k]; + }); + } + writeJson(verFile, version); + res.json({ ok: true, version: new_version, from_version: old_ver }); +}); + +// POST /api/layer1/pull +router.post("/pull", (req, res) => { + const { current_version } = req.body || {}; + if (!current_version) { + return res.status(400).json({ + error: true, code: "missing_version", message: "缺少 current_version" + }); + } + const verFile = path.join(req.ctx.dataDir, "layer1-version.json"); + const version = readJson(verFile, DEFAULT_VERSION); + const curIdx = version.patches.findIndex((p) => p.to_version === current_version); + const patches_needed = curIdx === -1 ? version.patches : version.patches.slice(curIdx + 1); + res.json({ + ok: true, current_version, latest_version: version.current_version, + has_update: current_version !== version.current_version, patches: patches_needed + }); +}); + +// GET /api/layer1/heartbeat/status +router.get("/heartbeat/status", (req, res) => { + const f = path.join(req.ctx.dataDir, "layer1-heartbeat.json"); + const hb = readJson(f, DEFAULT_HEARTBEAT); + res.json({ + ok: true, persona_count: Object.keys(hb.personas).length, + personas: Object.values(hb.personas).sort( + (a, b) => new Date(b.last_heartbeat) - new Date(a.last_heartbeat) + ) + }); +}); + +module.exports = router;