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

144 lines
20 KiB
Python

"""Publish review documents and prepare evidence-only changes to the existing matrix."""
from pathlib import Path
from datetime import datetime, timezone
import csv, hashlib, json, shutil
R=Path(__file__).resolve().parent.parent
P=R/'storage/app/private/investmoz-v2/portal-audit'
O=R/'outputs/investmoz-operational-onboarding-20261011'
load=lambda p:json.loads(p.read_text(encoding='utf-8-sig'))
sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest()
m=load(O/'manifesto.json')
b=load(P/'onboarding-release-target-backup.json')
i=load(P/'onboarding-release-readonly.json')
r=load(O/'ensaio-disponibilizacao-reversao.json')
assert m['delta_sha256']==b['candidate_revision']==r['delta']
assert m['package_sha256']==b['candidate_zip_sha256']==r['package_sha256']
assert b['restore_verified'] and b['files_verified'] and b['MFA_envelopes_readable']==2
assert len(b['tables'])==295 and len(b['sequences'])==31 and b['active_originals_backed_up']==4
assert i['source_writes']==0 and i['flags']['organization_country_enabled']
assert not any(v for k,v in i['flags'].items() if k!='organization_country_enabled')
assert i['superuser_pair']==[{'first_user_id':13,'second_user_id':2,'version':1}]
for relative,value in {x['path']:x['before_sha256'] for x in m['files']}.items():
assert (sha(R/relative) if (R/relative).is_file() else None)==value
assert sha(O/m['package'])==m['package_sha256'] and r['base_restored']
sanitized={k:b[k] for k in ['verified_at','valid_until','candidate_revision','candidate_zip_sha256','candidate_manifest_sha256','target','restored_target','dump_sha256','files_sha256','restore_verified','files_verified','sequences_verified','MFA_envelopes_readable','active_originals_backed_up','file_count','missing_files','source_installations','active_writes','services_scope']}
sanitized.update({'table_count':len(b['tables']),'sequence_count':len(b['sequences']),
'file_scope':'4 originais; configuração cifragem/.env privados; código app/config/routes/bootstrap/resources; bundle ativo; metadados de dependências; TLS e vhosts',
'file_count_difference':'1291 inclui resources (fontes de interface) para reversão deste delta. O backup anterior de 867 tinha scope diferente e incluía serviços; nenhum original foi excluído.',
'services_recovery':'Sem alteração aos serviços neste delta. O arquivo anterior de B1/B2 permanece preservado.',
'real_destination_restore_performed':False,'restore_destination_is_QA_only':True})
(O/'backup-restauro-renovado.json').write_text(json.dumps(sanitized,indent=2,ensure_ascii=False),encoding='utf-8')
shutil.copyfile(P/'onboarding-release-readonly.json',O/'destino-leitura-pre-disponibilizacao.json')
shutil.copyfile(P/'onboarding-designations-readonly.json',O/'designacoes-leitura-atualizada.json')
changes='\n'.join('| `'+x['path']+'` | '+({
'app/Http/Middleware/EnsurePortalProfileAdministration.php':'Middleware novo: preparação disponível sem abrir operações; falha controlada se faltar schema.',
'app/Services/PortalProfileAccess.php':'Distingue instalação, administração e operação. Preparação não abre oportunidades/anexos.',
'app/Services/PortalProfileAdministration.php':'Pedido de Promotor aceita a preparação independente; mantém representação e revisão documental.',
'routes/web.php':'Separa rotas /perfil e /perfis das rotas /minhas-oportunidades.',
'config/portal.php':'Nova flag de administração, OFF por defeito.',
'resources/js/pages/profiles/administration.tsx':'Nomes acessíveis dos seletores Papel e Âmbito organizacional.',
'app/Http/Middleware/PortalProfileSession.php':'Aplica revisão/revogação de sessões durante a preparação.',
'app/Http/Middleware/ProtectPrivilegedIdentity.php':'Preserva restrições de Auditor, MFA e titular durante a preparação.',
'app/Http/Middleware/AuditProfileDecision.php':'Regista recusas de autorização durante a preparação.'
}[x['path']])+' |' for x in m['files'])
doc=f'''# Disponibilização da administração de perfis — delta 41a5ce778932
Preparado em {datetime.now(timezone.utc).isoformat()}. Este procedimento esclarece a execução paralela solicitada após o relatório inicial. O ZIP e o manifesto originais mantêm os seus bytes e hashes. A disponibilização no destino aguarda autorização própria; não foi executada nesta preparação.
## Âmbito exato para autorização
Base ativa `41caea0f900f`; delta `{m['delta_sha256']}`. ZIP `{m['package']}`, SHA-256 `{m['package_sha256']}`. Nove ficheiros e 159 recursos compilados, incluindo o manifesto de produção `{m['production_manifest_sha256']}`.
Disponibilizar o código e bundle com a nova flag OFF. Depois habilitar **somente** `PORTAL_PROFILE_ADMINISTRATION_ENABLED=true`. Manter `PORTAL_PROFILE_ACCESS_ENABLED=false`, dashboard, rascunhos especializados, experimentais e notificações externas OFF. COUNTRY02/050000 permanece ON. Não executar migrations, 030000, 040000, 050000, bootstrap ou 190000. Nenhuma criação automática de conta, organização, representação ou atribuição.
| Ficheiro | Alteração adicional face à base ativa |
| --- | --- |
{changes}
Configuração: uma flag. PHP/Apache, certificados, URL HTTPS, cookie `portal_investidor_https`, cifra AES-256-CBC, chaves atuais/anteriores, serviços ClamD/worker, limites, ACLs, Composer/vendor e dependências permanecem iguais. Zero novas tabelas/colunas. A configuração do destino está sem cache: conservar esse estado. Não introduzir `config:cache` ou `key:generate`.
## Provas e backup correspondentes
Reutilizadas as seis provas HTTP/persistência, os oito testes de registo/email e o Chromium dashboard → atribuição visível → atualização → nova sessão/MFA → recuperação. A prova HTTP de Validador/Publicador usa `organization_id=null` e não cria organização/representação: nenhum comprovativo de Aurora é requisito desses dois grants. Operações e anexos permanecem fechados. A prova de navegador é da conta sintética QA; não prova atribuições destas pessoas reais.
Ensaio adicional em árvore QA privada: base → ZIP com flag OFF → repetição do mesmo ZIP sem ações de dados → administração ON/operações OFF → contenção OFF → reposição do código e manifesto base. Middleware novo carregável; administração e operação usam gates separados. Conservados recursos com nomes por hash para abas anteriores. Estado de configuração sem cache confirmado. Nenhum ensaio A/B ou C37 foi repetido.
Backup do destino `investemoz:5432` concluído em **{b['verified_at']}**, renovação necessária após **{b['valid_until']}** ou alteração pertinente de contas/autorizações antes da intervenção. Restaurado somente em **{b['restored_target']['database']}:55441**: 295 tabelas por linhas/bytes, 31 sequências, **{b['file_count']} ficheiros**, quatro originais, dois conjuntos MFA legíveis, zero ficheiros em falta. O destino não foi restaurado nem escrito. Dump SHA-256 `{b['dump_sha256']}`; arquivo privado SHA-256 `{b['files_sha256']}`. Arquivos com segredos permanecem privados, fora do ZIP de disponibilização.
O backup anterior tinha 288 tabelas antes de 030000/040000; as sete tabelas acrescentadas legitimamente explicam as 295 atuais. A contagem de 1291 ficheiros inclui agora fontes `resources` para reversão desta interface; não deve ser comparada como se o âmbito de 867 ficheiros anterior fosse igual. Serviços não mudam neste delta e o arquivo B1/B2 anterior permanece preservado.
## Sequência única de disponibilização, após autorização
1. Conferir o ZIP, todos os hashes do manifesto e os nove hashes anteriores no destino. Confirmar URL/base/portas, 030000/040000/050000 presentes, dupla 2/13 preservada, COUNTRY02 ON e operações OFF. Nova leitura dos emails sem distinção de maiúsculas preserva contas criadas entretanto. Havendo mudança de pessoas, grants ou organizações desde o snapshot, renovar o backup/restauro antes de escrever. Uma sessão normal nova não constitui divergência funcional.
2. Copiar os recursos com nomes por hash e os nove ficheiros previstos. Trocar `public/build/manifest.json` por último. Conservar recursos antigos para pedidos de abas anteriores. Não escrever em vendor, armazenamento documental, chaves, TLS ou base. Evitar servir uma combinação parcial durante a troca; usar uma janela curta de indisponibilidade do portal, preservando PostgreSQL/scanner/worker.
3. Conferir hashes posteriores e manifesto; confirmar que a flag ainda está OFF. Se houver divergência, conter e repor código/bundle base antes da abertura. Não prosseguir para atribuições.
4. Definir exclusivamente a nova flag ON, mantendo as outras flags OFF. Preservar o modo sem cache. Se o PHP web mantiver código anterior, recarregar normalmente o Apache pelo Laragon e conferir os vhosts; não terminar processos ou alterar serviços para contornar a verificação.
5. Um titular 2 ou 13 entra em `https://portal-investidor.test/login` com o seu MFA recente e abre, pelo dashboard, **Gestão de perfis**, `https://portal-investidor.test/perfis/gestao`. Conferir dupla, ausência de acessos adicionais e lista atual. Conta comum não pode abrir administração. `/minhas-oportunidades` continua indisponível com a flag operacional OFF. Não atribuir pessoas fictícias para demonstrar.
6. Registar resultado, hashes, flags e verificação no destino na mesma matriz. O sucesso deste delta é permitir preparar atribuições; não declarar o workflow ou os papéis reais habilitados só pela troca de código.
## Preparação em paralelo e atribuições por pessoa
| Pessoa | Própria preparação | Ação autenticada quando cumprir os requisitos | Dependência |
| --- | --- | --- | --- |
| Aurora Chachuaio — Aurora.chachuaio@tempo.co.mz | Registo, email confirmado e MFA próprio | Responsável cadastra Tempo, Lda no cadastro mestre; Aurora apresenta representação/comprovativos e pedido; titular independente decide o Promotor nas seis categorias | Cadastro/representação e envio próprio de comprovativos ainda precisam de implementação QA. Não atribuir por SQL ou grant direto |
| Palmira Uamusse — Palmira.matsinhe@tempo.co.mz | Registo, email confirmado e MFA próprio | Titular com MFA recente atribui `validator`, seis categorias, `organization_id=null`, motivo que cite a designação | Só os requisitos da conta/papel e disponibilização desta administração. **Não depende de Tempo/Aurora/comprovativos de Promotor** |
| Mario Melembe — mario.melembe@tempo.co.mz | Registo, email confirmado e MFA próprio | Titular com MFA recente atribui `publisher`, seis categorias, `organization_id=null`, publicação pública após aprovação independente | Só os requisitos da conta/papel e disponibilização desta administração. **Não depende de Tempo/Aurora/comprovativos de Promotor** |
As três pessoas podem começar agora em https://portal-investidor.test/register, confirmar o próprio email e configurar MFA em https://portal-investidor.test/settings/security. Não devem aguardar umas pelas outras. Consulta em {load(P/'onboarding-designations-readonly.json')['at']}: os três emails ainda não existem. Nenhuma mensagem foi enviada nesta preparação. STARTTLS/AUTH SMTP anterior comprova conectividade, não entrega real de email.
Usar os IDs reais recuperados quando existirem; nunca usar IDs QA por coincidência. Em `/perfis/gestao`, selecionar Papel, Âmbito organizacional “Todas”, **somente seis categorias base**: projeto de investimento, imobiliário, direitos sobre terra, venda de negócio, parceria de negócio e franquia. Não adicionar responsabilidades de coordenação/recurso/suplência nem prazos por suposição. O formulário envia `POST /perfis/atribuicoes`; antes de repetir um envio com resultado desconhecido, recuperar a lista e confirmar se a atribuição foi gravada. Não prometer idempotência de grants: este endpoint não tem chave de operação; a repetição idempotente demonstrada é do pedido de Promotor.
Confirmar email/MFA do destinatário no servidor e impedimento de autoatribuição. Recuperar cada grant após atualização e nova sessão; verificar `role_granted` na auditoria. Âmbito global não autoriza rever autoria/coedição ou organização representada, nem permite publicação por quem aprovou a mesma versão. Aurora recebe representação da Tempo apenas com decisão documentada pelo mecanismo legítimo; Palmira/Mario não recebem essa representação por vínculo laboral.
## Limitações e abertura posterior
Este delta não entrega cadastro autenticado de organização/representação nem upload próprio de comprovativos pré-Promotor. Esses componentes continuam no mesmo backlog, reutilizando `organizations`, `memberships` e o processamento documental existente. Não apresentar o percurso de Aurora como executável completo. A disponibilização da administração e as atribuições independentes de Palmira/Mario podem avançar sem ele.
Os fluxos das seis categorias só serão habilitados depois das atribuições operacionais e representação pertinentes, e da verificação do âmbito do processo que efetivamente será aberto. A designação e a atribuição preparatória não abrem operações automaticamente. Primeiro processo legítimo e processamento documental real no destino continuam pendentes. Restauração e partilha individual OFF; 190000, cartografia, legado, experimentais, ofertas, competências modulares e SLA são frentes separadas. COUNTRY02, A/B e C37 conservam os seus fechos anteriores.
## Critérios de sucesso e reversão
Sucesso técnico: hashes correspondentes, HTTPS/cookie/cifra preservados, COUNTRY02 ON, administração acessível a titular com MFA recente pelo dashboard, utilizador sem privilégio recusado, operações/anexos fechados, zero migrações e zero criação automática. Sucesso por pessoa é separado: contacto/MFA próprios, grant correto, recuperação e auditoria, sem conflito de interesse.
Conter falha desligando apenas `PORTAL_PROFILE_ADMINISTRATION_ENABLED`, conservando operações OFF, COUNTRY02, schema, chaves e histórico. Para reposição do código, recuperar os oito ficheiros anteriores e o manifesto/bundle base do backup privado; retirar apenas o novo middleware se estiver sem referência e corresponder ao hash previsto. Nunca apagar uploads, grants ou auditoria para simular reversão. Grants incorretos exigem revogação autenticada com motivo e auditoria; restauro completo da base depende de decisão separada. O ensaio de reposição foi feito apenas em QA. Não exigir aos titulares uma sequência de testes de infraestrutura já coberta; a intervenção humana após disponibilização é o próprio login/MFA e a decisão dos acessos legítimos.
'''
(O/'procedimento-disponibilizacao.md').write_text(doc,encoding='utf-8')
marker='<!-- onboarding-release-parallel-20261011 -->'
block=f'''{marker}
## Preparação independente dos papéis e disponibilização do delta — 11/10/2026
Delta `41a5ce778932` e ZIP original preservados. Backup do destino pós-030000/040000 renovado: 295 tabelas, 31 sequências, 1291 ficheiros, quatro originais e dois conjuntos MFA legíveis; restauro somente em clone QA 55441. Ensaio QA de troca de código/bundle, flags, repetição de ZIP e reversão passou. Disponibilização da administração ainda não executada.
Palmira e Mario dependem de contacto/MFA próprios e atribuição autenticada nos seis tipos; **não dependem dos comprovativos de Aurora nem de representação na Tempo**. Aurora tem frente própria: organização legítima, representação, envio de comprovativos e aprovação independente. Faltam as vias autenticadas de criação/representação e upload pré-Promotor. Operações OFF até às condições de abertura, sem reabrir COUNTRY02/A/B/C37.
Procedimento, limitações e reversão: `outputs/investmoz-operational-onboarding-20261011/procedimento-disponibilizacao.md`. O relatório inicial e os blocos seguintes permanecem como histórico; a numeração anterior não cria uma dependência entre estes papéis.
'''
for file in ['docs/investmoz/v2/auditoria-portal/backlog.md','docs/investmoz/v2/auditoria-portal/perfis-v1-reconciliacao.md']:
dest=R/file
old=dest.read_text(encoding='utf-8')
assert marker not in old
dest.write_text(block+old,encoding='utf-8')
csv.field_size_limit(10_000_000)
with (R/'docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv').open(encoding='utf-8-sig',newline='') as f: rows=list(csv.reader(f))
h=rows[0];c=h.index('Evidência funcional')
additions={
'AUD-PROFILE-01':'Confirmação 11/10: Palmira/Validador e Mario/Publicador, seis tipos base e todas as organizações, têm preparação/atribuição independente dos documentos ou representação de Aurora. Requisitos próprios: contacto confirmado, MFA configurado e confirmado, atribuição por titular independente com MFA recente e motivo. As três contas reais ainda ausentes na leitura 19:00:58 UTC; nenhuma atribuição automática. Operações continuam OFF. outputs/investmoz-operational-onboarding-20261011/procedimento-disponibilizacao.md',
'AUD-PROFILE-03':'Dependências específicas de Aurora mantidas: cadastro mestre Tempo, Lda, representação legítima, via própria de comprovativos pré-Promotor e revisão independente ainda pendentes. Não bloquear a atribuição de Validador/Publicador por estas lacunas. O delta de preparação não acrescenta estas vias nem declara este percurso concluído. outputs/investmoz-operational-onboarding-20261011/procedimento-disponibilizacao.md',
'AUD-PROFILE-31':f'Delta 41a5ce778932/ZIP 505ab7293423 preservados. Backup destino {b["verified_at"]}, pós-030000/040000/050000: 295 tabelas/31 sequências/1291 ficheiros/4 originais/2 MFA legíveis, zero ausentes; restauração apenas em clone 55441. Ensaio QA base→ZIP/flag OFF→repetição de ZIP→admin ON/ops OFF→contenção→reversão passou, sem mudança no ativo. Procedimento exato: nove ficheiros/159 assets e apenas flag de administração ON, sem migrations ou serviços; COUNTRY02 ON. Autorização de disponibilização e contas/grants reais ainda pendentes. outputs/investmoz-operational-onboarding-20261011/backup-restauro-renovado.json; procedimento-disponibilizacao.md; ensaio-disponibilizacao-reversao.json'
}
updates={row[0]:{'Evidência funcional':row[c]+'\n\n'+additions[row[0]]} for row in rows[1:] if row[0] in additions}
assert len(updates)==3
(P/'onboarding-release-matrix-baseline.json').write_text(json.dumps({'rows':rows,'updates':updates},ensure_ascii=False),encoding='utf-8')
wrapper={'at':datetime.now(timezone.utc).isoformat(),'delta_sha256':m['delta_sha256'],'base_candidate':'41caea0f900f',
'package':m['package'],'package_sha256':m['package_sha256'],'original_manifest_sha256':sha(O/'manifesto.json'),
'files':m['files'],'assets_count':len(m['assets']),'production_manifest_sha256':m['production_manifest_sha256'],
'activation_state':'prepared_not_authorized_not_activated','requested_enablement':{'PORTAL_PROFILE_ADMINISTRATION_ENABLED':True,'PORTAL_PROFILE_ACCESS_ENABLED':False},
'COUNTRY02_enabled_preserved':True,'migrations_required':[],'service_changes':[],'new_accounts_organizations_memberships_grants':0,
'backup_verified_at':b['verified_at'],'backup_valid_until':b['valid_until'],
'documents':{name:sha(O/name) for name in ['procedimento-disponibilizacao.md','backup-restauro-renovado.json','ensaio-disponibilizacao-reversao.json','destino-leitura-pre-disponibilizacao.json','designacoes-leitura-atualizada.json']}}
(O/'manifesto-disponibilizacao.json').write_text(json.dumps(wrapper,indent=2,ensure_ascii=False),encoding='utf-8')
print(json.dumps({'result':'release_review_prepared','delta':m['delta_sha256'][:12],'backup_tables':len(b['tables']),
'backup_files':b['file_count'],'matrix_changes_prepared':3,'active_writes':0}))