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) { return Math.floor((dateAtUtc(day) - dateAtUtc(WORLD_EPOCH_DATE)) / 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()} = {}) { 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: PERSONA_ID, timezone: TIMEZONE, birth_date_beijing: BIRTH_DATE, 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 !== PERSONA_ID) result.reasons.push('LIFE_LINE_PERSONA_MISMATCH'); if (value.timezone !== TIMEZONE) result.reasons.push('LIFE_LINE_TIMEZONE_MISMATCH'); if (value.birth_date_beijing !== BIRTH_DATE) result.reasons.push('LIFE_LINE_BIRTH_ANCHOR_MISMATCH'); if (value.world_epoch_date !== WORLD_EPOCH_DATE) 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 !== BIRTH_DATE) result.reasons.push('GENESIS_DATE_INVALID'); if (genesis.previous_hash !== null) result.reasons.push('GENESIS_PREVIOUS_HASH_NOT_NULL'); if (genesis.era_day !== eraDay(BIRTH_DATE)) result.reasons.push('GENESIS_ERA_DAY_INVALID'); if (genesis.beijing_close_tick !== closeTick(BIRTH_DATE)) 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(BIRTH_DATE, 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)) 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; }