Files
Investidor-V2/tools/portal-workflow-activation-browser.cjs
nala 287e991486
tests / ci (push) Waiting to run
oportunidades 7
2026-10-13 11:40:29 +02:00

93 lines
14 KiB
JavaScript

// Real Chromium HTTP actions for030000/040000 and crossed bootstrap2/13 only.
// Local human login/MFA; no credentials, codes, cookies or sensitive payloads in evidence.
const fs=require('node:fs'),path=require('node:path'),assert=require('node:assert/strict'),crypto=require('node:crypto');
const {chromium}=require('C:/Users/macbook/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright');
const ROOT=path.resolve(__dirname,'..'),PRIV=path.join(ROOT,'storage/app/private/investmoz-v2/portal-audit'),BASE='https://portal-investidor.test';
const REV='41caea0f900fe0705e84e065a65f9534ff3a585ee34098f860666f522875a3a5';
const manifest=JSON.parse(fs.readFileSync(path.join(ROOT,'outputs/investmoz-workflow-consolidated-20261011/manifesto.json')));
const backup=JSON.parse(fs.readFileSync(path.join(PRIV,'workflow-execution-target-backup.json')));
assert.equal(manifest.revision,REV);
const statusFile=path.join(PRIV,'workflow-activation-browser-status.json'),commandFile=path.join(PRIV,'workflow-activation-browser-command.json');
const resuming=process.argv.includes('--resume-profiles');
if(resuming){
const verification=JSON.parse(fs.readFileSync(path.join(PRIV,'workflow-activation-resources-verified.json')));
assert(verification.result==='passed'&&verification.resources_installed&&!verification.profiles_installed&&verification.revision===REV);
if(fs.existsSync(statusFile))fs.copyFileSync(statusFile,path.join(PRIV,'workflow-activation-browser-before-controller-recovery.json'));
}else assert(!fs.existsSync(statusFile),'Existing browser receipt: recover deliberately instead of starting another intervention');
const proof={at:new Date().toISOString(),revision:REV,origin:BASE,phase:'opening',navigation:[],assets:{},tls:[],errors:[],mixed:[],criteria:{},credentials_or_codes_recorded:false};
const write=()=>{proof.at=new Date().toISOString();fs.writeFileSync(statusFile,JSON.stringify(proof,null,2));};
const pending=[];let browser,contextA,pageA,contextB,pageB,current,done=false,lastCommand;
function events(p,label){
p.setDefaultTimeout(20000);
p.on('pageerror',e=>{proof.errors.push({session:label,path:new URL(p.url()).pathname,type:e.name});write();});
p.on('request',r=>{const u=new URL(r.url());if(u.protocol==='http:')proof.mixed.push({origin:u.origin,path:u.pathname});});
p.on('response',r=>{const u=new URL(r.url());if(u.origin!==BASE)return;const route=u.pathname;
if(r.request().isNavigationRequest()||r.request().method()==='POST')proof.navigation.push({session:label,path:route,method:r.request().method(),status:r.status(),location:r.headers().location?new URL(r.headers().location,BASE).pathname:null});
if(route.startsWith('/build/'))pending.push((async()=>{const digest=crypto.createHash('sha256').update(await r.body()).digest('hex');proof.assets[route]={status:r.status(),matches:digest===manifest.payload['public'+route],sha256:digest};})().catch(()=>{}));
if(r.request().isNavigationRequest())pending.push((async()=>{const t=await r.securityDetails();if(t)proof.tls.push({session:label,path:route,...t});})().catch(()=>{}));
});
}
async function actor(p){
const read=()=>p.evaluate(()=>{const el=document.getElementById('app'),script=document.querySelector('script[data-page="app"]');const text=script?.textContent??el?.getAttribute('data-page');return text?JSON.parse(text)?.props?.auth?.user?.id??null:null;});
let id=await read();
// The initial Inertia DOM payload stays guest after client-side login. Refresh the
// management GET with the SAME real authenticated session before identifying its actor.
if(id===null&&new URL(p.url()).pathname.startsWith('/management/')){await p.reload({waitUntil:'networkidle'});id=await read();}
return id;
}
async function getPlan(p,url){const r=await p.evaluate(async url=>{const r=await fetch(url,{headers:{Accept:'application/json'}});return {status:r.status,value:r.ok?await r.json():null};},url);assert.equal(r.status,200,'Authenticated preview unavailable');return r.value;}
function gate(){const ready=JSON.parse(fs.readFileSync(path.join(PRIV,'workflow-activation-technical-ready.json')));assert(ready.passed&&ready.revision===REV&&ready.worker_enabled===false);assert(Date.now()-Date.parse(backup.verified_at)<86400000);}
function matching(p,kind){assert.equal(p.target.database,'investemoz');assert.equal(Number(p.target.port),5432);assert.equal(p.code_hash,kind==='resources'?manifest.resources_fingerprint:manifest.profile_fingerprint);assert.equal(p.backupDate,backup.verified_at);assert(p.ready);assert.equal(p.migration,kind==='resources'?'2026_10_09_030000_extend_opportunity_resources':'2026_10_10_040000_create_portal_profile_authorization');if(kind==='profiles')assert(p.consistent&&p.dependencies.resources_030000&&p.dependencies.management_020000);}
async function inspect(p){
await Promise.allSettled(pending);proof.path=new URL(p.url()).pathname;proof.current_actor_id=await actor(p);
proof.runtime=await p.evaluate(()=>({secureContext:isSecureContext,subtle:typeof crypto.subtle}));assert(proof.runtime.secureContext&&proof.runtime.subtle==='object');
proof.cookie_attributes=(await p.context().cookies(BASE)).map(c=>({name:c.name,domain:c.domain,path:c.path,secure:c.secure,httpOnly:c.httpOnly,sameSite:c.sameSite}));
assert(proof.cookie_attributes.filter(c=>c.name==='portal_investidor_https').every(c=>c.secure&&c.httpOnly&&c.domain==='portal-investidor.test'&&c.path==='/'&&c.sameSite==='Lax'));
assert.equal(proof.errors.length,0);assert.equal(proof.mixed.length,0);for(const a of Object.values(proof.assets))assert(a.status===200&&a.matches);
const login=await p.locator('#email').isVisible();proof.phase=login?'awaiting_local_login':proof.path==='/two-factor-challenge'?'awaiting_local_MFA':'authenticated_observation';
if(proof.path==='/management/opportunity-resources-install'&&!login){const v=await getPlan(p,proof.path);matching(v,'resources');proof.resources_preview=Object.fromEntries(['target','code_hash','hash','migration','installed','ready','backupDate'].map(k=>[k,v[k]]));}
if(proof.path==='/management/profile-install'&&!login){const v=await getPlan(p,proof.path);matching(v,'profiles');proof.profiles_preview=Object.fromEntries(['target','code_hash','hash','migration','installed','ready','consistent','backupDate','bootstrap','dependencies'].map(k=>[k,v[k]]));}
write();console.log(JSON.stringify({phase:proof.phase,path:proof.path,actor:proof.current_actor_id,resources:proof.resources_preview,profiles:proof.profiles_preview}));
}
(async()=>{
gate();browser=await chromium.launch({headless:false,executablePath:'C:/Users/macbook/AppData/Local/ms-playwright/chromium-1243/chrome-win64/chrome.exe'});
contextA=await browser.newContext({locale:'pt-MZ',viewport:{width:1280,height:900},ignoreHTTPSErrors:false});pageA=await contextA.newPage();events(pageA,'first_holder');current=pageA;
await pageA.goto(BASE+(resuming?'/management/profile-install':'/management/opportunity-resources-install'),{waitUntil:'networkidle'});await inspect(pageA);let observed=pageA.url();
while(!done){await new Promise(r=>setTimeout(r,1200));if(current.isClosed()){proof.phase='browser_closed';write();break;}
if(current.url()!==observed){observed=current.url();await inspect(current);}
if(!fs.existsSync(commandFile))continue;const command=JSON.parse(fs.readFileSync(commandFile));if(command.id===lastCommand)continue;lastCommand=command.id;assert.equal(command.revision,REV);
try{
if(command.action==='inspect')await inspect(current);
else if(command.action==='resources'){
gate();assert.equal(await actor(pageA),2);const v=await getPlan(pageA,'/management/opportunity-resources-install');matching(v,'resources');assert.equal(v.hash,command.preview_hash);assert(!v.installed);
await pageA.getByLabel('Confirmo a aplicação deste plano',{exact:true}).check();const wait=pageA.waitForResponse(r=>r.request().method()==='POST'&&new URL(r.url()).pathname==='/management/opportunity-resources-install');
await pageA.getByRole('button',{name:'Aplicar plano de gestão',exact:true}).click();assert([302,303].includes((await wait).status()));await pageA.waitForLoadState('networkidle');await pageA.reload({waitUntil:'networkidle'});
const after=await getPlan(pageA,'/management/opportunity-resources-install');matching(after,'resources');assert(after.installed);proof.criteria.resources_authenticated_UI_installed=true;
await pageA.goto(BASE+'/management/profile-install',{waitUntil:'networkidle'});await inspect(pageA);
}else if(command.action==='profiles'){
gate();assert.equal(await actor(pageA),2);const v=await getPlan(pageA,'/management/profile-install');matching(v,'profiles');assert.equal(v.hash,command.preview_hash);assert(!v.installed);
await pageA.getByLabel('Fundamento',{exact:true}).fill('Instalação exclusiva040000, após030000, candidata41caea0f900f autorizada em11/10/2026. PreservarCOUNTRY02 e manter os fluxos operacionais desabilitados.');
await pageA.getByLabel('Confirmo exclusivamente a ação indicada nesta base',{exact:true}).check();const wait=pageA.waitForResponse(r=>r.request().method()==='POST'&&new URL(r.url()).pathname==='/management/profile-install');await pageA.getByRole('button',{name:'Instalar schema 040000',exact:true}).click();assert.equal((await wait).status(),200);await pageA.reload({waitUntil:'networkidle'});await inspect(pageA);assert(proof.profiles_preview.installed);proof.criteria.profiles_authenticated_UI_installed=true;write();
}else if(command.action==='request-bootstrap'){
gate();assert.equal(await actor(pageA),2);const v=await getPlan(pageA,'/management/profile-install');matching(v,'profiles');assert(v.installed&&!v.bootstrap.initialized&&!v.bootstrap.pending);assert.equal(v.hash,command.preview_hash);
await pageA.getByLabel('Fundamento',{exact:true}).fill('Designação explícita das contas existentes2 e13, pertencentes a pessoas distintas conforme confirmação do responsável. Pedido por2 e confirmação independente por13, cada um com sessão e MFA próprios. Candidata41caea0f900f autorizada; fluxos operacionaisOFF.');
await pageA.getByLabel('ID da primeira conta explicitamente designada',{exact:true}).fill('2');await pageA.getByLabel('ID da segunda conta explicitamente designada',{exact:true}).fill('13');await pageA.getByLabel('Confirmo exclusivamente a ação indicada nesta base',{exact:true}).check();
const wait=pageA.waitForResponse(r=>r.request().method()==='POST'&&new URL(r.url()).pathname==='/management/profile-install/bootstrap');await pageA.getByRole('button',{name:'Pedir confirmação ao segundo titular',exact:true}).click();const res=await wait;assert.equal(res.status(),200);const result=await res.json();assert(result.confirmation_url);proof.confirmation_url=result.confirmation_url;proof.criteria.first_holder_requested_without_self_approval=true;
await pageA.goto(BASE+new URL(result.confirmation_url,BASE).pathname,{waitUntil:'networkidle'});const own=await getPlan(pageA,new URL(result.confirmation_url,BASE).pathname);assert.equal(Number(own.requested_by),2);assert.equal(Number(own.second_user_id),13);assert(!own.can_confirm&&!own.consumed);proof.criteria.first_holder_cannot_confirm=true;write();
}else if(command.action==='open-second'){
assert(proof.confirmation_url&&!contextB);contextB=await browser.newContext({locale:'pt-MZ',viewport:{width:1280,height:900},ignoreHTTPSErrors:false});pageB=await contextB.newPage();events(pageB,'second_holder');current=pageB;
await pageB.goto(BASE+new URL(proof.confirmation_url,BASE).pathname,{waitUntil:'networkidle'});await inspect(pageB);proof.criteria.second_holder_own_browser_session=true;write();
}else if(command.action==='confirm-bootstrap'){
gate();assert.equal(await actor(pageB),13);const url=new URL(proof.confirmation_url,BASE).pathname;const v=await getPlan(pageB,url);
assert.equal(v.target.database,'investemoz');assert.equal(Number(v.target.port),5432);assert.equal(Number(v.requested_by),2);assert.equal(Number(v.second_user_id),13);assert(v.can_confirm&&v.mfa_confirmed_in_session&&v.code_current&&!v.expired&&!v.consumed);assert.equal(v.hash,command.preview_hash);
await pageB.getByLabel('Fundamento da confirmação',{exact:true}).fill('Confirmo de forma independente, como titular13 em sessão própria comMFA, a dupla explicitamente designada2 e13. Autorização do responsável para candidata41caea0f900f; não atribuir papéis operacionais nem habilitar fluxos.');await pageB.getByLabel('Confirmo apenas estas duas contas e esta base',{exact:true}).check();
const wait=pageB.waitForResponse(r=>r.request().method()==='POST'&&new URL(r.url()).pathname===url+'/confirm');await pageB.getByRole('button',{name:'Confirmar designação cruzada',exact:true}).click();assert.equal((await wait).status(),200);
await pageB.getByRole('status').filter({hasText:'Dupla inicializada por confirmação cruzada'}).waitFor();proof.criteria.second_holder_confirmed_with_own_MFA=true;proof.phase='bootstrap_confirmed_operational_flags_OFF';write();
}else if(command.action==='close'){done=true;proof.phase='completed';write();}
else throw new Error('Unsupported bounded action');
proof.last_command={id:command.id,action:command.action,result:'passed'};write();console.log(JSON.stringify(proof.last_command));
}catch(e){proof.last_command={id:command.id,action:command.action,result:'stopped',error_type:e.name,message:e.message.slice(0,200)};write();console.log(JSON.stringify(proof.last_command));}
}
if(done)await browser.close();
})().catch(e=>{proof.phase='stopped_before_further_action';proof.error_type=e.name;proof.message=e.message.slice(0,200);write();console.error(JSON.stringify({phase:proof.phase,error_type:e.name,message:proof.message}));process.exitCode=1;});