Files
Investidor-V2/tools/portal-functional-lease-results.py
T
nala 287e991486
tests / ci (push) Waiting to run
oportunidades 7
2026-10-13 11:40:29 +02:00

119 lines
14 KiB
Python

"""Bind the demonstrated lease criteria and C125 to the existing matrix only."""
from pathlib import Path
from datetime import datetime, timezone
from collections import Counter
import csv, hashlib, json, sys
csv.field_size_limit(20_000_000)
root=Path(__file__).resolve().parent.parent
private=root/'storage/app/private/investmoz-v2/portal-audit'
directory=private/'functional-lease-results-20261011'
docs=root/'docs/investmoz/v2/auditoria-portal'
out=root/'outputs/investmoz-opportunity-lease-qa-20261011'
read=lambda p:json.loads(p.read_text(encoding='utf-8-sig'))
sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest()
write=lambda p,v:p.write_text(json.dumps(v,ensure_ascii=False,indent=2),encoding='utf-8')
plan=read(private/'portal-stability-latest.json');source=Path(plan['source'])
ui=read(private/'functional-lease-editor-chromium-20261011.json')
sql=read(private/'functional-lease-persistence-qa-20261011.json')
assert ui['result']==sql['result']=='passed' and ui['opportunity_id']==sql['opportunity_id']==54
assert ui['checks']['corrected_values_visible_in_new_session']
old_candidate=root/'outputs/investmoz-opportunity-workflow-candidate-20261011'
old=read(old_candidate/'manifesto.json')
previous=(old_candidate/'payload/app/Services/PortalProfileAccess.php').read_text(encoding='utf-8')
expected=previous.replace(' $contexts = [];\n',' $contexts = [];\n $seen = [];\n',1).replace(
' if ($this->canCreate($actor, $grant->organization_id, $type)) {',
" $key = ($grant->organization_id ?? 'individual').'|'.$type;\n if (! isset($seen[$key]) && $this->canCreate($actor, $grant->organization_id, $type)) {",1).replace(
" $contexts[] = ['organization_id' => $grant->organization_id, 'type' => $type];",
" $contexts[] = ['organization_id' => $grant->organization_id, 'type' => $type];\n $seen[$key] = true;",1)
assert (source/'app/Services/PortalProfileAccess.php').read_text(encoding='utf-8')==expected
code={name:sha(source/name) for name in old['payload'] if name.startswith(('app/','resources/'))}
assert [name for name,h in code.items() if h!=old['payload'][name]]==['app/Services/PortalProfileAccess.php']
revision=hashlib.sha256(json.dumps(code,sort_keys=True,separators=(',',':')).encode()).hexdigest()
tag='[QA 11/10/2026 — arrendamento54/C125] '
ids={'AUD-OP-02','AUD-OP-03','AUD-PROFILE-01','AUD-PROFILE-07','AUD-PROFILE-25','AUD-PROFILE-28','AUD-UI-01'}
evidence='Chromium HTTPS QA/Promotor21: dashboard → Minhas oportunidades → Nova oportunidade → arrendamento54; rascunho incompleto guardado/recuperado noutra sessão; renda -1 recusada422 com mensagem junto ao campo/aria-invalid/aria-describedby, input preservado mas valor não gravado; renda4500/periodicidade monthly corrigidas e apresentadas noutra sessão. ResponseGET e SQL coincidem. C125: uma opção por organização/tipo apesar de concessões repetidas; QA22 continua apenas com o seu tipo, sem alterar papéis/representações. Recibos functional-lease-editor-chromium-20261011.json/functional-lease-persistence-qa-20261011.json. Venda31/A-B/C37 preservados.'
remaining='Critérios agora demonstrados: edição/validação de renda e recuperação do rascunho de arrendamento54; navegação sem opções duplicadas. Continuam pendentes submissão, revisão, documentos aplicáveis e publicação específicas de arrendamento, demais variantes categoria/Response§27, restauração por decidir e acessibilidade integral. Os códigos monthly e demais enums conservam o valor canónico; apresentação traduzida desses rótulos continua P2. Não encerra o requisito completo.'
if sys.argv[1]=='prepare':
assert not directory.exists();directory.mkdir()
files=[]
for filename in ['matriz-requisitos.csv','backlog-priorizado.csv']:
rows=list(csv.reader((docs/filename).open(encoding='utf-8-sig',newline='')));headers=rows[0];patches=[]
for i,row in enumerate(rows[1:],1):
if row[headers.index('ID')] not in ids:continue
patch={}
if filename=='matriz-requisitos.csv':
for field,note in {'Evidência funcional':evidence,'Lacuna':remaining,'Interface':'Arrendamento54: valores recuperados visíveis e erro junto ao campo; C125 sem duplicação.','API':'POST drafts201, PUT draft200/422, GET response200; alvo QA guardado.','Gravação':'Renda inválida não gravada; renda4500/periodicidade monthly no JSON canónico existente.','Recuperação':'Nova sessão Chromium apresenta os mesmos valores; SQL confirma autor21/organização/estado privado.','Resultado apresentado':'Rascunho privado recuperável; não representa submissão, contrato ou direito real.','Consolidação':'Provas específicas acrescentadas sem novo ID ou repetição de A/B/C37. Identidade documental confirmada permanece preservada.'}.items():
assert tag not in row[headers.index(field)];patch[field]=row[headers.index(field)]+'\n'+tag+note
else:
for field,note in {'Pendência':remaining,'Correção/componentes':'C125 corrigido apenas em PortalProfileAccess::navigation na cópia QA; manter concessões/histórico e regras de autorização. '+evidence,'Ordem do próximo lote':'Completar semântica documental/submissão de arrendamento e outras variantes já delimitadas. Consolidar C125 na próxima candidata sem sobrescrever ZIP3d9. Backup/restauro renovado obrigatório se novo acervo integrar futura entrega.'}.items():
assert tag not in row[headers.index(field)];patch[field]=row[headers.index(field)]+'\n'+tag+note
patches.append({'row':i,'values':patch})
assert len(patches)==7
files.append({'filename':filename,'input_sha256':sha(docs/filename),'rows':rows,'patches':patches,'allowed_columns':sorted({k for x in patches for k in x['values']}),'source_hash_updates':[]})
write(directory/'csv-edits.json',{'files':files});print(json.dumps({'prepared':True,'existing_rows_per_file':7,'new_requirements':0}))
elif sys.argv[1]=='finalize':
packet=read(directory/'csv-edits.json')
for edit in packet['files']:
rows=list(csv.reader((docs/edit['filename']).open(encoding='utf-8-sig',newline='')));expected=[list(r) for r in edit['rows']]
for patch in edit['patches']:
for k,v in patch['values'].items():expected[patch['row']][rows[0].index(k)]=v
assert rows==expected
matrix=list(csv.DictReader((docs/'matriz-requisitos.csv').open(encoding='utf-8-sig',newline='')))
assert len(matrix)==3401 and sum(int(r['Quantidade de linhas de origem']) for r in matrix)==7739
assert Counter(r['Estado'] for r in matrix)==Counter(read(private/'segmentation-reconciliation-20261011/counts.json')['states'])
for name,h in read(private/'functional-active-files-before-20261011.json').items():assert sha(root/name)==h
assert sha(old_candidate/'investmoz-workflow-3d9d392e0a0f.zip')=='6366c49d44cdc4242f4f93da5f5c1a53f2292d9eb485f70ef2b4e9a9f88589e9'
out.mkdir(exist_ok=True)
report=f'''# Arrendamento e navegação em QA
Identidade de `segmentar processos v1(3).docx` confirmada pelo responsável; `segmentar processos v1.docx` é o nome local do mesmo exemplar. Nenhuma nova importação, documento ou requisito por causa do nome. Mesma matriz: 3401 requisitos/linhas e 7739 ocorrências; estados/fechos anteriores conservados.
## Critérios demonstrados
{evidence}
UI: 8 verificações anteriores mais apresentação explícita dos valores em nova sessão. SQL: autor21, organização autorizada, modalidade lease, renda4500, periodicidade monthly, estado draft e nenhuma submissão/aprovação/publicação. Outra organização continua negada pela política; esta última prova é de política/SQL e não um novo ensaio visual com QA23. Sale31 conserva versão e hash de publicação anteriores.
## Falha C125 corrigida
Passos: QA21 com concessões de Promotor sobrepostas → dashboard → Minhas oportunidades → Nova oportunidade. Esperado: uma opção por organização/tipo autorizado. Observado: 32 opções para 10 pares distintos. Causa: `PortalProfileAccess::navigation` acrescentava o contexto em cada concessão. Correção: deduplicar o par depois de validar representação/permite criação, sem alterar concessões, organização, tipo ou regras de autorização. Resultado: 10 opções únicas no Chromium; QA22 mantém 1 contexto. Os quatro tipos experimentais continuam propostas e não são aprovados por esta correção.
As primeiras tentativas do harness procuravam um label exato que inclui o texto das opções; a troca para combobox com prefixo corrigiu o seletor de teste. Isso não foi atribuído à autorização do portal. O único defeito de navegação identificado nesta reprodução foi C125.
## Critérios restantes
{remaining}
Próximo lote executável: preparar recursos documentais próprios para submissão de arrendamento e demonstrar as transições pertinentes. Não reutilizar uma licença, título ou documento de outra oportunidade como se pertencesse a este caso. Manter os exemplos explicitamente sintéticos em QA.
## Código, candidata e reversão
Única alteração de código face à candidata selada3d9: `app/Services/PortalProfileAccess.php`, SHA256 `{code['app/Services/PortalProfileAccess.php']}`. Revisão de código QA atual `{revision}`; não é uma autorização de ativação nem um novo pacote selado. O bundle de produção não mudou (alteração exclusivamente PHP; mesma estrutura dos props). A candidata3d9 e o seu ZIP foram conservados e não contêm C125. A próxima candidata terá de incorporar este delta, atualizar hashes/prévias e verificar as dependências correspondentes.
O backup QA de12:10:22UTC precede o novo acervo e o caso54. Não cobre esses dados. Se o acervo integrar uma entrega futura, renovar backup/restauro do âmbito e da revisão final. Nenhum backup QA substitui o backup/restauro do destino. Não houve alteração de código/configuração/schema/dados ativos.
Contenção QA da correção: repor somente esse ficheiro PHP a partir do payload selado3d9, preservando papéis, concessões, rascunhos e histórico; isso reapresenta opções duplicadas e exige nova prévia. Nenhuma migration, remoção de dados ou alteração de MFA. COUNTRY02 ativo e fechosA/B/C37 permanecem intactos.
'''
report=report.replace('autor21','autor 21').replace('organização autorizada','organização autorizada').replace('renda4500','renda 4500').replace('Sale31','Venda 31').replace('candidata3d9','candidata 3d9').replace('selada3d9','selada 3d9').replace('caso54','caso 54').replace('de12:10','de 12:10').replace('fechosA/B','fechos A/B').replace('QA22','QA22').replace('label','rótulo')
(out/'relatorio.md').write_text(report,encoding='utf-8')
marker='<!-- workflow-lease-20261011 -->'
for filename in ['backlog.md','relatorio-executivo.md']:
p=docs/filename;t=p.read_text(encoding='utf-8');assert marker not in t
intro=marker+'\n\n## QA — arrendamento54 e C125\n\n'+evidence+'\n\n'+remaining+f' Revisão de código QA `{revision[:12]}` adicional à candidata selada `3d9d392e0a0f`; sem ativação. O novo acervo mantém a necessidade de backup/restauro correspondente antes de futura inclusão.\n\n'
p.write_text(intro+t,encoding='utf-8')
p=docs/'workflow-criterios-saida-20261011.md';t=p.read_text(encoding='utf-8');assert marker not in t
p.write_text(t+'\n\n'+marker+'\n\n## Atualização — variante de arrendamento54\n\n'+evidence+'\n\n'+remaining+'\n',encoding='utf-8')
p=docs/'casos-reproducao.md';t=p.read_text(encoding='utf-8');assert '### C125 —' not in t
case='### C125 — contextos de criação repetidos por concessões sobrepostas — P2\n\nQA21 → dashboard → Minhas oportunidades → Nova oportunidade: 32 opções para 10 pares organização/tipo. Esperado: cada par aparece uma vez. Causa: navegação acrescenta um par por concessão, sem deduplicação. Correção apenas na cópia QA de PortalProfileAccess::navigation, depois de verificar representação/permissão; concessões e regras intactas. Chromium confirmou 10 opções únicas, criou arrendamento54 e recuperou; SQL confirmou QA22 ainda com1contexto e venda31 sem alteração. Evidências: functional-lease-editor-chromium-20261011.json e functional-lease-persistence-qa-20261011.json. A correção não ativa módulos experimentais. Ativo inalterado; delta posterior à candidata3d9.\n\n'
p.write_text(case+t,encoding='utf-8')
manifest={'format':'investmoz-functional-QA-increment-1','at':datetime.now(timezone.utc).isoformat(),'code_revision':revision,'code_payload_hashes':code,
'changed_since_sealed_candidate':['app/Services/PortalProfileAccess.php'],'previous_sealed_candidate':'3d9d392e0a0f','production_bundle_changed':False,
'production_manifest_sha256':sha(source/'public/build/manifest.json'),'case':54,'active_writes':0,'activation_authorized':False,'activation_ready':False,
'QA_snapshot_predates_new_acervo_and_case':True,'new_acervo_delivery_requires_backup_restore':True,
'evidence':{name:sha(private/name) for name in ['functional-lease-editor-chromium-20261011.json','functional-lease-persistence-qa-20261011.json']},
'matrix_sha256':sha(docs/'matriz-requisitos.csv'),'backlog_sha256':sha(docs/'backlog-priorizado.csv'),'report_sha256':sha(out/'relatorio.md')}
write(out/'manifesto-qa.json',manifest);write(directory/'verification.json',{'result':'passed','requirements':3401,'source_occurrences':7739,'states_unchanged':True,'active_critical_files_unchanged':True,'old_candidate_unchanged':True,'code_revision':revision})
print(json.dumps({'result':'passed','requirements':3401,'new_requirements':0,'code_revision':revision,'active_writes':0}))
else:raise ValueError('Use prepare or finalize')