import fs from 'node:fs';import path from 'node:path';import net from 'node:net';import assert from 'node:assert/strict';import crypto from 'node:crypto';import {spawn} from 'node:child_process'; const read=p=>JSON.parse(fs.readFileSync(p,'utf8')),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 cost fixture required');const root=path.join(state.root,'private/ledger-'+tag),g=read(path.join(root,'cost-fixture.json')),out=path.join(root,'cost-http.json');if(fs.existsSync(out)||!fs.existsSync(path.join(root,'cost-http-baseline.json')))throw Error('Fresh HTTP proof with private baseline required'); await new Promise((resolve,reject)=>{const s=net.createServer();s.once('error',reject);s.listen(8047,'127.0.0.1',()=>s.close(resolve));});const log=fs.openSync(path.join(root,'cost-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/ledger-cost-http-router.php')],{windowsHide:true,stdio:['ignore',log,log],env:{...process.env,LEDGER_QA_SETTINGS:path.join(root,'settings.json')}}); let checks=0;const ok=(v,m)=>{checks++;assert(v,m);};const client=()=>{const jar=new Map();let auth;const request=async(url,body,key=crypto.randomUUID(),extra={})=>{const r=await fetch('http://127.0.0.1:8047'+url,{method:body?'POST':'GET',headers:{Accept:'application/json',Cookie:[...jar].map(([k,v])=>k+'='+v).join('; '),'X-SGF-Context':auth?.context??'',...(body?{'Content-Type':'application/json','X-CSRF-TOKEN':auth?.csrf??'','Idempotency-Key':key}:{}),...extra},body:body?JSON.stringify(body):undefined,redirect:'manual',signal:AbortSignal.timeout(15000)});for(const c of r.headers.getSetCookie()){const p=c.split(';')[0],i=p.indexOf('=');jar.set(p.slice(0,i),p.slice(i+1));}const text=await r.text();let data;try{data=JSON.parse(text);}catch{}return{status:r.status,data,text,headers:r.headers};};return{request,enter:async role=>{const r=await request('/qa/cost-enter/'+role);ok(r.status===200,'Owned '+role+' entry');auth=r.data;ok(jar.has('sgf_cost_'+tag)&&!jar.has('XSRF-TOKEN'),'Separate session cookie and shared CSRF preserved');},module:async module=>{const old=auth.context,r=await request('/api/context',{...g.scope,module_id:module});ok(r.status===200,'Explicit module transition');auth={csrf:r.data.csrf,context:r.data.context.token};return old;}};}; const api='/api/general-ledger/costs?order_id='+g.order_id,html='/general-ledger/costs?order_id='+g.order_id;const command=async(c,a,b,key)=>{const r=await c.request('/api/general-ledger/'+a,b,key);ok(r.status===200,a+' '+r.status+' '+(r.data?.message??''));return r.data;}; try{ const p=client(),v=client(),r=client(),tech=client();let ready=false;for(let n=0;n<50&&!ready;n++){try{ready=(await r.request(api)).status===401;}catch{}if(!ready)await new Promise(res=>setTimeout(res,100));}ok(ready,'Unauthenticated cost read refused');await p.enter('prepare');await v.enter('validate');await r.enter('read');await tech.enter('technician');ok((await tech.request(api)).status===403,'Technical recording rights do not grant accounting view'); let w=await r.request(api,undefined,undefined,{'X-Cost-QA-Proof':'read'});ok(w.status===200&&w.data.cost.source.principal_minor===27500&&w.data.book.journals.length===0,'Actual selected cost275 not automatically posted');const mode=read(path.join(root,'cost-http-read.json'));ok(mode.transaction.isolation==='repeatable read'&&mode.transaction.read_only==='on'&&mode.writer_locks===0,'Source, book and period read coherently without writer locks'); let view=await p.request(html);ok(view.status===200&&view.text.includes('data-action="effect-prepare"')&&view.text.includes('275,00 MZN'),'Preparer sees exact impact and individual capture');fs.writeFileSync(path.join(root,'cost-preparer.html'),view.text);view=await r.request(html);ok(view.status===200&&!view.text.includes('data-action='),'Reader has no decision forms'); for(const query of ['order_id=999999','order_id=0','order_id%5Bx%5D=1','order_id='+g.order_id+'&company_id=9'])ok([403,422].includes((await r.request('/api/general-ledger/costs?'+query)).status),'Invalid/cross-context order refused'); const body={effect_type:'workshop.direct_cost.validated',source_id:g.order_id,journal_on:'2026-10-29',period_key:'QA-2026-10',rule_key:'QA-DIRECT-COST',reason:'Actual selected complete275, technical and cost approvals preserved.'}; ok((await p.request('/api/general-ledger/effect-prepare',body,undefined,{'X-CSRF-TOKEN':''})).status===419,'Real CSRF validation');ok((await r.request('/api/general-ledger/effect-prepare',body)).status===403,'Reader cannot prepare');ok((await p.request('/api/general-ledger/period-prepare',{period_key:'QA-2026-10',reason:'Unposted selected cost blocks completeness.'})).status===409,'No closing before selected cost posting'); const old=await p.module('finance');ok((await p.request(api)).status===409,'Wrong module refuses cost workspace');await p.module('accounting');ok((await p.request('/api/general-ledger/effect-prepare',body,undefined,{'X-SGF-Context':old})).status===409,'Stale context generation refused'); const key=crypto.randomUUID(),proposal=await command(p,'effect-prepare',body,key);ok((await command(p,'effect-prepare',body,key)).replayed,'Capture retry recovers original receipt');view=await v.request(html);ok(view.status===200&&view.text.includes('data-action="validate"')&&view.text.includes('QA-CUSTO-DIRECTO')&&view.text.includes('QA-COMPENSACAO-ENSAIO'),'Independent reviewer sees both artificial lines');fs.writeFileSync(path.join(root,'cost-reviewer.html'),view.text); const review={proposal_id:proposal.proposal_id,revision:proposal.revision,decision:'approved',reason:'Independent exact selected cost and source version conference.'};ok((await p.request('/api/general-ledger/validate',review)).status===403,'Preparer lacks independent review authority');ok((await v.request('/api/general-ledger/validate',{...review,revision:'0'.repeat(64)})).status===409,'Old capture revision refused');await command(v,'validate',review); const post={proposal_id:proposal.proposal_id,revision:proposal.revision,reason:'Apply reviewed selected cost once without changing original evidence.'},postKey=crypto.randomUUID(),j=await command(p,'post',post,postKey);ok((await command(p,'post',post,postKey)).replayed,'Posting retry receipt');ok((await command(p,'post',post)).journal_id===j.journal_id,'Other key returns same source journal');ok((await p.request('/api/general-ledger/effect-prepare',body)).status===409,'No second recognition of same selected perimeter'); w=await r.request(api);ok(w.status===200&&w.data.book.journals.length===1&&w.data.book.lines.length===2&&w.data.book.events.length===3,'Focused book and three decision/application events');view=await r.request('/general-ledger');ok(view.status===200&&view.text.includes('/general-ledger/costs?order_id='+g.order_id),'Generic diary recognises cost source'); const dates='from_on=2026-10-01&to_on=2026-10-31';let report=await r.request('/api/general-ledger/trial-balance?'+dates);ok(report.status===200&&report.data.totals.debit_minor===27500&&report.data.totals.credit_minor===27500,'Trial balance reconciles same writer');const account=g.accounts['QA-CUSTO-DIRECTO'];report=await r.request('/api/general-ledger/account-ledger?'+dates+'&account_id='+account);ok(report.status===200&&report.data.closing_net_minor===27500&&report.data.rows[0].cost_order_id===String(g.order_id),'Account ledger retains typed cost origin');view=await r.request('/general-ledger/account-ledger?'+dates+'&account_id='+account);ok(view.status===200&&view.text.includes('/general-ledger/costs?order_id='+g.order_id),'Account ledger links back to actual cost conference'); const period=await command(p,'period-prepare',{period_key:'QA-2026-10',reason:'Capture all composed current cost facts and exact journal.'});await command(v,'period-validate',{check_id:period.check_id,revision:period.revision,decision:'approved',reason:'Independent complete cost period evidence review.'});const close={check_id:period.check_id,revision:period.revision,reason:'Explicit synthetic cost period close.'},closeKey=crypto.randomUUID(),closed=await command(p,'period-close',close,closeKey);ok((await command(p,'period-close',close,closeKey)).replayed,'Close request retry is idempotent');ok((await command(p,'period-close',close)).closure_id===closed.closure_id,'Other key does not duplicate closure');view=await p.request(html);ok(view.status===200&&!view.text.includes('data-action='),'Closed period hides new decision forms'); fs.writeFileSync(out,JSON.stringify({passed:true,checks,actual_http:true,actual_csrf:true,principal_minor:27500,journals:1,lines:2,source_events:3,report_reconciled:true,typed_cost_origin_link:true,one_reviewed_qa_close:true,repeated_keys_and_source_no_duplicates:true,read_only_workspace:true,writer_locks:0,visual_review:false},null,2)+'\n');console.log(JSON.stringify({passed:true,checks,journals:1,lines:2})); }finally{server.kill();fs.closeSync(log);}