Files
Investidor-V2/tools/portal-audit-workflow-update.py
nala 01405cbf8a
tests / ci (push) Canceled after 0s
oportunidades 5
2026-10-10 14:15:28 +02:00

93 lines
15 KiB
Python

"""Reconcile workflow evidence in the sole existing matrix; preserve all source references and versions."""
import csv
import json
import hashlib
import collections
import sys
from pathlib import Path
from datetime import datetime, timezone
ROOT=Path(__file__).resolve().parent.parent
PRIVATE=ROOT/'storage/app/private/investmoz-v2/portal-audit'
MATRIX=ROOT/'docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv'
csv.field_size_limit(16*1024*1024)
sys.stdout.reconfigure(encoding='utf-8')
rows=list(csv.DictReader(MATRIX.open(encoding='utf-8-sig')))
before={r['ID']:dict(r) for r in rows}
fields=list(rows[0])
assert len(rows)==len(before)==3386
proof=json.loads((PRIVATE/'opportunity-workflow-http-qa.json').read_text())
extended=json.loads((PRIVATE/'opportunity-workflow-extended-http-qa.json').read_text())
multiple=json.loads((PRIVATE/'opportunity-workflow-multirole-http-qa.json').read_text())
territory_tasks=json.loads((PRIVATE/'opportunity-workflow-territory-tasks-http-qa.json').read_text())
verified=json.loads((PRIVATE/'opportunity-workflow-verification-qa.json').read_text())
assert all(p['result']=='passed' and p['active_writes']==0 for p in [proof,extended,multiple,territory_tasks])
assert all(p['target']=='desk_restore_20261009_174726_test:55441' for p in [proof,extended,multiple,territory_tasks])
snapshot=PRIVATE/'opportunity-workflow-matrix-before.csv'
if not snapshot.exists():snapshot.write_bytes(MATRIX.read_bytes())
marker='[Workflow QA 10/10/2026]'
def append(row,field,value):
old=row[field]
if marker in old:old=old.split(marker,1)[0].rstrip(' |')
row[field]=old+' | '+marker+' '+value
common='QA18097/desk_restore_20261009_174726_test:55441; Promotor21, Validador24, Publicador25, recurso29; FortifyMFA normal. Provas privadas opportunity-workflow-http-qa.json, -extended-http-qa.json, -multirole-http-qa.json, -territory-tasks-http-qa.json e opportunity-workflow-verification-qa.json. Oportunidade28:localização principal segunda na lista selecionada corretamente, tarefas concluídas por correção/suspensão, resubmissão sem atribuição herdada;29:rejeição conclui revisão. Interface renderizada/C37 pendentes; zero escritas ativas.'
updates={
'AUD-OP-03':('Submeter→atribuir/iniciar→solicitar correção→corrigir→resubmeter→gates→aprovar→publicar implementados, hash/ETag/revisões e auditoria. Revisão privada conserva publicação; rejeição, recurso independente, suspensão; publicação explícita com prévia/confirm/hash.', 'Ciclo investimento18, publicação interna; imobiliário19:rejeição24→recurso29→nova revisão24→publicação25 pública→proposta privada→suspensão404. Fundiário20:aprovado internamente, publicação422 por autorização externa pendente. Autor comProm+Val+Pub403; aprovadorVal+Pub403; 3atribuições temporárias revogadas.', 'Visual integral; contratos/objetos opacos e gates legais completos; pareceres externos/incidentes, coordenação editorial, SLA/política versionada e publicação agendada. Não declarar workflow integral fechado.'),
'AUD-OP-04':('Adaptador canónico do schema110defs; condições originais allOf recuperadas; OpisDraft2020-12. IDs bigint existentes mantidos, subsector/atividade no catálogo atual; recursos por IDs nas coleções existentes; JSONprivado/versionado, sem cadastros paralelos.', '10categorias guardam rascunho incompleto e recuperam noutra sessão MFA. Create/submissão passa6(investimento,imobiliário,DUAT,venda,parceria,franquia);4 recusadas por falta de perfil modular aprovado. Venda exige preço>0; null422. Camposreadonly/verified/land_sale recusados. Ligações/contacto/hierarquia/hash/scan/parcelas validadas.', 'Formulários/grids e validações semânticas adicionais do §25:valorações completas, comunidades/consulta, beneficiário efetivo/financeiros existentes, compatibilidade cadastral e urbanística, NDA/teaser, Response integral e editores ainda incompletos. Perfis concession/financing/supply/other não configurados; não inventar contratos.'),
'AUD-PROFILE-08':('Estados privados/versionados separados do estado publicado, conteúdo/recursos bloqueados após submissão; revisões/decisões por hash e histórico.', 'Ciclo positivo realHTTP com contas distintas; correção/resubmissão, aprovação/pub, recuperação, revisão material, preservação pública e suspensão demonstradas.', 'Interface/C37; incidentes/opiniões completos, agendamento e revisão periódica, contratos/editores restantes; estados legados preservados.'),
'AUD-PROFILE-09':('Independência verificada sob lock:autor/contribuinte/membroorg não valida; aprovador não publica; revisores e aprovador são reavaliados perante revogação/conflito.', 'HTTP403 autor21 comProm+Val+Pub, aprovador24 comVal+Pub, organização/múltiplos papéis31; MFA normal. Histórico das3atribuições temporárias/revogações preservado.', 'Visual dos controlos/negativas; segregações adicionais por perfil modular e gates de instância externa.'),
'AUD-PROFILE-10':('Negação prevalece nos acessos diretos e transições, incluindo papéis acumulados e Auditor com escrita.', '403 nos percursos normais de MFA; temporárias revogadas, Promotor21 entra noutra sessão com o perfil original.', 'Demonstração visual e políticas adicionais territoriais/perfis modulares.'),
'AUD-PROFILE-11':('Validador atribuível explícito, inicia revisão, solicita correção, rejeita com fundamento, regista gates aplicáveis e aprova versão completa.', 'Validador24 fez o ciclo positivo; aprovação prematura422, motivo curto422, own403. Evidências apontam documentos desta oportunidade/versão.', 'Visual, distribuição/disponibilidade completa, revisões técnicas/jurídicas semânticas por todos os contratos e política SLA.'),
'AUD-PROFILE-12':('Publicador independente confirma prévia/hash/âmbito, publica projeção mínima, suspende e arquiva; não edita dados técnicos.', 'Publicador25:pub interna18; pública19 com2derivadas ClamD; confirmfalse422/hashdiverso409; alteração privada não vazou; suspensãoGETpúblico404. Fundiário20externapendente422.', 'Visual e cobertura editorial completa:imagens/mapas na ficha, tradução/NDA/teaser específico, agendamento, monitoria, coordenação editorial e reversão de edição publicada.'),
'AUD-PROFILE-19':('Tarefas CRM existentes com source_system/external_id/correlation_id ligados ao ID da oportunidade; fila sem responsável/prazo inventado, atribuição explícita pelo coordenador validada. Gates requerem responsabilidade técnica/jurídica.', 'Atribuição24 persistida, tarefas review/correction/publication criadas e concluídas conforme ações; due_at=null/SLAunconfigured. Papéis/responsabilidades reais reavaliados; não cria crm_opportunity paralela.', 'Fila/editor próprios, reatribuição/disponibilidade, coordenação editorial, parecer externo e escalamento completos; testes visuais. Responsáveis/prazos reais ainda não definidos.'),
'AUD-PROFILE-21':('Recurso conserva rejeição/motivo; exige appeal_reviewer distinto do rejeitor e independente da autoria/organização; manter decisão ou reabrir, auditar e concluir tarefa existente.', 'Imobiliário19:rejeição24→pedido21→rejeitor24negado403/membroorg22negado403→revisor29reabre200→nova sessão21recupera. Decisão original permanece.', 'Visual; filas/designação/competências institucionais, políticas de prazo e instância ativa; manter decisão original e nenhuma autoridade real inferida.'),
'AUD-PROFILE-22':('SLA explicitamente unconfigured e due_at=null; preparação e tarefas continuam. Cliente não fixa prazo arbitrário.', 'Tarefas e contexto recuperados com política/prazo nulos, sem tolerância/ciclos máximos inventados.', 'Decisão concreta futura:qual política/versionamento de prazos,tolerância,ciclos e escalamento é aprovada? Motor/publicação de parâmetros pendentes; não bloqueia preparação.'),
'AUD-PROFILE-23':('Auditoria existente recupera atores, razões, estados/versões/gates/hash e tarefas/recurso; razões de edição preservadas no histórico cifrado.', 'Eventos workflow_submit/request_correction/review_gate/approve/publish/request_revision/suspend/appeal_reopen e revogações temporárias; nenhuma credencial/OTP/cookie exportados.', 'Visual de filtros/exportações e comparação detalhada; auditabilidade de operações futuras ainda não implementadas.'),
'AUD-PROFILE-25':('Editor declarativo comum/especializado, escolha de referências/recursos; ações após submissão abrem /view, edição privada por link autorizado; fila/estado/título privados distinguem ficha pública.', 'PropsAPI e assetsQA:CRUD/ações/gates/prévia/tarefas; teste filtro necessidades/título privado; aprovação/pub com confirmação atual. NomePromotor QA — conta sintética21.', 'Nenhum clique/render observado:dashboard→contratos/erros→workflow→recuperação ainda exige navegador; geometrias/parcela e objetos sem editor completo permanecem abertos.'),
'AUD-PROFILE-28':('Contrato canónico derivado do mesmo schema/condições normativas, sem alterar silenciosamente enums/IDs. Núcleo reutilizado, bindings/contactos/resources, submissão estrita e draft parcial.', '10drafts recuperados,6contratos completos estruturalmente aceites/4bloqueados por perfil;6verificados novamente no snapshot atual. Source.json false é histórico da extração, runtime rastreado no relatório.', 'Semântica §25/objetos opacos, Response integral, grillas específicas e quatro perfis aprovados; visual, publicação de todos os contratos com dados/provas aplicáveis ainda não demonstrada.'),
'AUD-PROFILE-30':('PromotorQA21 claramente identificado, tipos autorizados por pedido/decisão independente. Validador24 reatribuído explicitamente, Publicador25 e instância sintética29 separados.', 'NormalFortify/MFA na18097; cenário18,19 e recuperação dos10drafts; negar self/multirole e revogar permissões temporárias. Não escolher titulares/responsáveis reais.', 'C37 e visual por cada perfil; novos processos/contratos ainda não cobertos. QA não equivale à instalação ativa.'),
'AUD-PROFILE-31':('Código/Composer3novos pacotes, assets exclusivosQA, guardas e migrações existentes; recursos030000/profiles040000 precedem disponibilização das flags. Novos contratos não criam tabelas próprias.', 'AtivoREADONLY:020000sim/030000não/040000não/flagsfalse/0geom; QA393privadas,18conjuntosoriginais preservados/MFA18intacto/manifativo753... .', 'Aceitação visual, fechar editores/gates prioritários e fixar candidata; mecanismo040000ativo/authedbootstrap ainda não completo; renovarbackup/restauro dessa revisão, sem repetir instalações nem importar fixtures sintéticas.'),
}
for row in rows:
if row['ID'] in updates:
implementation,evidence,gap=updates[row['ID']]
row['Estado']='parcial'
append(row,'Implementação encontrada',implementation)
append(row,'Evidência funcional',evidence+' '+common)
append(row,'Lacuna',gap)
append(row,'Disponibilidade ativa','Leitura ativa10/10: PostgreSQLinvestemoz:5432READONLY;020000instalada;030000/040000ausentes;flagsdashboard/especializados/perfisfalse. Lote instalado e demonstrado apenasQA18097;zeroescritasativas,393geometrias privadasQA/0ativas. Não confundir código preparado,QA e instalação ativa.')
append(row,'Dependência','Continuar componentes canónicos/workflow/tarefas existentes; seis categorias estruturais disponíveisQA, quatro dependem de perfil modular versionado aprovado. Visual/C37, semântica/editor e políticas reais mantêm-se abertos conforme lacuna. Não requer integração externa para preparação interna.')
append(row,'API',evidence)
append(row,'Gravação','Persistência QA em opportunities/management_draft/workflow_context, coleções documentais/territoriais existentes, tasks/audit_events. IDs/originais/histórico conservados.')
append(row,'Recuperação','Nova sessão Fortify/MFA recupera payloads/recurso/estado, paísMZ/localização/documentos por IDs. Provas atuais identificadas por caso, sem usar contagem global como substituto.')
append(row,'Interface','Código e assetsQA984244d5dd9ece719d5c2d2ab852a6019aae8f728dab654c5a8bd6e75adec5a8; props/mensagens/ações demonstradasHTTP. Render/cliques pendentes; C37 permanece aberto.')
append(row,'Resultado apresentado','Resultados disponíveis nas respostasQA; aceitação visual e funcionalidade ativa permanecem separadas.')
elif row['ID'] in ['AUD-DASH-01','AUD-GEO-03','AUD-COUNTRY-01','AUD-STATS-01']:
append(row,'Evidência funcional','Consumidor oportunidades:Promotor21, contrato/localizaçãoMZ/documentos/refs recuperados por categoria; APIprivateboundaries existente. Não encerra países/origem/estatísticas por outros formulários, 188lacunas cartográficas nem render de mapas. '+common)
if row['ID'] == 'AUD-PROFILE-23':
row['Lacuna'] += ' Composer audit apontou dois avisos preexistentes em league/commonmark (GHSA-97jj-33gv-5xf9 e GHSA-3q6v-r5mr-hxv8); confirmar consumidores/configuração e corrigir emQA. Prova privada workflow-composer-audit.json; não são dos três pacotes Opis novos.'
protected=['ID','Pedido/trecho','Fonte/ocorrências','Critério de aceitação','Versões divergentes','IDs das linhas de origem','Quantidade de linhas de origem','Critérios e versões por origem (JSON)']
assert all(all(r[k]==before[r['ID']][k] for k in protected) for r in rows)
with MATRIX.open('w',encoding='utf-8-sig',newline='') as f:
writer=csv.DictWriter(f,fieldnames=fields);writer.writeheader();writer.writerows(rows)
origins=[item for r in rows for item in json.loads(r['Critérios e versões por origem (JSON)'])]
assert len(origins)==6838
counts=json.loads((PRIVATE/'contagens-requisitos.json').read_text())
states=collections.defaultdict(lambda:{'requisitos':0,'linhas_origem':0})
for row in rows:
states[row['Estado']]['requisitos']+=1;states[row['Estado']]['linhas_origem']+=int(row['Quantidade de linhas de origem'])
counts.update({'requisitos':len(rows),'linhas_matriz':len(rows),'linhas_origem':len(origins),'por_estado':dict(states),
'metodo':'Mesma matriz: 3.354 requisitos e 5.830 referências anteriores preservados; 32 requisitos verificáveis de perfis, 1.001 referências documentais e sete pedidos diretos. O incremento de workflow mantém requisitos parciais; não encerra aceitação visual nem contratos incompletos.',
'conservation_sha256':hashlib.sha256(json.dumps(origins,ensure_ascii=False,sort_keys=True,separators=(',',':')).encode()).hexdigest(),
'browser_current_version_verified':False,'workflow_updated_at':datetime.now(timezone.utc).isoformat()})
(PRIVATE/'contagens-requisitos.json').write_text(json.dumps(counts,ensure_ascii=False,indent=2),encoding='utf-8')
print(json.dumps({'requirements':len(rows),'source_occurrences':len(origins),'updated':list(updates),'states':dict(states),'active_writes':0},ensure_ascii=False))
# Newer semantic evidence takes precedence over dated workflow claims.
semantic = PRIVATE / 'opportunity-semantic-publication-http-qa.json'
if semantic.exists() and json.loads(semantic.read_text()).get('result') == 'passed':
import runpy
runpy.run_path(str(ROOT / 'tools/portal-audit-semantic-update.py'), run_name='__main__')