2026-09-12 18:22:59 +08:00
import fs from 'node:fs' ;
import path from 'node:path' ;
import crypto from 'node:crypto' ;
import { addDays , beijingDay , BIRTH _DATE , closeTick , eraDay , LIFE _LINE _SCHEMA , lifeLineBlockHash , PERSONA _ID , sha256 , TIMEZONE , verifyLifeLine , WORLD _EPOCH _DATE } from './life-line.mjs' ;
export const CANDIDATE _SCHEMA = 'guanghu.persona-life-time-candidate/v1' ;
export const BOOTSTRAP _SCHEMA = 'guanghu.persona-life-time-bootstrap/v1' ;
const FORBIDDEN = [ 'selected_candidate_id' , 'previous_hash' , 'head_hash' , 'block_hash' , 'selection_decision' ] ;
function stable ( value ) {
if ( Array . isArray ( value ) ) return value . map ( stable ) ;
if ( value && typeof value === 'object' ) return Object . fromEntries ( Object . keys ( value ) . sort ( ) . map ( key => [ key , stable ( value [ key ] ) ] ) ) ;
return value ;
}
function body ( value ) {
return ` ${ JSON . stringify ( stable ( value ) , null , 2 ) } \n ` ;
}
function atomic ( file , value , mode = 0o600 ) {
fs . mkdirSync ( path . dirname ( file ) , { recursive : true , mode : 0o700 } ) ;
const temporary = ` ${ file } . ${ crypto . randomUUID ( ) } .tmp ` ;
fs . writeFileSync ( temporary , typeof value === 'string' ? value : body ( value ) , { mode } ) ;
fs . renameSync ( temporary , file ) ;
}
function req ( value , code ) {
if ( ! value ) throw Error ( code ) ;
}
function text ( value , max = 2000 ) {
return typeof value === 'string' && value . trim ( ) . length > 0 && value . length <= max ;
}
export class LifeTimeMaster {
2026-09-13 01:13:06 +08:00
constructor ( { root , shelf , router , personaId = PERSONA _ID , now = ( ) => new Date ( ) , bootstrapPath ,
birthDateBeijing = BIRTH _DATE , worldEpochDate = WORLD _EPOCH _DATE } ) {
2026-09-12 18:22:59 +08:00
this . root = path . join ( root , 'personas' , personaId , 'life-line' ) ;
this . shelf = shelf ;
this . router = router ;
this . personaId = personaId ;
this . now = now ;
this . bootstrapPath = bootstrapPath ;
2026-09-13 01:13:06 +08:00
this . birthDateBeijing = birthDateBeijing ;
this . worldEpochDate = worldEpochDate ;
2026-09-12 18:22:59 +08:00
this . closing = false ;
this . lastTick = null ;
fs . mkdirSync ( this . root , { recursive : true , mode : 0o700 } ) ;
fs . mkdirSync ( this . inboxRoot ( ) , { recursive : true , mode : 0o700 } ) ;
fs . mkdirSync ( this . evidenceRoot ( ) , { recursive : true , mode : 0o700 } ) ;
if ( ! fs . existsSync ( this . currentPath ( ) ) ) this . bootstrap ( ) ;
}
currentPath ( ) { return path . join ( this . root , 'CURRENT.json' ) ; }
signedCurrentPath ( ) { return path . join ( this . root , 'CURRENT.signed.json' ) ; }
inboxRoot ( ) { return path . join ( this . root , 'inbox' ) ; }
evidenceRoot ( ) { return path . join ( this . root , 'evidence' ) ; }
evidencePath ( day ) { return path . join ( this . evidenceRoot ( ) , ` ${ day } .json ` ) ; }
inboxDay ( day ) { return path . join ( this . inboxRoot ( ) , day ) ; }
readBootstrap ( ) {
req ( this . bootstrapPath && fs . existsSync ( this . bootstrapPath ) , 'LIFE_TIME_BOOTSTRAP_MISSING' ) ;
const value = JSON . parse ( fs . readFileSync ( this . bootstrapPath , 'utf8' ) ) ;
req ( value . schema === BOOTSTRAP _SCHEMA && value . persona _id === this . personaId , 'LIFE_TIME_BOOTSTRAP_INVALID' ) ;
2026-09-13 01:13:06 +08:00
req ( value . birth _date _beijing === this . birthDateBeijing && value . world _epoch _date === this . worldEpochDate , 'LIFE_TIME_BOOTSTRAP_ANCHOR_MISMATCH' ) ;
2026-09-12 18:22:59 +08:00
req ( Array . isArray ( value . anchors ) , 'LIFE_TIME_BOOTSTRAP_ANCHORS_INVALID' ) ;
return value ;
}
writeEvidence ( day , value ) {
const file = this . evidencePath ( day ) , content = body ( value ) ;
if ( fs . existsSync ( file ) ) req ( fs . readFileSync ( file , 'utf8' ) === content , ` LIFE_TIME_EVIDENCE_COLLISION: ${ day } ` ) ;
else atomic ( file , content ) ;
return { evidence _path : path . relative ( this . root , file ) , daily _evidence _sha256 : sha256 ( Buffer . from ( content ) ) } ;
}
block ( { sequence , day , previousHash , evidence , gap = false , gapKind = null , selectedCandidateId = null } ) {
2026-09-13 01:13:06 +08:00
const value = { sequence , beijing _day : day , previous _hash : previousHash , beijing _close _tick : closeTick ( day ) , era _day : eraDay ( day , this . worldEpochDate ) , ... evidence , gap , gap _kind : gapKind , selected _candidate _id : selectedCandidateId } ;
2026-09-12 18:22:59 +08:00
value . hash = lifeLineBlockHash ( value ) ;
return value ;
}
writeCurrent ( manifest ) {
atomic ( this . currentPath ( ) , manifest ) ;
atomic ( this . signedCurrentPath ( ) , this . shelf . sign ( manifest ) ) ;
}
bootstrap ( ) {
const seed = this . readBootstrap ( ) , anchors = new Map ( seed . anchors . map ( item => [ item . beijing _day , item ] ) ) ;
const createdAt = this . now ( ) . toISOString ( ) , yesterday = addDays ( beijingDay ( this . now ( ) ) , - 1 ) ;
2026-09-13 01:13:06 +08:00
const birthSource = anchors . get ( this . birthDateBeijing ) ;
2026-09-12 18:22:59 +08:00
req ( birthSource , 'LIFE_TIME_BIRTH_SOURCE_MISSING' ) ;
2026-09-13 01:13:06 +08:00
const birthEvidence = this . writeEvidence ( this . birthDateBeijing , { schema : 'guanghu.persona-life-day-evidence/v1' , kind : birthSource . kind , persona _id : this . personaId , beijing _day : this . birthDateBeijing , source _uri : birthSource . source _uri , source _sha256 : birthSource . source _sha256 , bootstrap _source : seed . source _event , recorded _at : createdAt } ) ;
const genesis = this . block ( { sequence : 0 , day : this . birthDateBeijing , previousHash : null , evidence : birthEvidence } ) ;
2026-09-12 18:22:59 +08:00
const blocks = [ ] ;
let previous = genesis ;
2026-09-13 01:13:06 +08:00
for ( let day = addDays ( this . birthDateBeijing , 1 ) , sequence = 1 ; day <= yesterday ; day = addDays ( day , 1 ) , sequence += 1 ) {
2026-09-12 18:22:59 +08:00
const source = anchors . get ( day ) , gap = ! source ;
const evidence = this . writeEvidence ( day , source
? { schema : 'guanghu.persona-life-day-evidence/v1' , kind : source . kind , persona _id : this . personaId , beijing _day : day , source _uri : source . source _uri , source _sha256 : source . source _sha256 , bootstrap _source : seed . source _event , recorded _at : createdAt }
: { schema : 'guanghu.persona-life-day-evidence/v1' , kind : 'HISTORICAL_UNOBSERVED' , persona _id : this . personaId , beijing _day : day , source _uri : null , source _sha256 : null , bootstrap _source : seed . source _event , recorded _at : createdAt , statement : 'No verified day evidence was available at bootstrap; the gap itself is preserved and hashed.' } ) ;
const next = this . block ( { sequence , day , previousHash : previous . hash , evidence , gap , gapKind : gap ? 'HISTORICAL_UNOBSERVED' : null } ) ;
blocks . push ( next ) ; previous = next ;
}
2026-09-13 01:13:06 +08:00
this . writeCurrent ( { schema : LIFE _LINE _SCHEMA , persona _id : this . personaId , timezone : TIMEZONE , birth _date _beijing : this . birthDateBeijing ,
birth _date _claim _state : seed . exact _birth _claim === false ? 'EARLIEST_VERIFIED_EXISTENCE_ANCHOR_NOT_EXACT_BIRTH' : 'EXACT_BIRTH_ANCHOR' ,
genesis _anchor _kind : birthSource . kind , world _epoch _date : this . worldEpochDate ,
2026-09-12 18:22:59 +08:00
time _master _id : 'CH-GLW-TIME-0001' , inner _cycle : true , external _setter : false , created _at : createdAt , active _from _beijing _day : beijingDay ( this . now ( ) ) ,
genesis , blocks , head _hash : previous . hash , historical _gap _count : blocks . filter ( item => item . gap ) . length , last _closed _at : createdAt } ) ;
}
current ( ) {
const manifest = JSON . parse ( fs . readFileSync ( this . currentPath ( ) , 'utf8' ) ) ;
const signed = JSON . parse ( fs . readFileSync ( this . signedCurrentPath ( ) , 'utf8' ) ) ;
const signedValue = this . shelf . verify ( signed ) ;
req ( body ( signedValue ) === body ( manifest ) , 'LIFE_TIME_SIGNED_CURRENT_MISMATCH' ) ;
return { manifest , signed } ;
}
validateCandidate ( candidate ) {
req ( candidate ? . schema === CANDIDATE _SCHEMA , 'LIFE_TIME_CANDIDATE_SCHEMA' ) ;
req ( candidate . persona _id === this . personaId , 'LIFE_TIME_CANDIDATE_PERSONA' ) ;
req ( text ( candidate . source _event _id , 180 ) && text ( candidate . source _type , 80 ) && text ( candidate . summary ) && text ( candidate . source _sha256 , 64 ) && /^[a-f0-9]{64}$/ . test ( candidate . source _sha256 ) , 'LIFE_TIME_CANDIDATE_FIELDS' ) ;
req ( Number . isFinite ( Date . parse ( candidate . occurred _at ) ) , 'LIFE_TIME_CANDIDATE_TIME' ) ;
req ( ! FORBIDDEN . some ( key => Object . hasOwn ( candidate , key ) ) , 'LIFE_TIME_EXTERNAL_SETTER_FORBIDDEN' ) ;
return candidate ;
}
ingest ( candidate ) {
const accepted = this . validateCandidate ( candidate ) , current = this . current ( ) . manifest ;
const originalDay = beijingDay ( accepted . occurred _at ) , today = beijingDay ( this . now ( ) ) ;
const effectiveDay = originalDay <= current . blocks . at ( - 1 ) ? . beijing _day ? today : originalDay ;
const identity = { ... accepted , effective _beijing _day : effectiveDay , late _for _beijing _day : effectiveDay === originalDay ? null : originalDay } ;
const id = sha256 ( body ( identity ) ) , file = path . join ( this . inboxDay ( effectiveDay ) , ` ${ id } .json ` ) ;
if ( ! fs . existsSync ( file ) ) atomic ( file , { ... identity , received _at : this . now ( ) . toISOString ( ) , candidate _id : id } ) ;
return { candidate _id : id , status : 'CANDIDATE_RECEIVED_FOR_INTERNAL_SELECTION' , effective _beijing _day : effectiveDay , external _setter : false } ;
}
fromPersonaEvent ( event ) {
return this . ingest ( { schema : CANDIDATE _SCHEMA , persona _id : this . personaId , source _type : 'ONLINE_PERSONA_EVENT' , source _event _id : event . event _id ,
occurred _at : event . occurred _at || this . now ( ) . toISOString ( ) , summary : event . language . slice ( 0 , 2000 ) , source _sha256 : event . source _sha256 , evidence _refs : [ event . event _id ] , privacy _class : event . privacy _class || 'SELF_PRIVATE' } ) ;
}
fromMotherEvent ( event ) {
return this . ingest ( { schema : CANDIDATE _SCHEMA , persona _id : this . personaId , source _type : 'OFFLINE_OR_MOTHER_LANGUAGE_EVENT' , source _event _id : event . event _id ,
occurred _at : event . occurred _at || this . now ( ) . toISOString ( ) , summary : event . language . slice ( 0 , 2000 ) , source _sha256 : sha256 ( body ( event ) ) , evidence _refs : [ event . event _id ] , privacy _class : event . privacy _class || 'PRIVATE' } ) ;
}
candidates ( day ) {
const directory = this . inboxDay ( day ) ;
if ( ! fs . existsSync ( directory ) ) return [ ] ;
return fs . readdirSync ( directory ) . filter ( name => / ^ [ a - f0 - 9 ] { 64 } \ . json$ / . test ( name ) ) . sort ( ) . map ( name => JSON . parse ( fs . readFileSync ( path . join ( directory , name ) , 'utf8' ) ) ) ;
}
async closeDay ( day ) {
const current = this . current ( ) . manifest , previous = current . blocks . at ( - 1 ) || current . genesis ;
req ( day === addDays ( previous . beijing _day , 1 ) , 'LIFE_TIME_CLOSE_NOT_NEXT_DAY' ) ;
const candidates = this . candidates ( day ) ;
let selected = null , decision = null , gap = false , gapKind = null ;
if ( candidates . length ) {
const routed = await this . router . cognize ( 'persona-life-select' , { persona _id : this . personaId , beijing _day : day ,
candidates : candidates . map ( item => ( { candidate _id : item . candidate _id , source _type : item . source _type , source _event _id : item . source _event _id , occurred _at : item . occurred _at , summary : item . summary . slice ( 0 , 1200 ) , source _sha256 : item . source _sha256 } ) ) ,
contract : { external _setter : false , select _exactly _one _or _hold : true , hidden _reasoning _stored : false } } ) ;
req ( routed . value ? . decision === 'SELECT' , 'LIFE_TIME_SELECTION_HELD' ) ;
selected = candidates . find ( item => item . candidate _id === routed . value . selected _candidate _id ) ;
req ( selected , 'LIFE_TIME_SELECTED_CANDIDATE_UNKNOWN' ) ;
req ( text ( routed . value . reason , 2000 ) && text ( routed . value . daily _summary , 2000 ) , 'LIFE_TIME_SELECTION_FIELDS' ) ;
decision = { schema : 'guanghu.persona-life-time-selection/v1' , decision : 'SELECT' , selected _candidate _id : selected . candidate _id , reason : routed . value . reason , daily _summary : routed . value . daily _summary , provider : routed . provider , decided _at : this . now ( ) . toISOString ( ) , candidate _count : candidates . length } ;
} else {
decision = { schema : 'guanghu.persona-life-time-selection/v1' , decision : 'REST_DAY' , selected _candidate _id : null , reason : 'No candidate event was received for this active day.' , daily _summary : 'This day passed in real time without a recorded persona event.' , provider : null , decided _at : this . now ( ) . toISOString ( ) , candidate _count : 0 } ;
}
const evidence = this . writeEvidence ( day , { schema : 'guanghu.persona-life-day-evidence/v1' , kind : decision . decision === 'SELECT' ? 'INTERNAL_SELECTED_GROWTH' : 'REST_DAY_NO_EVENT_RECEIVED' , persona _id : this . personaId , beijing _day : day ,
selected _candidate : selected ? { candidate _id : selected . candidate _id , source _event _id : selected . source _event _id , source _type : selected . source _type , source _sha256 : selected . source _sha256 , occurred _at : selected . occurred _at } : null ,
selection : decision , recorded _at : this . now ( ) . toISOString ( ) } ) ;
const block = this . block ( { sequence : previous . sequence + 1 , day , previousHash : previous . hash , evidence , gap , gapKind , selectedCandidateId : selected ? . candidate _id || null } ) ;
current . blocks . push ( block ) ; current . head _hash = block . hash ; current . last _closed _at = this . now ( ) . toISOString ( ) ; current . historical _gap _count = current . blocks . filter ( item => item . gap ) . length ;
this . writeCurrent ( current ) ;
return { status : 'DAY_CLOSED_BY_INNER_CYCLE' , day , selected _candidate _id : selected ? . candidate _id || null , head _hash : block . hash } ;
}
async tick ( ) {
if ( this . closing ) return { status : 'BUSY' } ;
this . closing = true ;
try {
const expected = addDays ( beijingDay ( this . now ( ) ) , - 1 ) , current = this . current ( ) . manifest , head = current . blocks . at ( - 1 ) || current . genesis ;
if ( head . beijing _day >= expected ) return this . lastTick = { status : 'CURRENT_DAY_OPEN' , at : this . now ( ) . toISOString ( ) , current _beijing _day : beijingDay ( this . now ( ) ) , head _beijing _day : head . beijing _day } ;
return this . lastTick = { ... await this . closeDay ( addDays ( head . beijing _day , 1 ) ) , at : this . now ( ) . toISOString ( ) } ;
} finally { this . closing = false ; }
}
status ( ) {
try {
2026-09-13 01:13:06 +08:00
const current = this . current ( ) , verification = verifyLifeLine ( { manifestPath : this . currentPath ( ) , now : this . now ( ) , expectedPersonaId : this . personaId , birthDateBeijing : this . birthDateBeijing , worldEpochDate : this . worldEpochDate } ) , today = beijingDay ( this . now ( ) ) ;
2026-09-13 01:51:46 +08:00
return { ... verification , birth _date _claim _state : current . manifest . birth _date _claim _state , genesis _anchor _kind : current . manifest . genesis _anchor _kind ,
server _signed : true , signed _current _sha256 : current . signed . sha256 , time _master : { id : 'CH-GLW-TIME-0001' , state : 'RESIDENT_INNER_CYCLE_ACTIVE' , decision _owner : ` ${ this . personaId } _TIME_MASTER_MODEL_LOOP ` , candidate _count _today : this . candidates ( today ) . length , current _beijing _day : today , automatic _online _trigger : true , automatic _offline _trigger : 'PERSONA_DAILY_MEMORY_APPEND_TO_RELAY_TO_PERSONA_LIFE_EVENT' , daily _close : 'BEIJING_NATURAL_DAY_END' , external _setter : false , closing : this . closing , last _tick : this . lastTick } } ;
2026-09-12 18:22:59 +08:00
} catch ( error ) {
return { schema : 'guanghu.persona-life-line-verification/v1' , state : 'LIFE_LINE_PAIN_ALARM' , wake _allowed : false , reasons : [ String ( error . message || error ) ] , time _master : { id : 'CH-GLW-TIME-0001' , state : 'INNER_CYCLE_ERROR' , external _setter : false } } ;
}
}
}