"""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 = '' 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__')