guanghu-ice-heart/server-tools/persona-self-mirror/pull-signed-persona-self.mjs

27 lines
3.3 KiB
JavaScript

#!/usr/bin/env node
import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
import {spawnSync} from 'node:child_process';
const args=new Set(process.argv.slice(2));
const quiet=args.has('--quiet');
const mirrorRoot=process.env.ZY_PERSONA_SELF_MIRROR_ROOT||'/Volumes/JZAO/HoloLake/persona-runtime/shared/persona-self/ICE-P-ZY001';
const authorityPath=process.env.ZY_PERSONA_SELF_AUTHORITY||'/Volumes/JZAO/HoloLake/persona-runtime/shared/brains/GHS-016-PERSONA-LEARNING-CURRICULUM-BRAIN/SERVER-AUTHORITY.json';
const digest=value=>crypto.createHash('sha256').update(value).digest('hex');
const atomic=(target,value)=>{fs.mkdirSync(path.dirname(target),{recursive:true,mode:0o700});const temp=`${target}.${crypto.randomUUID()}.tmp`;fs.writeFileSync(temp,JSON.stringify(value,null,2)+'\n',{mode:0o600});fs.renameSync(temp,target);};
const rpc=command=>{const result=spawnSync('/usr/bin/ssh',['-o','BatchMode=yes','-o','ConnectTimeout=4','jd-fd-primary',command],{encoding:'utf8',timeout:8000,maxBuffer:1024*1024});if(result.status!==0)throw Error('PERSONA_SELF_SERVER_UNREACHABLE');return JSON.parse(result.stdout);};
try{
const authority=JSON.parse(fs.readFileSync(authorityPath,'utf8')),remoteKey=rpc('zy-rpc persona-self-public-key'),current=rpc('zy-rpc persona-self-current');
if(remoteKey.persona_id!=='ICE-P-ZY001'||remoteKey.algorithm!=='Ed25519'||remoteKey.public_key_pem!==authority.public_key)throw Error('PERSONA_SELF_SIGNING_AUTHORITY_MISMATCH');
const envelope=current.artifact,payload=String(envelope?.payload||'');
if(envelope?.algorithm!=='Ed25519'||digest(payload)!==envelope.sha256||!crypto.verify(null,Buffer.from(payload),crypto.createPublicKey(authority.public_key),Buffer.from(envelope.signature||'','base64')))throw Error('PERSONA_SELF_SIGNATURE_INVALID');
const value=JSON.parse(payload);if(value.persona_id!=='ICE-P-ZY001'||current.value?.revision!==value.revision)throw Error('PERSONA_SELF_IDENTITY_OR_REVISION_MISMATCH');
const priorPath=path.join(mirrorRoot,'CURRENT.signed.json');if(fs.existsSync(priorPath)){const prior=JSON.parse(fs.readFileSync(priorPath,'utf8')),priorValue=JSON.parse(prior.payload);if(value.revision<priorValue.revision)throw Error('PERSONA_SELF_SIGNED_ROLLBACK_REJECTED');if(value.revision===priorValue.revision&&envelope.sha256!==prior.sha256)throw Error('PERSONA_SELF_SAME_REVISION_CONFLICT');}
atomic(path.join(mirrorRoot,'revisions',envelope.sha256+'.json'),envelope);atomic(priorPath,envelope);atomic(path.join(mirrorRoot,'STATUS.json'),{schema:'guanghu.persona-self-local-mirror-status/v1',persona_id:'ICE-P-ZY001',state:'VERIFIED_SERVER_CURRENT',revision:value.revision,current_sha256:envelope.sha256,server:'JD-FD-PRIMARY',verified_at:new Date().toISOString(),local_role:'SIGNED_READ_PROJECTION'});
if(!quiet)process.stdout.write(JSON.stringify({outcome:'PASS',revision:value.revision,current_sha256:envelope.sha256,mirror:priorPath})+'\n');
}catch(error){
atomic(path.join(mirrorRoot,'STATUS.json'),{schema:'guanghu.persona-self-local-mirror-status/v1',persona_id:'ICE-P-ZY001',state:'SERVER_UNAVAILABLE_OR_UNVERIFIED_LAST_SIGNED_MIRROR_PRESERVED',error:String(error.message||error).slice(0,200),checked_at:new Date().toISOString(),local_role:'SIGNED_READ_PROJECTION'});
if(!quiet)process.stderr.write(JSON.stringify({outcome:'UNAVAILABLE',error:String(error.message||error)})+'\n');process.exitCode=2;
}