prepare-infrastructure.mjs 3.5 KB

123456789101112131415
  1. import fs from 'node:fs';import path from 'node:path';import {createHash} from 'node:crypto';import {withGateway,parse} from './server-util.mjs';
  2. const root='/Users/mac/.local/state/xiaoshu/cloud-releases/2026-11-10-admin-speed-round2',mode=process.argv[2]||'prepare';fs.mkdirSync(root,{recursive:true,mode:0o700});
  3. const files=['payroll-incremental-reads.sql','payroll-source-fast.sql','payroll-cutoff-fast.sql','payroll-verified-preview.sql'],sql=files.map(f=>fs.readFileSync('scripts/sql/'+f,'utf8')).join('\n');
  4. const defSql="SELECT proname,pg_get_functiondef(p.oid) AS definition FROM pg_proc p JOIN pg_namespace n ON n.oid=p.pronamespace WHERE n.nspname='public' AND proname IN ('xs_payroll_sources','xs_payroll_source_version','xs_payroll_cutoff_snapshot','xs_payroll_cutoff_conflicts') ORDER BY proname";
  5. if(mode==='prepare'){
  6. if(fs.existsSync(root+'/infrastructure-receipt.json'))throw Error('Already installed; preserve original backup');
  7. const gateway=(await parse('/classes/Function?'+new URLSearchParams({where:JSON.stringify({path:'xiaoshu/ops/gateway-v3'}),limit:'2'}))).results;if(gateway.length!==1)throw Error('Ambiguous gateway');
  8. const definitions=await withGateway('async function run(){return Psql.query('+JSON.stringify(defSql)+');}',call=>call({}));
  9. fs.writeFileSync(root+'/original-gateway.json',JSON.stringify(gateway[0]),{mode:0o600});fs.writeFileSync(root+'/original-definitions.json',JSON.stringify(definitions),{mode:0o600});fs.writeFileSync(root+'/infrastructure.sql',sql,{mode:0o600});
  10. fs.writeFileSync(root+'/infrastructure-manifest.json',JSON.stringify({sqlHash:createHash('sha256').update(sql).digest('hex'),beforeGatewayHash:createHash('sha256').update(gateway[0].code).digest('hex')},null,2),{mode:0o600});console.log(JSON.stringify({prepared:true,definitions:definitions.length,files}));
  11. }else if(mode==='install'){
  12. const expected=JSON.parse(fs.readFileSync(root+'/original-definitions.json')),manifest=JSON.parse(fs.readFileSync(root+'/infrastructure-manifest.json'));if(createHash('sha256').update(sql).digest('hex')!==manifest.sqlHash)throw Error('Prepared SQL changed');
  13. async function run(p){if(p.action==='vacuum'){await Psql.none('VACUUM (ANALYZE) "PayrollReadDigest"');return true;}if(p.action!=='install')throw Error('Unknown release action');return Psql.tx(async tx=>{await tx.none("SET LOCAL lock_timeout='5s';SET LOCAL statement_timeout='120s';");const current=await tx.query(defSql);if(JSON.stringify(current)!==JSON.stringify(expected))throw Error('Original financial definitions changed');const running=await tx.one('SELECT count(*)::int AS count FROM "LegacyDirectRun" WHERE status=$1',['running']);if(running.count)throw Error('Synchronization must be idle');if((await tx.one("SELECT to_regclass('\"PayrollReadDigest\"') IS NOT NULL AS present")).present)throw Error('Derived infrastructure already exists');await tx.none(sql);const ready=await tx.one('SELECT xs_payroll_digest_ready() AS ready,(SELECT count(*)::int FROM "PayrollReadDigest") AS rows');if(!ready.ready)throw Error('Digest maintenance is not verified');return ready;});}
  14. const receipt=await withGateway('const expected='+JSON.stringify(expected)+';const sql='+JSON.stringify(sql)+';const defSql='+JSON.stringify(defSql)+';'+run.toString(),async call=>{const result=await call({action:'install'});fs.writeFileSync(root+'/infrastructure-receipt.json',JSON.stringify({at:new Date().toISOString(),...result,...manifest}),{mode:0o600});await call({action:'vacuum'});return result;});console.log(JSON.stringify({installed:true,...receipt}));
  15. }else throw Error('Use prepare or install');