Files
SGF/scripts/summarize-association-release.mjs
T
2026-11-02 20:29:34 +02:00

107 lines
13 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
const read=p=>JSON.parse(fs.readFileSync(p,'utf8').replace(/^\uFEFF/,''));
const hash=p=>crypto.createHash('sha256').update(fs.readFileSync(p)).digest('hex');
const state=read('C:/laragon/data/sgf-tempo/run/modular-regression-current.json');
const input=path.resolve(process.argv[2]||'');
if(path.dirname(input)!==path.resolve(state.root)||!/^association-release-\d+\.json$/.test(path.basename(input)))throw Error('Unexpected regression report');
const r=read(input), xmlPath=input.replace(/\.json$/,'.xml'), xml=fs.readFileSync(xmlPath,'utf8');
const suite=xml.match(/<testsuite\b([^>]*)>/)?.[1];
const number=k=>Number(suite?.match(new RegExp('\\b'+k+'="(\\d+)"'))?.[1]);
const test=Object.fromEntries(['tests','assertions','failures','errors'].map(k=>[k,number(k)]));
const projection=read('docs/proofs/module-association-projection-summary-20261102.json');
const source='app/Modules/Identity/Services/ModuleAssociationReview.php';
if(r.identity.database!=='sgf_tempo_test'||r.identity.role!=='sgf_test_runner'||r.identity.port!==55439
||r.identity.cluster!=='sgf_modular_regression_'+state.id||!r.completed||!r.previous_qa_rows_and_index_catalog_preserved
||r.cases.length!==4||test.tests!==1||test.failures!==0||test.errors!==0||hash(source)!==r.source_sha256
||projection.source.final_sha256!==r.source_sha256)throw Error('Incomplete or inconsistent proof');
for(const c of r.cases)if(c.duplicate_decision||!c.wait.actual_wait_observed||!c.wait.blocking_parent_verified)throw Error('Invalid release case');
const proof='docs/proofs/module-association-release-20261102.json';
fs.copyFileSync(input,proof);
const oldProofs=['optimization-comparison','optimization-summary','projection-fanout','projection-summary','concurrency','concurrency-summary'];
const summary={date:r.date,source_sha256:r.source_sha256,test:{...test,junit_sha256:hash(xmlPath)},cases:r.cases,
preservation:{previous_qa_rows_and_index_catalog_preserved:true,protected_six_records_untouched:true,blocked_cache_untouched:true,
destination_connected:false,public_rights_changed:false,indexes_created:false,publication:false,purchasing_activation:false,
new_qa_fixture_retained:true,private_manifest_retained:true,functional_code_changed_in_this_followup:false},
proof:{path:proof,sha256:hash(proof)},reused_proofs:oldProofs.map(n=>{const p='docs/proofs/module-association-'+n+'-20261102.json';return {path:p,sha256:hash(p)};}),
deployment_capacity_validated:false,visual_review_pending:true};
fs.writeFileSync('docs/proofs/module-association-release-summary-20261102.json',JSON.stringify(summary,null,2)+'\n');
const a=read('docs/proofs/module-association-optimization-comparison-20261102.json');
const labels={clients:'Clientes',services:'Serviços',service_drafts:'Rascunhos serviço',billing_drafts:'Rascunhos facturação'};
const fmt=n=>Number(n).toFixed(2).replace('.',',');
const metric=(s,k)=>s.censored?'limite':k==='median_ms'?fmt(s[k]):k==='queries'?String(s[k]):fmt(s[k]/1024);
const table=[];
for(const d of a.datasets)for(const c of d.cases){
const ss=['baseline','batch','indexed_batch'].map(v=>c.summary[v]);
table.push(`| ${d.records_per_area} | ${labels[c.area]} / ${c.state} | ${c.page_size} | ${ss.map(s=>metric(s,'median_ms')).join(' / ')} | ${ss.map(s=>metric(s,'queries')).join(' / ')} | ${ss.map(s=>metric(s,'incremental_php_used_bytes')).join(' / ')} |`);
}
const cases=r.cases.map(c=>`| ${c.case} | ${c.release} | ${c.second_http_status}${c.second_replayed?' (repetição)':''} | ${fmt(c.elapsed_ms)} | ${c.record_version_after} | ${c.decision_events_after_retry} |`).join('\n');
const document=`# Fila de associações — consolidação de carga e concorrência
02/11/2026. **A optimização está implementada localmente.** A listagem usa leitura conjunta da página e das suas dependências em \`describePage/pageStates\`, com lotes limitados, sem materializar o universo. A projecção posterior \`pageColumns\` selecciona metadados e referências em vez dos corpos completos. Prévia e confirmação continuam a obter e revalidar o estado completo actual. Código: [ModuleAssociationReview.php](../${source}). Esta passagem acrescenta provas e documentação; não altera esse código funcional.
## Espera da confirmação: causa e resultado após libertação
A prova anterior atingiu \`lock_timeout=1000ms\` (SQLSTATE 55P03, 1 044,222 ms) porque a primeira transacção foi deliberadamente mantida aberta. A nova prova observou a consulta sobre **companies com FOR UPDATE**, o PID da primeira transacção em \`pg_blocking_pids\` e a espera **Lock / transactionid / ShareLock** em \`pg_stat_activity/pg_locks\`. É serialização da confirmação por empresa; o limite não surgiu da leitura conjunta da auditoria. Pode também serializar confirmações de registos diferentes da mesma empresa; esse volume de escritores não foi medido.
Em quatro cenários novos, a primeira transacção foi libertada depois de observar a espera real e aguardar mais 200 ms. O segundo processo tinha limite de bloqueio de 8 s exclusivamente para esta prova. Os tempos abaixo incluem espera e execução, não representam latência normal nem tempo puro após desbloqueio.
| Cenário | Libertação da primeira | Segunda confirmação | Tempo total ms | Versão do registo final | Eventos de decisão após repetição |
| --- | --- | --- | ---: | ---: | ---: |
${cases}
Após rollback, a segunda decisão gravou exactamente um evento. Após commit de outra decisão, o token preparado antes da espera recebeu **409 por revisão de associações desactualizada**, mesmo mantendo versão documental 1; nova prévia foi possível. O mesmo token da decisão vencedora recebeu sucesso idempotente e o mesmo identificador de evento. Quando a versão documental mudou para 2 durante a espera, o token da versão 1 recebeu **409**, sem decisão; uma prévia da versão 2 foi possível. As repetições não acrescentaram eventos. Os estados de sucesso/409 do segundo processo são resultados do serviço; as repetições e novas prévias foram também verificadas pela API em processo. A apresentação HTTP de timeout de bloqueio continua sem prova própria.
**Resultado novo: ${test.tests} teste / ${test.assertions} asserções, sem falhas ou erros**, mais quatro trabalhadores concluídos. [Prova](proofs/module-association-release-20261102.json) e [resumo com hashes](proofs/module-association-release-summary-20261102.json). A primeira tentativa desta prova concluiu os quatro cenários mas falhou na comparação final da auditoria por exceder 65 535 parâmetros SQL; corrigiu-se apenas o auxiliar de preservação para IDs inteiros capturados. A execução acima repetiu esta prova afectada e passou; não se repetiram as matrizes ou suites válidas.
## Comparação A/B/C: leitura conjunta e índices separados
Medições já válidas, reutilizadas sem nova execução. **A**: checkpoint 340f3bb, consultas por registo; **B**: leitura conjunta, sem índices experimentais, **antes da projecção posterior**; **C**: mesmo B com dois índices experimentais de auditoria. Portanto A→B mede a leitura conjunta e B→C mede separadamente os índices. Não atribuir ao código actual exactamente os tempos de B: a projecção tem outra medição e hash abaixo.
Mesma fixture imutável em cada volume, 1 000/5 000/10 000 **por cada uma das quatro áreas**, dependências incluídas; três repetições, ordem A/B alternada, C sobre os mesmos dados, kernel e buffers aquecidos. Valores: mediana de tempo; consultas por pedido completo; máximo de memória PHP usada incremental acima do início do pedido, em **KiB**, sem incluir PostgreSQL ou memória total do processo. “limite” significa tentativa interrompida pelo orçamento diagnóstico de 15 s, sem mediana/consultas/memória comparáveis; não é um valor de 15 s nem um ganho calculável. Na prova completa foram 258 leituras medidas concluídas e 13 tentativas limitadas; respostas completas comparáveis idênticas.
Cada célula contém **A / B / C**. A memória em B pode aumentar face a A, por conservar o conjunto da página; a redução de consultas não implica redução de memória.
| Registos por área | Área / filtro | Página | Tempo ms A/B/C | Consultas A/B/C | Memória incremental KiB A/B/C |
| ---: | --- | ---: | ---: | ---: | ---: |
${table.join('\n')}
Métricas brutas, incluindo picos PHP absolutos, tempos SQL, amostras e hashes: [comparação](proofs/module-association-optimization-comparison-20261102.json). [Método, planos, índices e recusas de acesso](MODULE_ASSOCIATION_OPTIMIZATION_DELIVERY.md). Os dois índices (ligações por módulo e revisão por registo) foram criados apenas no ensaio sintético e desfeitos por rollback/savepoint; nenhum índice no destino.
## Código actual: projecção e documentos com muitas dependências
A fase posterior comparou B com a projecção actual, mantendo 10 000 registos por área e 100 linhas por rascunho, páginas 25/100, descrições de 128/1 000 bytes. Foram 132 leituras completas, 44 cenários, respostas equivalentes. A projecção reduziu a memória, mas não acelerou todas as leituras sem índice. Para descrições de 1 000 bytes:
| Área / página | Tempo ms antes projecção / actual sem índice / actual só índice revisão / actual par | Memória incremental MiB antes / actual |
| --- | ---: | ---: |
| Serviço / 25 | 668,119 / 728,558 / 97,487 / 225,676 | 8,28 / 4,21 |
| Serviço / 100 | 2150,980 / 2861,608 / 341,479 / 408,455 | 32,42 / 16,65 |
| Facturação / 25 | 815,219 / 905,073 / 111,725 / 127,118 | 11,55 / 4,85 |
| Facturação / 100 | 2720,541 / 2481,159 / 374,043 / 368,403 | 45,49 / 19,23 |
Consultas: serviço 27/57 e facturação 31/61, páginas 25/100; o crescimento decorre dos lotes das dependências (até 10 000 serviços na página), não do carregamento de todo o universo. Índice de revisão: 13,73 MiB; par: 30,50 MiB. O segundo índice não mostrou benefício consistente; não foi recomendado/aplicado automaticamente. Experiências desfeitas apenas na base sintética. [Relatório e provas desta fase](MODULE_ASSOCIATION_PROJECTION_DELIVERY.md).
Proveniência SHA-256 do serviço: A **${a.baseline_sha256}**; B/C **${a.optimized_sha256}**; projecção actual e nova prova de libertação **${r.source_sha256}**. A confirmação/prévia não foram alteradas pela optimização.
## Concorrência de leitura e equivalência preservadas
Mantêm-se válidas **440 leituras completas**, até quatro identidades/processos com sobreposição efectivamente observada: 420 da matriz e 20 durante confirmação sem commit. Página 25, 3 000 por área, rascunhos com 100 linhas de 1 000 bytes. Todas corresponderam aos hashes autorizados; os leitores viram o estado anterior enquanto a decisão não tinha commit. Não foram repetidas.
Com quatro processos, clientes: P50/P95 amostral **37,990/60,810 ms sem índice**, **14,790/21,402 ms com revisão**; facturação: **429,457/495,430 ms sem índice**, **102,861/121,342 ms com revisão**. Consultas 11/31, memória incremental máxima aproximada 0,09/4,85 MiB por pedido. Os percentis descrevem amostras pequenas (15/30/60 pedidos); não constituem SLA. O índice desta prova entre processos foi gravado só na regressão para ser visível, depois removido explicitamente, com catálogo anterior igual. [Ondas, tempos e limites](MODULE_ASSOCIATION_CONCURRENCY_REVIEW.md).
Preservam-se também as provas válidas afectadas: **21 testes / 322 asserções**, equivalência de filtros, ordenação, paginação, isolamento e recusas, versões/dependências, prévia, confirmação, falha e recuperação; ensaios separados de planos e acessos documentados na entrega. Não foram repetidos por não haver alteração funcional nesta continuação.
## Preservação e limites
A nova execução verificou fisicamente **sgf_tempo_test / sgf_test_runner / porta 55439 / cluster sgf_modular_regression_${state.id}**. Usou quatro clientes e âmbito novos, exclusivamente de regressão, gravados para visibilidade entre processos e retidos com manifesto privado. A primeira tentativa também deixou a sua pequena fixture sintética nesta base; não se apagou nem reinicializou a base. A execução válida verificou hashes dos registos QA anteriores e igualdade do catálogo de índices. Tokens, contexto e manifestos privados não integram estas provas públicas.
Os seis registos protegidos, a demonstração 8039, o SGF local, a cache bloqueada, provas válidas e trabalho alheio foram preservados; não se abriu ligação ao destino nesta prova. Sem novos índices, direitos públicos, activação de Compras, publicação ou checkpoint. O auxiliar de preservação foi corrigido, não a aplicação.
**Capacidade de implantação não validada.** Estes ensaios locais usam kernel HTTP em processo e trabalhadores PHP; não medem navegador, login/MFA, rede/TLS, servidor HTTP implantado, carga sustentada, vários escritores ou recursos completos do servidor. Faltam condições, orçamento de memória, índices/planos e política de espera/repetição próprios do destino. A revisão visual permanece separada e pendente, sem repetir a via de navegador bloqueada.
`;
fs.writeFileSync('docs/MODULE_ASSOCIATION_LOAD_CONSOLIDATION.md',document.replaceAll('\`','`'));
console.log(JSON.stringify({test,source_sha256:r.source_sha256,comparative_rows:table.length,proof},null,2));