← back to Tk11438 Postgres Migration

verification/ken-rollout/independent-check.cjs

57 lines

// Independent read-only verifier. No service starts/restarts, wrappers, or sends.
const fs=require('fs'),cp=require('child_process'),assert=require('assert/strict'),crypto=require('crypto'),os=require('os');
const base=__dirname,dir='/Users/macstudio3/Projects/Ken/kalshi-dash';
const parse=require('/Users/macstudio3/Projects/patterndesignlab/node_modules/dotenv').parse;
const {Client}=require(dir+'/node_modules/pg');
const deps='/Users/macstudio3/.npm-global/lib/node_modules/pm2/node_modules/';
const axon=require(deps+'pm2-axon'),rpc=require(deps+'pm2-axon-rpc');
const read=p=>fs.readFileSync(p),json=p=>JSON.parse(read(p)),hash=b=>crypto.createHash('sha256').update(b).digest('hex');
const run=(cmd,args)=>cp.execFileSync(cmd,args,{encoding:'utf8',timeout:15000,maxBuffer:8e6});
const proof={task_id:'TK-11438',owner:'codex-run-11438-verifier',risk:'R4',timestamp:new Date().toISOString(),assertions:[],commands:[],application_mutations:0,email_sends:0};
function pass(name,detail){proof.assertions.push({name,verdict:'PASS',detail});}
async function db(url,fn){const c=new Client({connectionString:url,application_name:'TK11438-independent-verifier',connectionTimeoutMillis:3000,options:'-c default_transaction_read_only=on -c statement_timeout=5000'});try{await c.connect();return await fn(c);}finally{await c.end().catch(()=>{});}}
async function monitor(){return await new Promise((resolve,reject)=>{const s=axon.socket('req'),c=new rpc.Client(s);const t=setTimeout(()=>{s.close();reject(Error('PM2 RPC timeout'));},30000);s.on('error',e=>{clearTimeout(t);s.close();reject(e);});s.connect('/Users/macstudio3/.pm2/rpc.sock');c.call('getMonitorData',{},(err,res)=>{clearTimeout(t);s.close();err?reject(Error('PM2 RPC failed')):resolve(res);});});}
(async()=>{
 const baseline=json(base+'/baseline.json'),receipt=json(base+'/private/receipt.json');
 const env=parse(read(dir+'/.env')),services=await monitor(),matches=services.filter(s=>s.name==='ken');assert.equal(matches.length,1);
 const live=matches[0],e=live.pm2_env;assert.equal(e.pm_cwd,dir);assert.equal(e.pm_exec_path,dir+'/start.sh');assert.equal(e.status,'online');assert.notEqual(live.pid,baseline.pid);
 proof.pid=live.pid;proof.pm_id=e.pm_id;proof.commands.push('direct existing PM2 getMonitorData RPC');pass('Ken unique identity and online restart',{before_pid:baseline.pid,after_pid:live.pid,restart_time:e.restart_time});
 const saved=json('/Users/macstudio3/.pm2/dump.pm2').filter(s=>s.name==='ken');assert.equal(saved.length,1);assert.equal(saved[0].pm_cwd,dir);assert.equal(saved[0].pm_exec_path,dir+'/start.sh');
 for(const key of ['DATABASE_URL','KEN_DATABASE_URL']){
  for(const v of [e[key],e.env[key],saved[0][key],saved[0].env[key]])assert.equal(v,env[key]);
  const expected=baseline.identities.find(x=>x.key===key);
  for(const [source,url]of [['dotenv',env[key]],['saved dump',saved[0][key]]]){
   assert.equal(new URL(url).searchParams.get('host'),'/tmp');
   const id=await db(url,async c=>(await c.query("SELECT current_database() database,current_user role,inet_client_addr()::text addr,current_setting('transaction_read_only') readonly")).rows[0]);
   assert.equal(id.database,expected.database);assert.equal(id.role,expected.role);assert.equal(id.addr,null);assert.equal(id.readonly,'on');pass(key+' '+source+' Unix socket identity',id);
  }
 }
 pass('Four effective/saved URL fields agree with dotenv',true);
 const settings=await db(env.DATABASE_URL,async c=>(await c.query("SELECT config->>'safe_mode' safe_mode,config->>'trading_on' trading_on,config->>'kalshi_env' kalshi_env,md5(config::text) config_hash FROM risk_state ORDER BY updated_at DESC LIMIT 1")).rows[0]);
 const ksettings=await db(env.KEN_DATABASE_URL,async c=>(await c.query("SELECT key,md5(value::text) value_hash FROM ken_config WHERE key IN ('live_run','trade_config') ORDER BY key")).rows);
 for(const key of ['safe_mode','trading_on','kalshi_env'])assert.equal(settings[key],baseline.settings[key]);assert.deepEqual(ksettings,baseline.kenSettings);pass('Captured operational switches and Ken trading configuration unchanged',{settings,ksettings});proof.warnings=[{name:'Whole risk_state.config comparison',reason:'Baseline whole-config hash differs. Unchanged server.js lines 8027-8031 refresh weather_cache on scan. This is consistent with expected cache churn, but baseline omitted individual other keys, so exclusive cause and all-key preservation cannot be proved retroactively.',baseline_hash:baseline.settings.config_hash,current_hash:settings.config_hash}];
 const lan=Object.values(os.networkInterfaces()).flat().find(x=>x.family==='IPv4'&&!x.internal&&x.address.startsWith('192.168.'));assert(lan);
 for(const route of ['/api/setup','/api/trading/history'])for(const auth of ['none','invalid','valid']){
  const headers=auth==='none'?{}:{authorization:'Basic '+Buffer.from(auth==='valid'?(env.ADMIN_USER||'admin')+':'+env.ADMIN_PASSWORD:'TK11438-verifier:invalid').toString('base64')};
  const response=await fetch('http://'+lan.address+':7810'+route,{headers,signal:AbortSignal.timeout(10000)}),body=await response.text();
  assert.equal(response.status,auth==='valid'?200:401);if(auth==='valid'){const obj=JSON.parse(body);assert(!obj.error);}
  pass('LAN '+route+' '+auth+' auth',{status:response.status,bytes:body.length,body_sha256:hash(body)});
 }
 let sockets='';try{sockets=run('lsof',['-nP','-a','-p',String(live.pid),'-iTCP']);}catch(err){assert.equal(err.status,1);sockets=err.stdout||'';}
 assert(!/:5432\b/.test(sockets));pass('Ken PID has no TCP 5432 socket',true);
 const unix=run('lsof',['-nP','-a','-p',String(live.pid),'-U']);assert(unix.includes('unix'));pass('Ken PID has active Unix sockets',{lines:unix.trim().split('\n').length-1,sha256:hash(unix)});
 const activity=await db('postgresql:///postgres?host=/tmp',async c=>(await c.query("SELECT datname,usename,client_addr::text,client_port,pid FROM pg_stat_activity WHERE datname IN ('ken','bertha_betting') AND application_name NOT LIKE 'TK11438%' ORDER BY datname,pid")).rows);
 for(const name of ['ken','bertha_betting'])assert(activity.some(x=>x.datname===name&&x.client_addr===null));pass('Application database sessions use Unix sockets',activity);
 for(const key of ['DATABASE_URL','KEN_DATABASE_URL']){const bad=new URL(env[key]);bad.searchParams.set('host',base+'/missing-socket');let code;try{await db(bad.toString(),async()=>{});}catch(e){code=e.code;}assert.equal(code,'ENOENT');pass(key+' missing socket fails closed',{code});}
 assert.equal(hash(read(dir+'/server.js')),baseline.source_hash);assert.equal(hash(read(dir+'/server.js')),receipt.source_hash);pass('Server source unchanged',baseline.source_hash);
 const manifest=json('/Users/macstudio3/Projects/tk11438-postgres-migration/next-batch.json');
 assert.equal(read(dir+'/follow-the-winners-check.sh').toString(),read(base+'/private/wrapper.before').toString().replace(manifest.file_changes[0].old,manifest.file_changes[0].new));pass('Hourly wrapper exact approved one-line change; not executed',true);
 for(const name of ['env','wrapper','plist']){assert.equal(hash(read(base+'/private/rehearsal-'+name)),receipt.hashes[name]);pass(name+' rollback rehearsal restored exact prior bytes',true);}
 assert.equal(hash(read(dir+'/.env')),hash(read(base+'/private/env.after')));pass('Durable dotenv matches prepared bytes',true);
 const plistPath='/Users/macstudio3/Library/LaunchAgents/com.steve.ken-reconcile-canary.plist',plist=JSON.parse(run('plutil',['-convert','json','-o','-',plistPath]));
 assert.equal(hash(read(plistPath)),hash(read(base+'/private/plist.after')));assert.equal(new URL(plist.EnvironmentVariables.KEN_DATABASE_URL).searchParams.get('host'),'/tmp');assert.equal(plist.StartInterval,1800);assert.deepEqual(plist.ProgramArguments,['/opt/homebrew/bin/node',dir+'/scripts/reconcile-canary.mjs']);
 const launch=run('launchctl',['print','gui/'+process.getuid()+'/com.steve.ken-reconcile-canary']);assert.equal(new URL(launch.match(/KEN_DATABASE_URL => (\S+)/)[1]).searchParams.get('host'),'/tmp');assert(/last exit code = 0/.test(launch));pass('Reconciliation durable and loaded socket; interval preserved; exit zero',{interval:plist.StartInterval,last_exit:0});
 const log=read(plist.StandardOutPath).toString().trim().split('\n');assert(log.at(-1).includes('[reconcile-canary] OK'));assert(fs.statSync(plist.StandardOutPath).mtimeMs>=Date.parse(json(base+'/reloaded.json').at)-10000);pass('Reconciliation post-reload result',{last_line:log.at(-1),mtime:fs.statSync(plist.StandardOutPath).mtime.toISOString()});
 proof.commit=run('git',['-C','/Users/macstudio3/Projects/Ken','rev-parse','HEAD']).trim();proof.verdict='PASS';proof.status='complete';proof.skipped=[{name:'Hourly email-capable wrapper execution',verdict:'SKIP',reason:'Explicitly excluded from authorization; parent separately verifies SELECT-only signal module.'},{name:'Full-fleet TCP shutdown',verdict:'SKIP',reason:'Outside Ken batch.'},{name:'Live rollback execution',verdict:'SKIP',reason:'Healthy rollout; rollback reviewed and copies rehearsed, no induced production fault.'}];
})().catch(err=>{proof.verdict='FAIL';proof.status='partial';proof.error={name:err.name,code:err.code,message:String(err.message).replace(/postgres(?:ql)?:\/\/\S+/g,'[REDACTED_URL]')};}).finally(()=>{fs.writeFileSync(base+'/independent-verification.json',JSON.stringify(proof,null,2)+'\n');console.log(JSON.stringify(proof,null,2));if(proof.verdict!=='PASS')process.exitCode=1;});