payroll-api-isolated.mjs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. // Review first. Default only prepares private code; --run authorizes this fixed
  2. // diagnostic execution. Nothing here publishes a gateway or writes business data.
  3. import fs from 'node:fs';import assert from 'node:assert/strict';import {createHash,randomBytes} from 'node:crypto';
  4. import {patchFunctions,localCandidates,OPERATIONS_HELPERS} from '../../scripts/speed-round3-patch.mjs';
  5. const read=p=>fs.readFileSync(p,'utf8'),hash=s=>createHash('sha256').update(s).digest('hex');
  6. const privateDir='tmp/speed-round3-2026-11-10',outDir='perf/speed-round3-2026-11-10',savedDir='/Users/mac/.local/state/xiaoshu/cloud-releases/2026-11-10-speed-round3';
  7. const args=process.argv.slice(2),execute=args.includes('--run'),smoke=args.includes('--smoke'),monthArg=args.find(a=>a.startsWith('--month=')),month=monthArg?.slice(8)||'2026-11';
  8. if(args.some(a=>!['--run','--smoke'].includes(a)&&a!==monthArg)||!/^\d{4}-(0[1-9]|1[0-2])$/.test(month))throw Error('Usage: node --import ./tmp/admin-speed-round2-2026-11-10/env.mjs perf/speed-round3-2026-11-10/payroll-api-isolated.mjs [--run] [--smoke] [--month=YYYY-MM]');
  9. const baseline=JSON.parse(read(savedDir+'/admin-gateway.json')),patched=patchFunctions(baseline.code,localCandidates().operations,OPERATIONS_HELPERS),definitions=JSON.parse(read(savedDir+'/original-definitions.json'));
  10. assert.equal(definitions.length,39,'Expected the reviewed 39 baseline PostgreSQL definitions');
  11. const schema='speed3_api_'+randomBytes(6).toString('hex'),company='7pIbDBJmKx';
  12. const rewrite=s=>s.replace(/^BEGIN;\s*$/m,'').replace(/^COMMIT;\s*$/m,'').replaceAll('public.',schema+'.').replace(/(\bnspname\s*=\s*)'public'/g,"$1'"+schema+"'").replaceAll('xs-payroll-source-cache:','isolated-'+schema+'-source:').replaceAll('xs-payroll-version-cache:','isolated-'+schema+'-version:');
  13. const tenantTables=['CommonModel','CourseAppointment','LessonRecord','LessonRecordFact','MemoryPracticeRecord','TeachingCompensationRule','CourseTeachingProfile','CoachPayRate','ReviewReward','ReviewRewardCorrection','TeachingCourseCategory','LegacyDirectRun','LegacyDirectEvidence','LegacySyncConflict','PayrollCutoffDecision','PayrollCutoffApproval','PayrollReadDigest','PayrollLine','PayrollBatch','_User','Node','CourseBinding'];
  14. const globalTables=['PayrollReadDigestMeta','PayrollReadAuthority'],emptyTables=['PayrollVerifiedReadCache','PayrollReadChange','PayrollReadChangeScope','PayrollCommand','CourseCreditReservation','UserUserPoint','PayrollReconciliation'];
  15. const sql={functions:definitions.map(row=>rewrite(row.definition)).join(';\n'),bulk:rewrite(read('scripts/sql/payroll-source-bulk.sql')),index:rewrite(read('scripts/sql/payroll-source-index.sql')),snapshot:rewrite(read('scripts/sql/payroll-read-snapshot.sql')),conflict:rewrite(read('scripts/sql/payroll-cutoff-bulk.sql'))};
  16. // No public fallback: an omitted table/function must fail, never silently read or
  17. // write public data. Public relations occur only in the fixed setup copy statements.
  18. const config={schema,company,month,tenantTables,globalTables,emptyTables,sql,operatorId:'__OPERATOR_BOUND_AT_RUN__'};
  19. function runtimeSupport(){
  20. let activeVariant='',inFlight=new Set(),queries=[],policyRows=new Map(),unexpectedPolicies=new Set();
  21. const quote=name=>'"'+name.replaceAll('"','""')+'"';
  22. const settings=async tx=>{await tx.none('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ');await tx.none('SET LOCAL search_path='+quote(config.schema)+',pg_catalog');await tx.none("SET LOCAL statement_timeout='120s'; SET LOCAL jit=off");};
  23. const track=promise=>{inFlight.add(promise);promise.then(()=>inFlight.delete(promise),()=>inFlight.delete(promise));return promise;};
  24. const dbProxy=(db,inTransaction=false)=>new Proxy({},{get(_target,name){
  25. if(name==='tx')return callback=>track(db.tx(async tx=>{await settings(tx);return callback(dbProxy(tx,true));}));
  26. if(!['query','one','oneOrNone','any','many','none'].includes(name))throw Error('Unexpected diagnostic DB method '+String(name));
  27. return(sql,args)=>{
  28. if(/\bpublic\s*\./i.test(sql)||/\bsearch_path\b/i.test(sql))throw Error('Gateway attempted to escape isolated schema');
  29. if(/^SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY$/i.test(sql)){if(!inTransaction)throw Error('Missing explicit read snapshot');return db.none(sql);}
  30. if(!/^\s*(SELECT|WITH)\b/i.test(sql))throw Error('Non-read gateway SQL rejected');
  31. const started=Date.now();
  32. const job=inTransaction?db[name](sql,args):db.tx(async tx=>{await settings(tx);return tx[name](sql,args);});
  33. return track(job.then(value=>{if(sql.includes('xs_payroll_read_snapshot')&&!value?.result)throw Error('Coherent snapshot unexpectedly fell back');queries.push({method:name,ms:Date.now()-started,kind:sql.includes('xs_payroll_read_snapshot')?'coherent-snapshot':sql.includes('xs_payroll_sources_cached')?'sources-cold':sql.includes('xs_payroll_cutoff_snapshot_fast')?'cutoff-cold':sql.includes('xs_payroll_preview_cache')?'preview-cache':sql.includes('xs_payroll_read_users')?'thin-users':'other',rows:Array.isArray(value)?value.length:1});return value;}));
  34. };
  35. }});
  36. const isolatedDb=dbProxy(Psql);
  37. const sharedPolicies=async(original,className,context,input,limit=1000)=>{const key=className+':'+limit;if(!policyRows.has(key)){unexpectedPolicies.add(key);policyRows.set(key,await original(className,context,input,limit));}return policyRows.get(key);};
  38. const context=()=>({roles:['ops-manager'],company:{id:config.company},current:{id:config.operatorId},isSuperAdmin:false});
  39. async function setup(){return Psql.tx(async tx=>{
  40. await tx.none('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ');await tx.none('CREATE SCHEMA '+quote(config.schema));await tx.none('REVOKE ALL ON SCHEMA '+quote(config.schema)+' FROM PUBLIC');await tx.none('SET LOCAL search_path='+quote(config.schema)+',pg_catalog');await tx.none("SET LOCAL statement_timeout='120s'; SET LOCAL jit=off");
  41. for(const table of config.tenantTables)await tx.none('CREATE TABLE '+quote(table)+' AS SELECT * FROM public.'+quote(table)+' WHERE company=$1'+(table==='CommonModel'?' AND "modelId" IN (54,59,60)':''),[config.company]);
  42. for(const table of config.globalTables)await tx.none('CREATE TABLE '+quote(table)+' AS SELECT * FROM public.'+quote(table));
  43. for(const table of config.emptyTables)await tx.none('CREATE TABLE '+quote(table)+' (LIKE public.'+quote(table)+' INCLUDING ALL)');
  44. await tx.none('CREATE TABLE "Function" AS SELECT * FROM public."Function" WHERE path=\'xiaoshu/ops/gateway-v3\'');
  45. // Keep pg_get_viewdef meaningful to baseline stamps while freezing exact rows.
  46. await tx.none('CREATE TABLE "ReviewRewardIncomeSnapshot" AS SELECT * FROM public."ReviewRewardIncome" WHERE company=$1',[config.company]);await tx.none('CREATE VIEW "ReviewRewardIncome" AS SELECT * FROM "ReviewRewardIncomeSnapshot"');
  47. for(const table of ['CommonModel','CourseAppointment','LessonRecord','LessonRecordFact','MemoryPracticeRecord','TeachingCompensationRule','CoachPayRate','LegacySyncConflict','_User','Node','CourseBinding','PayrollLine','PayrollBatch'])await tx.none('CREATE UNIQUE INDEX ON '+quote(table)+' ("objectId")');
  48. // Teacher mappings SELECT p.* GROUP BY p.objectId: PostgreSQL recognizes the
  49. // functional dependency only for a primary key, not a standalone unique index.
  50. await tx.none('ALTER TABLE "CourseTeachingProfile" ADD PRIMARY KEY ("objectId")');
  51. await tx.none('CREATE UNIQUE INDEX ON "PayrollReadAuthority" (name); CREATE UNIQUE INDEX ON "PayrollReadDigestMeta" (kind); CREATE UNIQUE INDEX ON "PayrollReadDigest" (kind,company,"objectId"); CREATE INDEX ON "PayrollReadDigest" (company,kind COLLATE "C",hash COLLATE "C")');
  52. for(const table of ['CourseAppointment','LessonRecord','MemoryPracticeRecord'])await tx.none('CREATE INDEX ON '+quote(table)+' (company,id)');
  53. await tx.none('CREATE INDEX ON "CommonModel" (company,"modelId","itemId","objectId"); CREATE INDEX ON "CommonModel" (company,"modelId",("generalId"::text)); CREATE INDEX ON "CommonModel" (company,"modelId","generalId")');
  54. // Saved definitions are alphabetical, not dependency sorted. Defer body lookup
  55. // until all definitions exist; copied command rowtypes are present but unused.
  56. await tx.none('SET LOCAL check_function_bodies=off');await tx.none(config.sql.functions);await tx.none('SET LOCAL check_function_bodies=on');
  57. // Static copied digest only. This diagnostic DOES NOT validate source writes,
  58. // digest triggers, or their concurrency; no copied business source is mutated.
  59. await tx.none('CREATE OR REPLACE FUNCTION xs_payroll_digest_ready() RETURNS boolean LANGUAGE sql STABLE AS $$ SELECT true $$');
  60. await tx.none('UPDATE "PayrollReadAuthority" SET hash=md5(pg_get_functiondef(to_regprocedure(name)))');
  61. await tx.any(config.sql.bulk);await tx.any(config.sql.index);await tx.any(config.sql.snapshot);await tx.any(config.sql.conflict);
  62. for(const table of [...config.tenantTables,...config.globalTables,...config.emptyTables,'ReviewRewardIncomeSnapshot','PayrollSourceIndex','PayrollSourceIndexMeta'])await tx.none('ANALYZE '+quote(table));
  63. const guards=await tx.one('SELECT xs_payroll_authority_ready() authority,xs_payroll_digest_ready() digest,xs_payroll_source_index_ready() source_index,xs_payroll_read_snapshot_authority_ready() snapshot');
  64. if(Object.values(guards).some(value=>value!==true))throw Error('Clone readiness check failed');
  65. return {guards,copiedTables:config.tenantTables.length+config.globalTables.length+config.emptyTables.length+2,staticDigestOnly:true};
  66. });}
  67. async function clearCache(){await Promise.allSettled([...inFlight]);return Psql.tx(async tx=>{await settings(tx);await tx.none('TRUNCATE "PayrollVerifiedReadCache"');const row=await tx.one('SELECT count(*)::int n FROM "PayrollVerifiedReadCache"');if(row.n!==0)throw Error('Cache not cold');return row.n;});}
  68. async function run(input){
  69. if(input.action==='setup')return setup();
  70. if(input.action==='drop'){await Promise.allSettled([...inFlight]);await Psql.none('DROP SCHEMA IF EXISTS '+quote(config.schema)+' CASCADE');return {dropped:true};}
  71. if(input.action!=='pair'||!['AB','BA'].includes(input.order))throw Error('Unknown fixed diagnostic action');
  72. policyRows=new Map();unexpectedPolicies=new Set();const ctx=context(),params={month:config.month,summaryOnly:false,displayOnly:false};
  73. // Use identical actual Parse objects for both variants, outside both timers.
  74. for(const [name,limit] of [['TeachingCourseCategory',1000],['CourseTeachingProfile',2000]])policyRows.set(name+':'+limit,await baselineApi.originalPolicies(name,ctx,params,limit));
  75. const results=[];
  76. for(const variant of input.order==='AB'?['baseline','candidate']:['candidate','baseline']){
  77. const coldBefore=await clearCache();activeVariant=variant;queries=[];const at=Date.now();
  78. try{
  79. const output=await (variant==='baseline'?baselineApi:candidateApi).overview(ctx,params),serviceMs=Date.now()-at;
  80. if(output.sourceStatus?.cacheHit!==false)throw Error('Expected explicitly cold result');
  81. if(output.sourceStatus?.sync?.state==='error')throw Error('Cutoff query failed; timings are invalid');
  82. if(variant==='candidate'&&!queries.some(q=>q.kind==='coherent-snapshot'))throw Error('Candidate did not exercise coherent snapshot');
  83. results.push({variant,serviceMs,queries:[...queries],coldBefore,output});
  84. }finally{await clearCache();}
  85. }
  86. if(unexpectedPolicies.size)throw Error('Unprefetched Parse policy reads: '+[...unexpectedPolicies].join(','));
  87. return {results,coldAfter:0};
  88. }
  89. return {isolatedDb,sharedPolicies,run};
  90. }
  91. function factory(code){return `(()=>{${code.replace(/\bPsql\b/g,'runtime.isolatedDb')}\nconst originalPolicies=newClassObjects;newClassObjects=(...args)=>runtime.sharedPolicies(originalPolicies,...args);return{overview:payrollOverviewData,originalPolicies};})()`;}
  92. function sourceFor(operatorId){return 'const config='+JSON.stringify({...config,operatorId})+';\nconst runtime=('+runtimeSupport.toString()+')();\nconst baselineApi='+factory(baseline.code)+';\nconst candidateApi='+factory(patched.code)+';\nasync function run(input){return runtime.run(input);}';}
  93. const normalize=value=>Array.isArray(value)?value.map(normalize):value&&typeof value==='object'?Object.fromEntries(Object.keys(value).sort().filter(key=>!['refreshedAt','lagSeconds','cacheHit'].includes(key)).map(key=>[key,normalize(value[key])])):value;
  94. const reportContext='Cold round-2 production admin gateway baseline versus selective round-3 candidate on one static real-data clone. Ten sequential pairs alternate AB/BA. Each SQL operation uses clone-only search_path and RR; explicit candidate transactions preserve their single-snapshot semantics. TeachingCourseCategory and CourseTeachingProfile Parse reads/schema lookups are prefetched and shared within each pair OUTSIDE service timers. Service time also excludes setup, cache clearing, authentication and transport; pairRequestMs includes those per-pair prefetches/clears and transport for BOTH variants. This is not full API user-experience timing. ReviewRewardIncome is frozen as an equivalent copied-row view. All clone result caches are emptied before and after every variant. Digest readiness is stubbed for an unchanged copied digest: no source-write/trigger acceptance is claimed. Operator-bound manager projection, not manager-login acceptance.';
  95. fs.mkdirSync(privateDir,{recursive:true,mode:0o700});new Function(sourceFor(config.operatorId));
  96. fs.writeFileSync(privateDir+'/payroll-api-isolated-prepared.js',sourceFor(config.operatorId),{mode:0o600});fs.writeFileSync(privateDir+'/payroll-api-isolated-patch-audit.json',JSON.stringify(patched.audit,null,2),{mode:0o600});
  97. if(!execute){console.log(JSON.stringify({prepared:true,willRun:false,rounds:smoke?1:10,month,script:privateDir+'/payroll-api-isolated-prepared.js',baselineHash:hash(baseline.code),candidateHash:hash(patched.code),reviewNote:'Run only after reviewing clone setup and baseline fidelity.'}));}
  98. else{
  99. const {withGateway,operator,parse}=await import('./server-util.mjs'),me=await operator();
  100. const live=await parse('/classes/Function/'+baseline.objectId);assert.equal(hash(live.code),hash(baseline.code),'Production gateway drifted; refresh reviewed baseline before running');
  101. const samples=[],rounds=smoke?1:10;let setupResult,removed=false;
  102. await withGateway(sourceFor(me.objectId),async call=>{try{
  103. setupResult=await call({action:'setup'});
  104. for(let round=0;round<rounds;round++){
  105. const order=round%2?'BA':'AB',started=performance.now(),pair=await call({action:'pair',order}),requestMs=Math.round(performance.now()-started);
  106. assert.equal(pair.coldAfter,0);const [a,b]=pair.results;assert.deepEqual(normalize(a.output),normalize(b.output),'Full overview output changed in round '+round);
  107. for(const row of pair.results){fs.writeFileSync(privateDir+'/payroll-api-isolated-'+round+'-'+row.variant+'.json',JSON.stringify(row),{mode:0o600});const sample={round,order,variant:row.variant,serviceMs:row.serviceMs,pairRequestMs:requestMs,outputHash:hash(JSON.stringify(normalize(row.output))),coldBefore:row.coldBefore,cacheHit:row.output.sourceStatus.cacheHit,queries:row.queries};samples.push(sample);console.log(JSON.stringify({round,order,variant:sample.variant,serviceMs:sample.serviceMs,outputHash:sample.outputHash,cacheHit:sample.cacheHit}));}
  108. }
  109. }finally{removed=(await call({action:'drop'})).dropped===true;}});
  110. const percentile=(values,p)=>values.toSorted((a,b)=>a-b)[Math.ceil(values.length*p)-1],summary=Object.fromEntries(['baseline','candidate'].map(variant=>{const rows=samples.filter(row=>row.variant===variant);return[variant,{samples:rows.length,p50Ms:percentile(rows.map(r=>r.serviceMs),.5),p75Ms:percentile(rows.map(r=>r.serviceMs),.75),p95Ms:percentile(rows.map(r=>r.serviceMs),.95)}];}));
  111. fs.writeFileSync(outDir+'/payroll-api-isolated'+(smoke?'-smoke':'')+'.json',JSON.stringify({context:reportContext,month,baselineHash:hash(baseline.code),candidateHash:hash(patched.code),setup:setupResult,removed,fullOutputEquality:true,normalization:['refreshedAt','lagSeconds','cacheHit'],summary,samples},null,2));console.log(JSON.stringify({summary,removed,fullOutputEquality:true}));
  112. }