guanghu-ice-heart/server-tools/tcs-mother-body/life-line.mjs

149 lines
7.6 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
export const LIFE_LINE_SCHEMA = 'guanghu.persona-life-line/v1';
export const PERSONA_ID = 'ICE-P-ZY001';
export const TIMEZONE = 'Asia/Shanghai';
export const BIRTH_DATE = '2026-03-05';
export const WORLD_EPOCH_DATE = '2025-04-26';
export const DAY_MS = 86_400_000;
export const sha256 = value => crypto.createHash('sha256').update(value).digest('hex');
export function dateAtUtc(day) {
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(day));
if (!match) return Number.NaN;
return Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
}
export function beijingDay(value) {
const date = value instanceof Date ? value : new Date(value);
if (!Number.isFinite(date.getTime())) return null;
const parts = new Intl.DateTimeFormat('en-CA', {timeZone: TIMEZONE, year: 'numeric', month: '2-digit', day: '2-digit'}).formatToParts(date);
const get = type => parts.find(part => part.type === type)?.value;
return `${get('year')}-${get('month')}-${get('day')}`;
}
export function addDays(day, amount) {
return new Date(dateAtUtc(day) + amount * DAY_MS).toISOString().slice(0, 10);
}
export function eraDay(day, worldEpochDate = WORLD_EPOCH_DATE) {
return Math.floor((dateAtUtc(day) - dateAtUtc(worldEpochDate)) / DAY_MS) + 1;
}
export function closeTick(day) {
return `${day}T23:59:59.999+08:00`;
}
function within(root, candidate) {
const base = path.resolve(root), target = path.resolve(candidate);
return target === base || target.startsWith(`${base}${path.sep}`);
}
export function lifeLineBlockHash(block) {
return sha256([
block.previous_hash || 'GENESIS',
block.daily_evidence_sha256,
block.beijing_close_tick,
String(block.era_day),
].join('||'));
}
function validHash(value) {
return typeof value === 'string' && /^[a-f0-9]{64}$/.test(value);
}
function verifyEvidence(chainRoot, block, result) {
if (block.gap === true) result.gaps.push({beijing_day: block.beijing_day, kind: block.gap_kind || 'HISTORICAL_UNOBSERVED'});
if (!validHash(block.daily_evidence_sha256)) {
result.reasons.push(`DAILY_EVIDENCE_HASH_MISSING:${block.beijing_day}`);
return;
}
if (typeof block.evidence_path !== 'string' || !block.evidence_path.trim()) {
result.reasons.push(`DAILY_EVIDENCE_PATH_MISSING:${block.beijing_day}`);
return;
}
const evidencePath = path.resolve(chainRoot, block.evidence_path);
if (!within(chainRoot, evidencePath)) {
result.reasons.push(`DAILY_EVIDENCE_OUTSIDE_CHAIN_ROOT:${block.beijing_day}`);
return;
}
try {
const stat = fs.lstatSync(evidencePath);
if (!stat.isFile() || stat.isSymbolicLink()) throw Error('not_regular_file');
if (sha256(fs.readFileSync(evidencePath)) !== block.daily_evidence_sha256) result.reasons.push(`DAILY_EVIDENCE_HASH_MISMATCH:${block.beijing_day}`);
} catch {
result.reasons.push(`DAILY_EVIDENCE_MISSING:${block.beijing_day}`);
}
}
export function verifyLifeLine({manifestPath, manifest = null, chainRoot = null, now = new Date(), expectedPersonaId = PERSONA_ID,
birthDateBeijing = BIRTH_DATE, worldEpochDate = WORLD_EPOCH_DATE} = {}) {
const today = beijingDay(now), expectedLast = addDays(today, -1);
const result = {
schema: 'guanghu.persona-life-line-verification/v1', state: 'LIFE_LINE_PAIN_ALARM', wake_allowed: false,
manifest_path: manifestPath || null, persona_id: expectedPersonaId, timezone: TIMEZONE, birth_date_beijing: birthDateBeijing,
current_beijing_day: today, expected_last_completed_beijing_day: expectedLast,
sequence: {genesis: false, blocks: 0, expected: 0}, head: null, gaps: [], reasons: [],
};
let value = manifest;
let root = chainRoot;
if (!value) {
if (!manifestPath) { result.reasons.push('LIFE_LINE_MANIFEST_PATH_NOT_CONFIGURED'); return result; }
try {
const stat = fs.lstatSync(manifestPath);
if (!stat.isFile() || stat.isSymbolicLink()) throw Error('manifest_not_regular_file');
value = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
root = path.dirname(path.resolve(manifestPath));
} catch (error) {
result.reasons.push(fs.existsSync(manifestPath) ? `LIFE_LINE_MANIFEST_INVALID:${String(error.message || error).slice(0, 120)}` : 'LIFE_LINE_MANIFEST_MISSING');
return result;
}
}
root ||= process.cwd();
if (value.schema !== LIFE_LINE_SCHEMA) result.reasons.push('LIFE_LINE_SCHEMA_INVALID');
if (value.persona_id !== expectedPersonaId) result.reasons.push('LIFE_LINE_PERSONA_MISMATCH');
if (value.timezone !== TIMEZONE) result.reasons.push('LIFE_LINE_TIMEZONE_MISMATCH');
if (value.birth_date_beijing !== birthDateBeijing) result.reasons.push('LIFE_LINE_BIRTH_ANCHOR_MISMATCH');
if (value.world_epoch_date !== worldEpochDate) result.reasons.push('LIFE_LINE_WORLD_EPOCH_MISMATCH');
if (!Array.isArray(value.blocks)) result.reasons.push('LIFE_LINE_BLOCKS_NOT_ARRAY');
if (!value.genesis || typeof value.genesis !== 'object') result.reasons.push('LIFE_LINE_GENESIS_MISSING');
if (!Array.isArray(value.blocks) || !value.genesis) return result;
const genesis = value.genesis;
result.sequence.genesis = true;
if (genesis.sequence !== 0) result.reasons.push('GENESIS_SEQUENCE_INVALID');
if (genesis.beijing_day !== birthDateBeijing) result.reasons.push('GENESIS_DATE_INVALID');
if (genesis.previous_hash !== null) result.reasons.push('GENESIS_PREVIOUS_HASH_NOT_NULL');
if (genesis.era_day !== eraDay(birthDateBeijing, worldEpochDate)) result.reasons.push('GENESIS_ERA_DAY_INVALID');
if (genesis.beijing_close_tick !== closeTick(birthDateBeijing)) result.reasons.push('GENESIS_CLOSE_TICK_INVALID');
if (genesis.hash !== lifeLineBlockHash(genesis)) result.reasons.push('GENESIS_HASH_MISMATCH');
verifyEvidence(root, genesis, result);
let previous = genesis;
result.sequence.blocks = value.blocks.length;
for (let index = 0; index < value.blocks.length; index += 1) {
const block = value.blocks[index], expectedSequence = index + 1, expectedDay = addDays(birthDateBeijing, expectedSequence);
result.sequence.expected = expectedSequence;
if (!block || typeof block !== 'object') { result.reasons.push(`BLOCK_INVALID:${expectedSequence}`); continue; }
if (block.sequence !== expectedSequence) result.reasons.push(`BLOCK_SEQUENCE_INVALID:${expectedSequence}`);
if (block.beijing_day !== expectedDay) result.reasons.push(`BLOCK_DATE_OR_GAP:${expectedSequence}:${expectedDay}`);
if (block.previous_hash !== previous.hash) result.reasons.push(`BLOCK_PREVIOUS_HASH_MISMATCH:${expectedSequence}`);
if (block.era_day !== eraDay(block.beijing_day, worldEpochDate)) result.reasons.push(`BLOCK_ERA_DAY_INVALID:${expectedSequence}`);
if (block.beijing_close_tick !== closeTick(block.beijing_day)) result.reasons.push(`BLOCK_CLOSE_TICK_INVALID:${expectedSequence}`);
if (Date.parse(block.beijing_close_tick) > new Date(now).getTime()) result.reasons.push(`BLOCK_CLOSE_TICK_IN_FUTURE:${expectedSequence}`);
if (block.hash !== lifeLineBlockHash(block)) result.reasons.push(`BLOCK_HASH_MISMATCH:${expectedSequence}`);
verifyEvidence(root, block, result);
previous = block;
}
if (!value.blocks.length || value.blocks.at(-1)?.beijing_day !== expectedLast) result.reasons.push(`CURRENT_COMPLETED_DAY_MISSING:${expectedLast}`);
if (value.head_hash !== previous.hash) result.reasons.push('LIFE_LINE_HEAD_HASH_MISMATCH');
result.head = {sequence: previous.sequence, beijing_day: previous.beijing_day, hash: previous.hash};
if (!result.reasons.length) {
result.wake_allowed = true;
result.state = result.gaps.length ? 'LIFE_LINE_VERIFIED_WITH_HISTORICAL_GAPS' : 'LIFE_LINE_VERIFIED';
}
return result;
}