import fs from 'node:fs';import path from 'node:path';import net from 'node:net';import {spawn} from 'node:child_process';import assert from 'node:assert/strict';import crypto from 'node:crypto';
const read=p=>JSON.parse(fs.readFileSync(p,'utf8'));
const state=read('C:/laragon/data/sgf-tempo/run/modular-regression-current.json'),tag=process.argv[2];
if(state.port!==55439||!/^[a-f0-9]{12}$/.test(tag??''))throw Error('Owned QA tag required');
const root=path.join(state.root,'private/settlement-page-'+tag),settings=path.join(root,'settings.json'),owned=read(settings),fixture=read(path.join(root,'settlement-fixture.json'));
if(owned.tag!==tag||owned.cluster!=='sgf_modular_regression_'+state.id)throw Error('Owned cluster mismatch');
const output=path.join(root,'settlement-http.json');if(fs.existsSync(output))throw Error('Completed HTTP proof preserved');
await new Promise((resolve,reject)=>{const p=net.createServer();p.once('error',()=>reject(Error('Proof port unavailable')));p.listen(8047,'127.0.0.1',()=>p.close(resolve));});
const log=fs.openSync(path.join(root,'settlement-http-server.log'),'a'),server=spawn('C:/laragon/tools/sgf-tempo/php-8.4.27/php.exe',['-S','127.0.0.1:8047',path.resolve('scripts/settlement-page-http-router.php')],{cwd:process.cwd(),env:{...process.env,SETTLEMENT_PAGE_QA_SETTINGS:settings},windowsHide:true,stdio:['ignore',log,log]});
let checks=0,auth;const jar=new Map(),check=(ok,why)=>{checks++;assert(ok,why);};
async function request(url,body,extra={}){
const headers={Accept:'application/json',Cookie:[...jar].map(([k,v])=>k+'='+v).join('; '),'X-SGF-Context':auth?.context??'',...extra};
if(body)Object.assign(headers,{'Content-Type':'application/json','X-CSRF-TOKEN':auth?.csrf??'','Idempotency-Key':crypto.randomUUID()},extra);
const r=await fetch('http://127.0.0.1:8047'+url,{method:body?'POST':'GET',headers,body:body?JSON.stringify(body):undefined,redirect:'manual',signal:AbortSignal.timeout(8000)});
for(const cookie of r.headers.getSetCookie()){const pair=cookie.split(';')[0],at=pair.indexOf('=');jar.set(pair.slice(0,at),pair.slice(at+1));}
const text=await r.text();let data;try{data=JSON.parse(text);}catch{}return {status:r.status,data,text,headers:r.headers};
}
try{
for(let n=0;n<50&&!auth;n++){try{const r=await request('/qa/settlement-enter');if(r.status===200)auth=r.data;}catch{}if(!auth)await new Promise(r=>setTimeout(r,100));}
check(Boolean(auth),'Owned QA entry');
const first=await request('/api/supplier-payments');check(first.status===200,'Actual kernel exposes bounded ledger');
for(const key of ['payments','allocation_proposals','allocations','events'])check(first.data[key].length===25,'Bounded HTTP window '+key);
const next=await request('/api/supplier-payments?payments_page=2&allocation_proposals_page=2&allocations_page=2&payment_events_page=2');
check(next.status===200,'Independent next windows');
for(const key of ['payments','allocation_proposals','allocations','events'])check(!next.data[key].some(row=>first.data[key].some(old=>old.id===row.id)),'Disjoint HTTP window '+key);
const large=await request('/api/supplier-payments?payment_per_page=100');check(large.status===200&&large.data.payments.length===100&&large.data.allocations.length===100,'100 rows enforced by server');
const focus=await request('/api/supplier-payments?payment='+fixture.payment_id+'&allocations_page=3');
check(focus.status===200&&focus.data.payments.length===1&&focus.data.payments[0].unallocated_minor===41&&focus.data.reversals.length===1,'Full-history amount and old reversal beyond page one');
const filtered=await request('/api/supplier-payments?payment_state=rejected');check(filtered.status===200&&filtered.data.settlement_pagination.payments.total===333&&filtered.data.allocations.length===0,'Root state constrains child windows before count');
for(const query of ['payments_page=0','payment_per_page=500','payment_state=paid','payment%5Bx%5D=1','payment=0','company_id=999'])check((await request('/api/supplier-payments?'+query)).status===422,'Invalid or injected filter refused');
check((await request('/api/supplier-payments?payment=9223372036854775807')).status===403,'Unavailable payment refused uniformly');
check((await request('/api/supplier-payments',undefined,{'X-SGF-Context':'obsolete'})).status===409,'Current context required');
const body={payment_id:fixture.payment_id,decision:'confirmed',evidence_reference:'SIM-HTTP-READONLY',reason:'Read page cannot grant a payment decision.'};
check((await request('/api/supplier-payments/payment-decide',body)).status===403,'Read actor cannot decide payment');
check((await request('/api/supplier-payments/payment-decide',body,{'X-CSRF-TOKEN':''})).status===419,'Real CSRF middleware enforced');
const html=await request('/supplier-payments?source='+fixture.source_ids[0]+'&payment_state=confirmed&payments_page=2');
check(html.status===200&&html.text.includes('Paginação de pagamentos')&&html.text.includes('Paginação de eventos de pagamentos'),'HTML independent pagers');
check(html.text.includes('name="source" value="'+fixture.source_ids[0]+'"')&&html.text.includes('payment_state=confirmed'),'Links and forms preserve document and ledger filters');
const old=await request('/supplier-payments?payment='+fixture.payment_id+'&allocations_page=3');
check(old.status===200&&old.text.includes('Conferir origem e reconhecimento')&&old.text.includes('Consultar outros pagamentos'),'Old allocation traces to origin and parent payment');
const xss=await request('/supplier-payments?payment_q='+encodeURIComponent(''));
check(xss.status===200&&!xss.text.includes('')&&xss.text.includes('<script>ledger_http_xss</script>'),'Search escaped');
check(first.headers.get('cache-control').includes('no-store'),'Private HTTP response');
const report={passed:true,checks,real_http_kernel:true,csrf_enforced:true,read_only_actor:true,visual_review:false,browser_interactions_tested:false};fs.writeFileSync(output,JSON.stringify(report,null,2)+'\n');console.log(JSON.stringify(report));
}finally{if(server.exitCode===null){server.kill();await new Promise(resolve=>server.once('exit',resolve));}fs.closeSync(log);}