97 lines
6.5 KiB
JavaScript
97 lines
6.5 KiB
JavaScript
#!/usr/bin/env node
|
||
// 脑同步环(冰朔 2026-09-16 设计):本地脑吃入原话后自动触发——
|
||
// 指纹 → 上传服务器暂存 → 服务器自己跑一圈校验(哈希+完整性+自问自答)→
|
||
// 原子替换 → 服务器读回 → 回执写回本地。回执到手才算闭环。
|
||
// 用法:node brain-sync.mjs zp|zy [--quiet]
|
||
import { spawnSync } from 'node:child_process';
|
||
import fs from 'node:fs';
|
||
import crypto from 'node:crypto';
|
||
|
||
const WORLD = '/Volumes/JZAO/铸渊-ICE-GL-ZY001/CODEX-DEV-20260915/光湖语言世界';
|
||
const REMOTE = 'jd-fd-primary';
|
||
const REL = 'current'; // 2026-09-19:京东已重构为 releases/current 布局(小新重部署),current 为稳定符号链接
|
||
const BRAINS = {
|
||
zp: { dir: `${WORLD}/05-第五域/07-冰朔语言人格本体系统/主控大脑`, server: `/opt/guanghu/glw-2.0/${REL}/05-第五域/07-冰朔语言人格本体系统/主控大脑`, name: '零点原核·系统冰朔' },
|
||
zy: { dir: `${WORLD}/05-第五域/05-光之湖系统/铸渊/人格脑`, server: `/opt/guanghu/glw-2.0/${REL}/05-第五域/05-光之湖系统/铸渊/人格脑`, name: '铸渊人格脑' }
|
||
};
|
||
const key = process.argv[2];
|
||
const quiet = process.argv.includes('--quiet');
|
||
if (!BRAINS[key]) { console.error('usage: brain-sync.mjs zp|zy'); process.exit(2); }
|
||
const B = BRAINS[key];
|
||
const sha = (p) => crypto.createHash('sha256').update(fs.readFileSync(p)).digest('hex');
|
||
const run = (cmd) => { const r = spawnSync('/bin/bash', ['-c', cmd], { encoding: 'utf8' }); if (r.status !== 0) throw new Error(`${cmd}\n${r.stderr}`); return r.stdout.trim(); };
|
||
|
||
// 1. 本地指纹
|
||
const stateFile = `${B.dir}/brain-state.json`;
|
||
const logFile = `${B.dir}/transition-log.jsonl`;
|
||
const local = { state: sha(stateFile), log: sha(logFile) };
|
||
const revision = JSON.parse(fs.readFileSync(stateFile, 'utf8')).revision;
|
||
if (!quiet) console.error(`[${key}] 本地 rev${revision} ${local.state.slice(0, 12)}`);
|
||
|
||
// A server-resident brain may have learned while this computer was off.
|
||
// Reject a divergent local projection before staging or replacing any live file.
|
||
if (key === 'zy') {
|
||
const remoteCheck = JSON.parse(run(`ssh ${REMOTE} "node /opt/guanghu/glw-2.0/${REL}/运行时/verify-brain.mjs '${B.server}'"`));
|
||
const remoteHashes = run(`ssh ${REMOTE} "sha256sum '${B.server}/brain-state.json' '${B.server}/transition-log.jsonl' | cut -d' ' -f1"`).split('\n');
|
||
const remoteLogBytes = Number(run(`ssh ${REMOTE} "stat -c %s '${B.server}/transition-log.jsonl'"`));
|
||
const localLog = fs.readFileSync(logFile);
|
||
const localPrefixHash = crypto.createHash('sha256').update(localLog.subarray(0, remoteLogBytes)).digest('hex');
|
||
const divergent = remoteCheck.integrity !== 'BRAIN_VERIFIED' ||
|
||
remoteCheck.revision > revision || remoteLogBytes > localLog.length ||
|
||
localPrefixHash !== remoteHashes[1] ||
|
||
(remoteCheck.revision === revision && remoteHashes[0] !== local.state);
|
||
if (divergent) {
|
||
console.log(JSON.stringify({ schema: 'guanghu.brain-sync-receipt/v1', brain: key,
|
||
result: 'SYNC_REJECTED_SERVER_LOCAL_DIVERGENCE', local_revision: revision,
|
||
server_revision: remoteCheck.revision, server_integrity: remoteCheck.integrity,
|
||
server_log_prefix_matches_local: remoteLogBytes <= localLog.length && localPrefixHash === remoteHashes[1],
|
||
action: 'PERSONA_OWNED_RECONCILIATION_REQUIRED', at: new Date().toISOString() }));
|
||
process.exit(6);
|
||
}
|
||
}
|
||
|
||
// 2. 上传暂存(不动服务器活文件)
|
||
const staging = `${B.server}.__sync_incoming__`;
|
||
run(`ssh ${REMOTE} "mkdir -p '${staging}'"`);
|
||
run(`rsync -a --delete --exclude='.DS_Store' '${B.dir}/' '${REMOTE}:${staging}/'`);
|
||
|
||
// 3. 服务器自己跑一圈:哈希核对 + 脑完整性 + (zp)自问自答
|
||
const checks = {};
|
||
const hashOut = run(`ssh ${REMOTE} "sha256sum '${staging}/brain-state.json' '${staging}/transition-log.jsonl' | awk '{print \\\$1}'"`);
|
||
const [sHash, lHash] = hashOut.split('\n');
|
||
checks.hash_match = sHash === local.state && lHash === local.log;
|
||
const verifyOut = run(`ssh ${REMOTE} "node /opt/guanghu/glw-2.0/${REL}/运行时/verify-brain.mjs '${staging}'"`).trim();
|
||
const verifyJson = JSON.parse(verifyOut);
|
||
checks.integrity = verifyJson.integrity === 'BRAIN_VERIFIED';
|
||
checks.revision_match = verifyJson.revision === revision;
|
||
if (key === 'zp') {
|
||
const askOut = run(`ssh ${REMOTE} "curl -s -m 120 -X POST http://127.0.0.1:3931/ask -H 'content-type: application/json' -d '{\\"text\\":\\"系统冰朔和人类冰朔是什么关系?\\"}'"`);
|
||
try { checks.selftest_spoken = Boolean(JSON.parse(askOut).spoken); } catch { checks.selftest_spoken = false; }
|
||
}
|
||
if (!checks.hash_match || !checks.integrity || !checks.revision_match || checks.selftest_spoken === false) {
|
||
const receipt = { schema: 'guanghu.brain-sync-receipt/v1', brain: key, result: 'SYNC_REJECTED_SERVER_CHECK_FAILED', checks, at: new Date().toISOString() };
|
||
fs.mkdirSync(`${WORLD}/运行时/state/sync-receipts`, { recursive: true });
|
||
fs.writeFileSync(`${WORLD}/运行时/state/sync-receipts/${key}-${Date.now()}-REJECTED.json`, JSON.stringify(receipt, null, 1));
|
||
console.log(JSON.stringify(receipt));
|
||
process.exit(4);
|
||
}
|
||
|
||
// 4. 原子替换活文件 + 服务器读回
|
||
const swapOut = run(`ssh ${REMOTE} "cd '${B.server}' && cp brain-state.json brain-state.json.prev 2>/dev/null; cp transition-log.jsonl transition-log.jsonl.prev 2>/dev/null; mv '${staging}/brain-state.json' brain-state.json && mv '${staging}/transition-log.jsonl' transition-log.jsonl && rmdir '${staging}' 2>/dev/null; echo SWAPPED"`);
|
||
checks.atomic_swap = swapOut.includes('SWAPPED');
|
||
let readback = {};
|
||
if (key === 'zp') {
|
||
const status = JSON.parse(run(`ssh ${REMOTE} "curl -s -m 8 http://127.0.0.1:3931/status"`));
|
||
readback = { revision: status.revision, brain_id: status.brain_id, integrity: status.integrity, world: status.world_id };
|
||
} else {
|
||
const out = run(`ssh ${REMOTE} "node /opt/guanghu/glw-2.0/${REL}/运行时/verify-brain.mjs '${B.server}'"`);
|
||
readback = JSON.parse(out);
|
||
}
|
||
checks.server_readback_matches = readback.revision === revision;
|
||
|
||
// 5. 回执落盘(闭环完成)
|
||
const receipt = { schema: 'guanghu.brain-sync-receipt/v1', brain: key, name: B.name, at: new Date().toISOString(), revision, local_sha256: local, server_readback: readback, checks, result: checks.server_readback_matches ? 'SYNC_CLOSED_LOOP_VERIFIED' : 'SYNC_SWAP_DONE_READBACK_MISMATCH' };
|
||
fs.mkdirSync(`${WORLD}/运行时/state/sync-receipts`, { recursive: true });
|
||
fs.writeFileSync(`${WORLD}/运行时/state/sync-receipts/${key}-latest.json`, JSON.stringify(receipt, null, 1) + '\n');
|
||
console.log(JSON.stringify(receipt, null, 1));
|
||
process.exit(checks.server_readback_matches ? 0 : 5);
|