119 lines
14 KiB
Python
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')
|