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

196 lines
27 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Publish sanitized QA evidence into the existing audit; never reads actor credentials."""
import json
import hashlib
import re
import sys
import xml.etree.ElementTree as ET
from datetime import datetime, timezone
from pathlib import Path
sys.stdout.reconfigure(encoding='utf-8')
ROOT = Path(__file__).resolve().parent.parent
DOCS = ROOT / 'docs/investmoz/v2/auditoria-portal'
PRIVATE = ROOT / 'storage/app/private/investmoz-v2/portal-audit'
def read(name):
return json.loads((PRIVATE / name).read_text(encoding='utf-8-sig'))
proof = read('opportunity-workflow-http-qa.json')
extended = read('opportunity-workflow-extended-http-qa.json')
multiple = read('opportunity-workflow-multirole-http-qa.json')
territory_tasks = read('opportunity-workflow-territory-tasks-http-qa.json')
verification = read('opportunity-workflow-verification-qa.json')
preserved = read('profiles-v1-preservation-qa.json')
counts = read('contagens-requisitos.json')
projection = read('pedidos-abertos-projection.json')
assert all(p['result'] == 'passed' and p['active_writes'] == 0 for p in [proof, extended, multiple, territory_tasks])
assert counts['requisitos'] == projection['canonical_requirements'] == 3386
assert counts['linhas_origem'] == projection['source_occurrences'] == 6838
assert all(preserved['previous_rows_preserved_by_id_and_original_columns'].values())
assert preserved['active_manifest_preserved'] and preserved['mfa18_unchanged']
test = ET.parse(PRIVATE / 'opportunity-workflow-final-tests-qa.xml').getroot().find('testsuite').attrib
assert test['errors'] == test['failures'] == '0'
manifest = hashlib.sha256((ROOT / 'public/build-qa-opportunities/manifest.json').read_bytes()).hexdigest()
stamp = datetime.now(timezone.utc).isoformat(timespec='seconds')
def prepend(path, content):
begin = '<!-- qa-workflow-20261010:begin -->'
end = '<!-- qa-workflow-20261010:end -->'
text = path.read_text(encoding='utf-8')
text = re.sub(re.escape(begin) + r'.*?' + re.escape(end) + r'\s*', '', text, flags=re.S)
title, _, rest = text.partition('\n')
block = '\n\n' + begin + '\n' + content.strip() + '\n\n'
block += '**Leitura temporal:** as secções seguintes preservam relatórios anteriores e respetivas evidências. Afirmações antigas sobre submissão sempre bloqueada, Validador 24 revogado, conta 29 apenas de bootstrap, contagens ou fingerprints não descrevem este incremento. O estado atual está acima e em [workflow-oportunidades-qa.md](workflow-oportunidades-qa.md).\n' + end + '\n\n'
if path.parent != DOCS:
for target in ['workflow-oportunidades-qa.md', 'matriz-requisitos.csv']:
block = block.replace('](' + target + ')', '](auditoria-portal/' + target + ')')
path.write_text(title + block + rest.lstrip('\n'), encoding='utf-8')
states = '\n'.join(f'| {state} | {value["requisitos"]} | {value["linhas_origem"]} |' for state, value in counts['por_estado'].items())
groups = '\n'.join(f'| {name} | {value} |' for name, value in projection['group_counts'].items())
evidence = f'''## Incremento canónico e workflow em QA — 10/10/2026
Servidor **http://127.0.0.1:18097**; destino guardado **desk_restore_20261009_174726_test:55441**. Zero escritas na base ativa. **C37 e toda a aceitação visual deste incremento permanecem pendentes.** Props da interface, API e persistência foram verificadas; nenhum clique ou renderização no navegador foi observado.
| Requisito na matriz única | Funciona / prova concreta em QA | Falta para fechar |
| --- | --- | --- |
| AUD-OP-04; AUD-PROFILE-28 | Dez categorias guardam rascunhos incompletos e recuperam noutra sessão MFA. Seis passam o contrato estrutural: investimento, imobiliário, DUAT, venda, parceria e franquia. Adapter do mesmo schema de 110 definições; condições `allOf` originais recuperadas, IDs existentes mantidos. | Semântica e editores dos objetos incompletos do §25; contrato Response integral; quatro perfis modulares aprovados; demonstração visual por categoria. |
| AUD-OP-03; PROFILE-08/11/12 | Promotor 21 criou a oportunidade **18**, submeteu; Validador 24 solicitou correção; 21 corrigiu/resubmeteu; 24 reviu gates e aprovou; Publicador 25 publicou com visibilidade interna. O número 18 identifica a oportunidade, não a conta do investidor C37. | Visual integral, todas as categorias, pareceres/incidentes e coordenação editorial completa. |
| PROFILE-09/10/12/21 | Imobiliário **19**: rejeição fundamentada por 24 → recurso reaberto pelo revisor independente 29 → nova aprovação 24 → publicação pública 25 → alteração privada sem alterar a ficha pública → suspensão com GET público 404. Autor Prom+Val+Pub e aprovador Val+Pub receberam 403. | Visual, filas institucionais e competências reais; suspensão/retoma e demais ações editoriais por todos os perfis. |
| PROFILE-19/21/22 | Tarefas existentes em `tasks`, vínculo à oportunidade por origem/ID/correlação; atribuição explícita, motivos e decisões recuperáveis. Correção/rejeição concluem revisão; resubmissão não herda atribuição; suspensão conclui tarefas pendentes. Recurso do próprio rejeitor e membro da organização negados. SLA `unconfigured`, responsável e prazo iniciais nulos. | Filas completas, reatribuição/disponibilidade, coordenação editorial e política SLA aprovada. Não foram inventados responsáveis nem prazos. |
| COUNTRY-01; GEO-03; OP-02 | Localização MZ/Cabo Delgado/distrito e documentos ligados ao mesmo contrato recuperados nas seis categorias. Oportunidade **28** publicou usando a localização principal mesmo em segundo lugar na lista. Documentos e imagem/duas derivadas verificados com ClamD real e hashes correspondentes. DUAT **20** aprovado internamente não publicou: 422 por autorização externa pendente. | Outros países/formulários, origem/filtros/estatísticas transversais, mapas renderizados, 188 correspondências cartográficas e operações documentais restantes. |
| PROFILE-30; DASH-01 | Conta **Promotor QA — conta sintética 21**, MFA normal confirmado, organização e tipos autorizados. Dashboard devolve navegação do Promotor. Permissões temporárias dos testes foram revogadas e uma nova sessão 21 recuperou o dashboard. | Dashboard → formulário → guardar → nova sessão → workflow e resultado visíveis. Auditor ou Superutilizador não substituem este ensaio. |
Os requisitos acima continuam **parciais**: nenhum passou a concluído pelo incremento. O detalhe está no [relatório do workflow](workflow-oportunidades-qa.md); fontes, versões, critérios e percursos permanecem na [mesma matriz](matriz-requisitos.csv).
| Estado canónico | Requisitos / linhas da matriz | Ocorrências de origem |
| --- | ---: | ---: |
{states}
Total: **3.386 requisitos = 3.386 linhas**, **6.838 ocorrências**, **3.380 requisitos abertos**. As referências e versões anteriores foram preservadas.
'''
backlog = f'''## Próximo trabalho executável — incremento de 10/10/2026
| Prioridade / requisito | Correção concreta seguinte | Critério de fecho e dependência |
| --- | --- | --- |
| P0 — UI-01/C37; DASH-01/C43 | Incorporar ensaio visual com Promotor 21 e, separadamente, investidor 18; registar falhas com URL, ação, estado e resposta sanitizada. | Login/MFA → dashboard → guardar → logout → nova sessão → recuperar, sem loop, perda ou acesso indevido; não substituível por HTTP. |
| P1 — OP-04; PROFILE-28 | Completar editores e validações semânticas §25: grelhas de serviços/ónus, avaliação/documento, ocupação/título/cadastro, comunidades/consulta/benfeitorias, financeiros/beneficiário efetivo, NDA, governação e franquia; completar Response. Reutilizar contrato e entidades atuais. | Rascunho parcial recuperável; cada condição aplicável recusa dados inválidos e aceita dados/evidências completos; interface por categoria. Confirmar correspondências de objetos opacos do schema, sem alterar os significados legados silenciosamente. |
| P1 — OP-03; PROFILE-11/12/19/21 | Completar filas/revisões e editoriais, reatribuição, pareceres, incidentes e recurso; manter hash da versão e independência. | Atores distintos e responsáveis explicitamente designados, motivos/evidência e histórico; revogação reavaliada. Ciclo principal já demonstrado por HTTP, não repetir o inventário. |
| P1 — COUNTRY/STATS/GEO | Completar cobertura por formulário/consumidor, mapas e nomes cartográficos pendentes. | País/origem persistidos → recuperação → filtros/estatísticas; território cadastrado ≠ geometria ≠ mapa apresentado. 393 limites privados não encerram integração visual; faltam 31 distritos e 157 postos. |
| P1 — OP-02/LIB/PROFILE-23 | Verificar tipos/partilhas/PDFs e derivadas ainda sem prova específica e autorizações do original. | ClamD real separado por operação; hashes/originais preservados; acesso permitido/negado e resultado visível. PNG/documentos do ciclo e duas derivadas já demonstrados. |
| P1 — PROFILE-23 (achado adicional) | Composer audit apontou dois avisos preexistentes em `league/commonmark`: GHSA-97jj-33gv-5xf9 e GHSA-3q6v-r5mr-hxv8. Identificar consumidores/configuração e preparar correção compatível em QA. | Prova local `workflow-composer-audit.json`; avaliar aplicação dos avisos e validar os consumidores afetados. Não atribuir aos três pacotes Opis nem declarar exploração demonstrada. |
| P2 — PROFILE-22/31 | Configuração SLA versionada, instalaçao autenticada de perfis/dupla e preparação de ativação. | Decisões reais explícitas; ensaio backup/restauro da candidata posterior à visual; nenhum prazo ou titular real escolhido por QA. |
**Decisões concretas:** (1) quais versões imutáveis de perfil, campos, documentos e gates são aprovadas para concessão, financiamento, fornecimento e outros? (2) qual política versionada fixa prazos, tolerância, ciclos e escalamento? (3) quem assume formalmente as responsabilidades institucionais e os exatamente dois superutilizadores ativos? Não bloqueiam as correções independentes, nem foram inferidas dos atores sintéticos.
| Grupo dos pedidos abertos (projeção da mesma matriz) | Requisitos |
| --- | ---: |
{groups}
Lista integral: **http://127.0.0.1:18097/qa-auditoria/pedidos-abertos**, sob MFA de gestor/Auditor/Superutilizador QA. Cada cartão conserva o estado canónico, fontes, lacuna e próxima ação. “Preparado em QA” indica um escopo provado, não conclusão total.
'''
activation = '''## Sequência reconciliada de disponibilização futura — não executada
Leitura ativa de 10/10: PostgreSQL `investemoz:5432`, transação READ ONLY, seis oportunidades publicadas; 020000 instalada, 030000/040000/percurso ausentes, flags desativadas, zero geometrias. Build ativo preservado. A revisão candidata **não está fixada**; `ready=true` e fingerprints anteriores não autorizam nem demonstram instalação.
| Ordem | Ação e efeito previsto | Dependência / reversão |
| --- | --- | --- |
| 0 | Incorporar visual C37/C43/A/B e workflow, corrigir falhas; fixar revisão, configuração e manifestos. Renovar backup DB + ficheiros privados/originais + chave estável e provar restauro numa nova QA isolada. | Provas anteriores preservadas, sem as apresentar como restauro desta revisão. Não gerar nova chave. |
| 1 | Disponibilizar código e dependências compatíveis, flags desligadas. Composer adiciona `opis/json-schema 2.4.1`, `opis/string 2.1.0`, `opis/uri 1.1.0`; zero atualizações/remoções preexistentes. | Não importar contas, classificações, contactos ou documentos sintéticos QA. Reverter código/assets compatíveis, preservando payloads/histórico. |
| 2 | Não repetir 020000. Se 030000 continuar ausente, usar prévia/aplicação autenticada `/management/opportunity-resources-install`: três tabelas, uma coluna documental, uma função e três triggers previstos. | Backup/restauro confirmado; hashes/contagens verificados no estado real; não publica nem habilita o percurso. |
| 3 | Preparar e testar o mecanismo autenticado 040000: quatro tabelas, uma coluna de utilizador, três de oportunidade, uma função e dois triggers previstos; dupla inicial/substituição independente. | Aplicação atual demonstrada exclusivamente QA. Instalação/bootstrap ativo autenticado ainda pendente; titulares reais não definidos. Não remover privilégios ativos automaticamente. |
| 4 | Configurar referências/contactos/hierarquia, perfis/tipos/representação, responsabilidades e MFA; quatro contratos modulares apenas após aprovação da versão. | Reutilizar dados mestres reais. As referências sintéticas de ensaio não resolvem cadastros ativos vazios. SLA permanece indeterminado até política aprovada. |
| 5 | Importação cartográfica autenticada, privada e idempotente; se ativo permanecer zero e correspondências iguais, até **393 = 11 províncias + 128 distritos + 254 postos**. Esperados 581 = 11/159/411; pendentes 188 = 0/31/157, nomes no relatório de correspondências. | Prévia → correspondências inequívocas → scan/hash → aplicar → auditoria; nenhuma localização/conta/perímetro de projeto inventado. Reversão pelo lote preserva origem/uso/histórico. **Publicação de camadas é ação separada.** |
| 6 | Disponibilizar flags/assets/navegação aprovados e o percurso I1–I3 pela instalação existente, sem repetir ações já presentes. Publicar cada oportunidade separadamente, com aprovação/hash/prévia/âmbito. | Cinco fases/19 etapas preservadas, dossier privado separado. Voltar flags a false limita disponibilização; manter guardas de confidencialidade para novos dados. Não executar down destrutivo após uso. |
Após uso, reverter por configuração compatível, suspensão/arquivo autorizado ou compensação auditada; não eliminar decisões, documentos, consentimentos, relações ou originais. Restauro global exige reconciliar alterações posteriores ao backup. Este plano não contém aprovação de ativação nem declaração de instalação ativa.
'''
manual = '''## Ensaio manual atual — Promotor QA e workflow
**URL inicial exato: http://127.0.0.1:18097/dashboard. Conta: Promotor QA — conta sintética 21.** Se necessário, o login normal é `/login`, com MFA Fortify. Credenciais e códigos permanecem exclusivamente no ficheiro privado local; não os copiar para a conversa. Conferir identificação QA/porta 18097; não usar `portal-investidor.test`.
1. Entrar como Promotor 21, concluir MFA e atualizar dashboard. Esperado: “Minhas oportunidades” e “Nova oportunidade”, sessão mantida. Um retorno a login entra como reprodução pendente, não como sucesso.
2. Pelo dashboard, clicar “Nova oportunidade”, escolher organização QA representada e categoria imobiliária; criar e guardar **rascunho incompleto**, incluindo um campo opcional limpo. Esperado: ID, estado rascunho e ausência de bloqueio por campos ainda não preenchidos.
3. Anotar o ID sem copiar informação privada; logout, nova sessão/MFA, dashboard → “Minhas oportunidades” → mesmo ID. Esperado: recuperação exata, organização/autor preservados. Submeter incompleto deve mostrar campos pendentes e manter o rascunho.
4. Abrir recursos pelo formulário: MZ → Cabo Delgado → distrito válido → principal; guardar/reabrir. Esperado: mesmos IDs/nome, filhos coerentes; mapa administrativo quando disponível. Não usar limite administrativo como geometria de parcela. Carregar documento sintético permitido; confirmar scan/recuperação e vínculo ao mesmo registo.
5. Completar núcleo e módulo imobiliário com dados explicitamente sintéticos. O catálogo de ensaio possui sector/subsector/atividade QA; contacto é da organização QA. Usar `sale`, `warehouse`, `completed`, condição `good`, áreas positivas, moeda/preço positivo, ocupação/título/ónus/avaliação e licença aplicável; documento e localização selecionados no contrato. Preencher resumo/pedido/evidências e declaração de verdade. Para este primeiro ciclo usar visibilidade **internal**. Os editores de objetos/grelhas ainda incompletos devem ser registados como falhas: não contornar pela consola nem fingir visual concluída.
6. Guardar com motivo → submeter. Esperado: `/view`, estado submetido, formulário bloqueado, snapshot/versionamento e tarefa de revisão sem responsável/prazo inventado. A rota `/edit` fechada não deve ser usada para continuar revisão.
7. Noutra sessão QA entrar como **Validador 24**; abrir o mesmo ID autorizado, atribuir/iniciar revisão, solicitar correção com motivo e campo identificado. Esperado: necessidades de informação, motivo recuperável e tarefa de correção.
8. Voltar como 21: abrir mesma oportunidade, corrigir, guardar com motivo e resubmeter. Logout/nova sessão deve recuperar a versão corrigida sem perder a decisão anterior.
9. Como 24: iniciar a revisão dessa versão, registar os gates aplicáveis com decisão/evidência/fundamento; aprovar. Esperado: versão/hash aprovados, aprovação prematura recusada.
10. Como **Publicador 25**: abrir `/view`, rever prévia, confirmar âmbito e publicar. Esperado: publicado interno, sem exposição na API pública. Para o ensaio público separado, preparar capa, legenda/alt/direitos e duas derivadas analisadas; confirmar prévia/hash e publicar explicitamente.
11. Como 21, solicitar alteração material com fundamento, editar a proposta privada. Esperado: nova revisão sem aprovação reutilizável; ficha pública anterior permanece intacta. Como 25 suspender com motivo; esperado GET público 404 e história preservada.
12. Em registo separado, como 24 rejeitar com fundamento; como 21 pedir recurso; como **revisor independente 29** reabrir. Esperado: rejeição original conservada. Rejeitor e membro da organização devem ser recusados. Conta 31 com papéis acumulados/Auditor continua sem escrita. Não atribuir responsabilidades reais às contas sintéticas.
Recolher por passo apenas hora UTC, perfil/ID, URL sem tokens, ação, resultado visível e referência do registo. Omitir Cookie, X-XSRF-TOKEN, passwords, segredo/código MFA e documentos privados de capturas. C37/investidor 18 e cenários A/B continuam no roteiro existente, separados deste ensaio. Sucesso HTTP abaixo não certifica nenhum dos passos visuais.
'''
cases = '''## Reproduções adicionais do incremento — 10/10/2026
| Caso / requisito | Reprodução e esperado | Falha ou risco encontrado / causa | Correção e evidência separada |
| --- | --- | --- | --- |
| C44 (complemento) — OP-04/PROFILE-28 | Guardar rascunho especializado parcial → completar → submeter; condições da categoria devem ser verificadas. | Submit permanecia bloqueado pelo contrato não integrado; a extração dos Create não conservava as condições `allOf` do schema original. | Adapter recupera condições, usa validação Draft2020-12 e recursos existentes. Dez rascunhos recuperados; seis submissões aceites/ quatro recusadas por perfil. Semântica integral e visual pendentes. |
| C50 — OP-04 | Imobiliário `sale`, `price:null`: guardar rascunho permitido, submeter recusado. | Campo nullable e `required` não garantiam preço positivo para venda. | Regra explícita positive; oportunidade19: draft aceite, submit422, preço corrigido e submit200. |
| C51 — PROFILE-25 | Após submissão, continuar pela visualização; edição deve ficar fechada. | Código da ação recarregava `/edit`, que passa corretamente a403 após submissão; o primeiro acesso HTTP Validador a essa rota confirmou a restrição, não uma falha de autorização. | Ações navegam `/view`; edição privada tem link apenas autorizado. API/props/build verificados; transição visível ainda pendente. |
| C52 — PROFILE-08/12/25 | Publicada → pedido de alteração → pesquisar título privado/estado → recuperar; ficha pública não pode mudar antes de aprovação. | Consulta/contagens usavam só título e estado públicos, ocultando a posição da proposta privada. | Consulta já autorizada usa estado/título de trabalho, preservando publicação. Teste específico do filtro/contagem e HTTP público19 original até suspensão. Visual pendente. |
| C53 — GEO-03/PROFILE-12 | Duas localizações, principal em segundo lugar → aprovar/publicar; território apresentado deve ser o principal. | Revisão de código encontrou uso da primeira posição da lista, que não define a principal. | Filtra `is_primary`; HTTP oportunidade28 apresenta ID/distrito principal correto. Teste específico isolado; mapa visual pendente. |
| C54 — PROFILE-19 | Revisão → correção → resubmeter; ou revisão → rejeitar. Esperado: tarefa anterior concluída, nova tarefa sem atribuição presumida. | Código mantinha revisão antiga aberta e podia herdar atribuição numa nova versão. | Conclui tarefas com motivo/histórico; limpa atribuição na submissão. HTTP28: correção/resubmissão/publicação/suspensão, nenhuma tarefa aberta ao final; HTTP29: rejeição fecha revisão. |
| C37 / C43 | Login investidor18; dashboard Promotor21 e ciclo de ações. | Não houve demonstração visual atual no navegador. | Mantidos pendentes, roteiro e conta preparados; não fechar por HTTP, props ou total de testes. |
Rejeições esperadas adicionais: motivo curto422, confirmação de publicação falsa422, hash de prévia errado409, self/multirole403, recurso pelo rejeitor/organização403, publicação fundiária com autorização externa pendente422. Provas privadas específicas preservam sequência/estado, sem segredos.
'''
for name, body in [('relatorio-executivo.md', evidence), ('backlog.md', backlog), ('plano-ativacao.md', activation), ('casos-reproducao.md', cases),
('roteiro-manual-perfis-v1.md', manual), ('perfis-v1-reconciliacao.md', evidence),
('cobertura-percursos-i0-i3.md', evidence), ('execucao-percurso-i1-i3.md', evidence)]:
prepend(DOCS / name, body)
prepend(ROOT / 'docs/investmoz/v2/gestao-oportunidades-dashboard.md', manual + '\n\n' + evidence)
details = f'''# InvestMoz — contratos e workflow de oportunidades em QA
Atualizado em **{stamp}**. Incremento da mesma auditoria e matriz; não cria inventário paralelo. Este relatório discrimina **código preparado**, **HTTP/persistência demonstrados em QA** e **funcionalidade ativa**.
{evidence}
## Provas reproduzíveis e limites
| Prova privada local (sem credenciais) | Percurso / camada |
| --- | --- |
| `opportunity-workflow-http-qa.json` | {len(proof['events'])} pedidos ao servidor 18097, Fortify/MFA normal; dez categorias, gravação/recuperação noutra sessão, ciclo investimento18, correção/gates/aprovação/publicação interna; rejeição/negativas iniciais. |
| `opportunity-workflow-extended-http-qa.json` | {len(extended['events'])} pedidos; recurso29, imobiliário19 público com ClamD/derivadas, confirmação/hash, proposta privada preserva público, suspensão404; fundiário20 autorizado internamente mas publicação422. |
| `opportunity-workflow-multirole-http-qa.json` | {len(multiple['events'])} pedidos; autor Prom+Val+Pub403, aprovador Val+Pub403, Auditor acumulado403, três atribuições temporárias revogadas; nova sessão Promotor21 normal. |
| `opportunity-workflow-territory-tasks-http-qa.json` | {len(territory_tasks['events'])} pedidos; oportunidade28 localiza principal em segundo lugar, conclui revisão por correção, resubmete sem atribuição herdada e suspende sem tarefas abertas; oportunidade29 conclui tarefa com rejeição. |
| `opportunity-workflow-verification-qa.json` | Releitura atual do contrato e referências: seis passam, quatro sem perfil; país/localização e hashes documentais. O total territorial inclui fixture QA isolada e não prova cobertura real. |
| `opportunity-workflow-final-tests-qa.xml` | {test['tests']} testes / {test['assertions']} asserções, zero erros/falhas; testes específicos de estados, binding estrangeiro, integridade de bytes, draft/null, filtros privados e independência. Fixtures de política não são prova de ClamD real. |
| `profiles-v1-preservation-qa.json` | Registos anteriores de 18 conjuntos conservados por ID/colunas originais; MFA18 e build ativo preservados. Captura {preserved['captured_at']}. |
PHPStan dos componentes alterados, TypeScript e compilação QA passaram. Manifesto QA **`{manifest}`**; ativo **`75352185c2a85c34ab726613db864a598e301a5b342ebbc80ac8e678eed17a94`**. Nenhuma página foi renderizada/clicada no navegador deste ensaio. A extensão de fonte `source.json` mantém flags false como fotografia histórica da extração, não como configuração do runtime atual.
## Compatibilidade e contratos restantes
Fonte integral: especificação do cadastro, SHA256 `9f173ed3024d2beddef7888b692d8b81fc5e4700249955f2bae9bdd2eb2be504`; §25 campos/semântica, §26 schema, §27 gates. Artefacto normativo de 110 definições preservado, SHA256 `9ce0acfa5df1f81a3357dee9e5a9a1c4e77e4eefc8bce294497d18f4caa8e5c0`. O adapter distingue entrada parcial e submissão, liga documentos/localizações/imagens pelos IDs existentes e valida relações/scan/bytes. Snapshots, decisões, motivos e hashes usam `management_draft`, `workflow_context` e auditoria existentes.
IDs bigint de oportunidades/sectors não foram convertidos aos UUID de exemplos; não foram criados cadastros paralelos. Tarefas reutilizam `tasks`; a sua coluna `opportunity_id` aponta a oportunidades CRM UUID e não recebe o bigint do portal: a relação é explícita por source_system/external_id/correlation_id. Contactos e subsector/atividade reutilizam `contacts`, `investors`, `investor_contacts`, `economic_activities` e versões existentes. As referências acrescentadas para o ensaio são marcadas QA-synthetic, não oficiais, e não devem ser importadas no ativo.
Este incremento **não implementa integralmente** os objetos normativos descritos apenas como object, as relações SQL adicionais do §27 nem o Response integral. Validação estrutural aceita parte desses objetos sem detalhamento: exige continuação semântica antes de ativar os contratos. Aprovação interna é uma decisão identificada de QA; não confirma atos externos, autenticidade ou situação jurídica. DUAT pendente foi recusado na publicação mesmo após todos os gates internos.
O rascunho admite ausência, vazio e null nos campos ainda não preenchidos, preservando as restrições de valores fornecidos e impedindo campos readonly/verified do cliente. Contratos de concessão/financiamento/fornecimento/outros ficam em rascunho até existir perfil versionado aprovado e imutável; não foi fabricada configuração para os submeter.
{manual}
{backlog}
{activation}
'''
(DOCS / 'workflow-oportunidades-qa.md').write_text(details, encoding='utf-8')
print(json.dumps({'updated_documents': 10, 'requirements': 3386, 'source_occurrences': 6838, 'qa_visual_pending': True, 'active_writes': 0}, ensure_ascii=False))
# Preserve the latest per-category evidence when rebuilding the historical report.
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-docs.py'), run_name='__main__')