From 2afbc5c9c73122b4fd3171c372c01fe4b81e36a8 Mon Sep 17 00:00:00 2001 From: nala Date: Sat, 14 Nov 2026 19:43:51 +0200 Subject: [PATCH] docs(offers): verify live target backup and isolated recovery before install --- .../unification/traceability-and-manual.md | 3 + .../v2/auditoria-portal/matriz-requisitos.csv | 2 +- .../ofertas-aceitacao-final-20261114.md | 2 + .../ofertas-prontidao-homologacao-20261114.md | 2 + ...rtas-verificacao-preinstalacao-20261114.md | 101 ++++++++++++ .../backup-reviewed.sh | 68 ++++++++ .../backup-summary.json | 13 ++ .../constraints-automatics.json | 49 ++++++ .../data-preservation.json | 11 ++ .../executor-operations.json | 34 ++++ .../executor-status.json | 152 ++++++++++++++++++ .../preflight-reviewed.sh | 30 ++++ .../preservation-reviewed.sh | 15 ++ .../qa-result.json | 86 ++++++++++ .../restore-reviewed.sh | 80 +++++++++ .../restore-summary.json | 16 ++ .../security-runtime.json | 10 ++ .../session-reconciliation.json | 10 ++ .../target-final-preservation.json | 29 ++++ .../target-identity.json | 14 ++ .../target-runtime-schema.json | 146 +++++++++++++++++ 21 files changed, 872 insertions(+), 1 deletion(-) create mode 100644 docs/investmoz/v2/auditoria-portal/ofertas-verificacao-preinstalacao-20261114.md create mode 100644 outputs/offers-preinstall-verification-20261114/backup-reviewed.sh create mode 100644 outputs/offers-preinstall-verification-20261114/backup-summary.json create mode 100644 outputs/offers-preinstall-verification-20261114/constraints-automatics.json create mode 100644 outputs/offers-preinstall-verification-20261114/data-preservation.json create mode 100644 outputs/offers-preinstall-verification-20261114/executor-operations.json create mode 100644 outputs/offers-preinstall-verification-20261114/executor-status.json create mode 100644 outputs/offers-preinstall-verification-20261114/preflight-reviewed.sh create mode 100644 outputs/offers-preinstall-verification-20261114/preservation-reviewed.sh create mode 100644 outputs/offers-preinstall-verification-20261114/qa-result.json create mode 100644 outputs/offers-preinstall-verification-20261114/restore-reviewed.sh create mode 100644 outputs/offers-preinstall-verification-20261114/restore-summary.json create mode 100644 outputs/offers-preinstall-verification-20261114/security-runtime.json create mode 100644 outputs/offers-preinstall-verification-20261114/session-reconciliation.json create mode 100644 outputs/offers-preinstall-verification-20261114/target-final-preservation.json create mode 100644 outputs/offers-preinstall-verification-20261114/target-identity.json create mode 100644 outputs/offers-preinstall-verification-20261114/target-runtime-schema.json diff --git a/docs/investmoz/unification/traceability-and-manual.md b/docs/investmoz/unification/traceability-and-manual.md index 18a2df70..4a8a5bec 100644 --- a/docs/investmoz/unification/traceability-and-manual.md +++ b/docs/investmoz/unification/traceability-and-manual.md @@ -10211,3 +10211,6 @@ A Tempo aceitou tecnicamente `9a014268` e autorizou apenas preparação. Parecer # Menus das Ofertas uniformizados na prévia QA — 14/11/2026 Por pedido expresso da Tempo durante a conferência, a navegação privada das Ofertas reutiliza o padrão horizontal com dropdown do portal. Área de trabalho → Ofertas agrupa consulta/preparação conforme os acessos existentes; o módulo agrupa Ofertas, estados e acompanhamento em dropdowns responsivos. Mudar de estado conserva os filtros de acompanhamento/denúncias. Todas as páginas Ofertas usam o mesmo enquadramento PortalNavigation. Não foram alteradas permissões, rotas, publicação ou dados. Passaram 29 testes de navegação, tipos, lint focado e build QA; um aviso de lint preexistente fora do delta foi preservado. A lista e os dois exemplos já existentes responderam HTTP 200 com os novos assets, mantendo a sessão e os dados da prévia. Conferência visual pelo utilizador pendente; sem instalação em homologação. Recibo: `outputs/offers-menu-qa-20261114/qa-result.json`. O bundle anterior `9a014268` permanece preservado e não inclui este novo delta visual. +# Verificação real pré-instalação das Ofertas — 14/11/2026 + +Por autorização específica, destino `teste-investidor.tempo.co.mz` confirmado em `96f51096`, sem instalar Ofertas ou alterar configuração/permissões/dados operacionais. Estruturas verificadas compatíveis; GD/WebP presente em CLI/FPM e codec em memória aprovado. Backup actual privado e restauro isolado passaram: 219 tabelas, 417 ficheiros, dois conjuntos de MFA; zero originais Laravel cifrados na fonte para ensaiar. Release anterior arquivada e 9.873 ficheiros reconferidos. Após a cópia, apenas uma sessão nova, com as 51 anteriores intactas; nenhuma outra tabela divergente. Parecer APTO COM CONDIÇÕES: autorização futura com SHA expresso (base `9a014268` ou candidata com menus `3558647e`), reconferência da salvaguarda e janela sem integrações concorrentes; o executor segue o último `main`. Publicação/NDA/agendamento/correio de Ofertas mantidos fechados; visual, galeria completa, email real e decisões jurídicas permanecem separados. Relatório: `../v2/auditoria-portal/ofertas-verificacao-preinstalacao-20261114.md`; recibos sanitizados em `outputs/offers-preinstall-verification-20261114`. Não houve instalação, migração operacional, alteração de `mne` ou envio real de email. diff --git a/docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv b/docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv index 79c85604..d6c6daac 100644 --- a/docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv +++ b/docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv @@ -163,7 +163,7 @@ RF-MIN-005,"Controlar cessão, mudança de controlo e aprovações.",upgrade v4 RF-MIN-006,Pesquisar e corresponder oportunidades mineiras.,upgrade v4 SHA f0b9815…; p. 3275; requirements-audit.csv,oportunidades,/api/v1/business-opportunities; /api/v1/business-opportunities/{slug}; /investidor/terra-duat; /investidor/terra-duat/novo; /investidor/terra-duat/{id}; /investidor/terra-duat/{id}/actualizacoes; /investidor/terra-duat/{id}/documentos; /investidor/terra-duat/{id}/documentos/{attachment}; /investidor/terra-duat/{id}/editar; /investidor/terra-duat/{id}/imprimir; /investidor/terra-duat/{id}/solicitar-apoio; /management/opportunities,"OpportunityManagement, OpportunityCanonicalContract/Response, OpportunityReviewMateriality, PortalProfileAccess, OpportunityResources. Capacidade existente associada ao domínio; correspondência integral desta cláusula só quando demonstrada na prova individual.","Ativo investemoz:5432, 295 tabelas, COUNTRY02/030000/040000 instalados; seis categorias e administração habilitadas. Leitura atual não demonstra esta cláusula individual. Legado, percurso integrado, modulares/ofertas e notificações desabilitados.",outputs/investmoz-audit-current-20261012/runtime-active.json; runtime-qa18445.json. Inventário/GET do domínio não demonstra esta cláusula individual.,Sem evidência suficiente,Reconciliar contrato/cláusula e provas por categoria; completar apenas falha/variante executável identificada. Response normativo não é completo. Critério individual original preservado; cobertura atual ainda não individualizada.,"030000→040000 já instalados; contratos/resources existentes, sessão/MFA, ClamD e worker para ficheiros; gates das seis categorias ON. Variantes modulares/offers continuam separadas. Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.","RF-MIN-006: Filtros incluem recurso, fase, título, localização, valor, modalidade e infra-estrutura; recomendação é explicável e revista por gestor.",P2,RF-MIN-006: Relatório 25/09 conservado; estado histórico não promove conclusão,RF-MIN-006,1,"[{""id"":""RF-MIN-006"",""pedido"":""Pesquisar e corresponder oportunidades mineiras."",""fontes"":[""upgrade v4 SHA f0b9815…; p. 3275; requirements-audit.csv""],""aceitacao"":""Filtros incluem recurso, fase, título, localização, valor, modalidade e infra-estrutura; recomendação é explicável e revista por gestor."",""estado"":""parcial"",""variantes"":""Relatório 25/09 conservado; estado histórico não promove conclusão"",""modulo"":""oportunidades""}]",Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Texto exatamente equivalente no mesmo módulo; sem fusão semântica aproximada,parcial,public.opportunities: 6 registos; public.opportunity_images: tabela ausente; public.opportunity_documents: tabela ausente; public.opportunity_location_drafts: tabela ausente,"A evidência existente é do domínio ou uma declaração histórica; não identifica a demonstração integral desta cláusula. Não afirmar implementado/parcial apenas por componente, menu ou teste global.",Reconciliar contrato/cláusula e provas por categoria; completar apenas falha/variante executável identificada. Response normativo não é completo. Critério individual original preservado; cobertura atual ainda não individualizada.,"OpportunityManagement, OpportunityCanonicalContract/Response, OpportunityReviewMateriality, PortalProfileAccess, OpportunityResources","030000→040000 já instalados; contratos/resources existentes, sessão/MFA, ClamD e worker para ficheiros; gates das seis categorias ON. Variantes modulares/offers continuam separadas.",Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.,Reconciliação e prova individual antes de estimar alteração,Estimativa partilhada no lote correspondente de visao-consolidada-modulos.md; não somar por linha/cláusula/campo. Grupo: oportunidades. Esforço de código adicional só após lacuna reproduzida.,outputs/investmoz-audit-current-20261012/runtime-active.json; runtime-qa18445.json. Inventário/GET do domínio não demonstra esta cláusula individual.,oportunidades,12/10/2026 — reconciliação e HTTP; nova inspeção visual bloqueada,,,Verificar individualmente este critério no contrato e percurso aplicável; a matriz inicial conserva texto/fonte sem o promover a aceitação.,"QA de UI quando browser disponível; reutilizar o domínio existente. Dados/decisões específicos no backlog, sem segunda validação de fonte oficial.",app/Services/OpportunityManagement.php; app/Services/OpportunityResources.php; resources/js/pages/management/opportunity-draft.tsx — componentes do domínio encontrados; correspondência integral deste requisito não presumida,"OpportunityManagementTest, OpportunityResourcesTest, OpportunityOriginReviewTest; suite global QA e GET por perfil disponíveis. Evidência do domínio não demonstra integralmente esta cláusula; UI individual pendente.",Não demonstrado individualmente; localizar critério e executar em QA.,,,,"0,5–2 h de análise/prova por cláusula sem correspondência, agrupada por contrato/consumidor; não somar estas faixas como projetos independentes. Implementação indeterminada até localizar lacuna.",,, RF-MN-001,Registar utilizadores singulares e colectivos com verificação de contactos.,upgrade v4 SHA f0b9815…; p. 2957; requirements-audit.csv | portal do investidor\upgrade do portal de investimentos.docx; SHA 471012abb0f56377956f4ab822fe3186e2561bee0471508a5d807091f2e0154f; w:p 573 | portal do investidor\Portal V4\upgrade do portal de investimentos v6 programacao.docx; SHA e1169a7743b13ff6c950d3ffd4672a2afea32e81da84885e11154b7d8de9241e; w:p 3409 | portal do investidor\Portal V4\upgrade do portal de investimentos v1.docx; SHA f70ce3075523c6278e00dce15b9c7acf2b690679e16959ae5f7d007e754cfc6e; w:p 580 | portal do investidor\Portal V4\upgrade do portal de investimentos v2.docx; SHA 51b8f0ad3296d1064a5c12b450874fc72640869f10a164319cd7f3153dd3a086; w:p 2214 | portal do investidor\Portal V4\upgrade do portal de investimentos v5 programacao.docx; SHA edece80c0519537244f8a8b86456af0aa92de1c526f791bb0ee9b85af7a7e8b2; w:p 3409,CRM/solicitações,//; /Simulador; /api/v1/documents/recommendations; /api/v1/investment/submissions; /api/v1/investment/submissions/{id}; /api/v1/investment/submissions/{id}/attachments; /api/v1/investment/submissions/{id}/attachments/{attachment}; /api/v1/investment/submissions/{id}/submit; /apoio; /balcao-virtual; /como-investir; /documentos,"investors/contacts/leads/tasks, OpportunityContacts, acompanhamento interno existente. Capacidade existente associada ao domínio; correspondência integral desta cláusula só quando demonstrada na prova individual.","Ativo investemoz:5432, 295 tabelas, COUNTRY02/030000/040000 instalados; seis categorias e administração habilitadas. Leitura atual não demonstra esta cláusula individual. Legado, percurso integrado, modulares/ofertas e notificações desabilitados.",outputs/investmoz-audit-current-20261012/runtime-active.json; runtime-qa18445.json. Inventário/GET do domínio não demonstra esta cláusula individual.,Sem evidência suficiente,"Converter sem duplicar, tarefa/responsável/próxima ação recuperáveis; corrigir apenas falha comprovada. Critério individual original preservado; cobertura atual ainda não individualizada.",Relações mestre/contexto e autorizações; não depende de CRM externo. C131 sóQA. Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.,RF-MN-001: Conta só é activada após confirmação do correio electrónico ou telefone; tentativas e falhas ficam registadas. | MASTER-471012ab-0051: Registar utilizadores singulares e colectivos com verificação de contactos. | MASTER-e1169a77-0201: Registar utilizadores singulares e colectivos com verificação de contactos. | MASTER-f70ce307-0057: Registar utilizadores singulares e colectivos com verificação de contactos. | MASTER-51b8f0ad-0137: Registar utilizadores singulares e colectivos com verificação de contactos. | MASTER-edece80c-0201: Registar utilizadores singulares e colectivos com verificação de contactos.,P2,RF-MN-001: Relatório 25/09 conservado; estado histórico não promove conclusão | MASTER-471012ab-0051: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-e1169a77-0201: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-f70ce307-0057: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-51b8f0ad-0137: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-edece80c-0201: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,RF-MN-001 | MASTER-471012ab-0051 | MASTER-e1169a77-0201 | MASTER-f70ce307-0057 | MASTER-51b8f0ad-0137 | MASTER-edece80c-0201,6,"[{""id"":""RF-MN-001"",""pedido"":""Registar utilizadores singulares e colectivos com verificação de contactos."",""fontes"":[""upgrade v4 SHA f0b9815…; p. 2957; requirements-audit.csv""],""aceitacao"":""Conta só é activada após confirmação do correio electrónico ou telefone; tentativas e falhas ficam registadas."",""estado"":""parcial"",""variantes"":""Relatório 25/09 conservado; estado histórico não promove conclusão"",""modulo"":""CRM/solicitações""},{""id"":""MASTER-471012ab-0051"",""pedido"":""Registar utilizadores singulares e colectivos com verificação de contactos."",""fontes"":[""portal do investidor\\upgrade do portal de investimentos.docx; SHA 471012abb0f56377956f4ab822fe3186e2561bee0471508a5d807091f2e0154f; w:p 573""],""aceitacao"":""Registar utilizadores singulares e colectivos com verificação de contactos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""CRM/solicitações""},{""id"":""MASTER-e1169a77-0201"",""pedido"":""Registar utilizadores singulares e colectivos com verificação de contactos."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v6 programacao.docx; SHA e1169a7743b13ff6c950d3ffd4672a2afea32e81da84885e11154b7d8de9241e; w:p 3409""],""aceitacao"":""Registar utilizadores singulares e colectivos com verificação de contactos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""CRM/solicitações""},{""id"":""MASTER-f70ce307-0057"",""pedido"":""Registar utilizadores singulares e colectivos com verificação de contactos."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v1.docx; SHA f70ce3075523c6278e00dce15b9c7acf2b690679e16959ae5f7d007e754cfc6e; w:p 580""],""aceitacao"":""Registar utilizadores singulares e colectivos com verificação de contactos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""CRM/solicitações""},{""id"":""MASTER-51b8f0ad-0137"",""pedido"":""Registar utilizadores singulares e colectivos com verificação de contactos."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v2.docx; SHA 51b8f0ad3296d1064a5c12b450874fc72640869f10a164319cd7f3153dd3a086; w:p 2214""],""aceitacao"":""Registar utilizadores singulares e colectivos com verificação de contactos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""CRM/solicitações""},{""id"":""MASTER-edece80c-0201"",""pedido"":""Registar utilizadores singulares e colectivos com verificação de contactos."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v5 programacao.docx; SHA edece80c0519537244f8a8b86456af0aa92de1c526f791bb0ee9b85af7a7e8b2; w:p 3409""],""aceitacao"":""Registar utilizadores singulares e colectivos com verificação de contactos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""CRM/solicitações""}]",Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Texto exatamente equivalente no mesmo módulo; sem fusão semântica aproximada,parcial,public.interest_requests: 4 registos; public.leads: 0 registos; public.investors: 3 registos,"A evidência existente é do domínio ou uma declaração histórica; não identifica a demonstração integral desta cláusula. Não afirmar implementado/parcial apenas por componente, menu ou teste global.","Converter sem duplicar, tarefa/responsável/próxima ação recuperáveis; corrigir apenas falha comprovada. Critério individual original preservado; cobertura atual ainda não individualizada.","investors/contacts/leads/tasks, OpportunityContacts, acompanhamento interno existente",Relações mestre/contexto e autorizações; não depende de CRM externo. C131 sóQA.,Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.,Reconciliação e prova individual antes de estimar alteração,Estimativa partilhada no lote correspondente de visao-consolidada-modulos.md; não somar por linha/cláusula/campo. Grupo: acompanhamento. Esforço de código adicional só após lacuna reproduzida.,outputs/investmoz-audit-current-20261012/runtime-active.json; runtime-qa18445.json. Inventário/GET do domínio não demonstra esta cláusula individual.,acompanhamento,12/10/2026 — reconciliação e HTTP; nova inspeção visual bloqueada,,,Verificar individualmente este critério no contrato e percurso aplicável; a matriz inicial conserva texto/fonte sem o promover a aceitação.,"QA de UI quando browser disponível; reutilizar o domínio existente. Dados/decisões específicos no backlog, sem segunda validação de fonte oficial.",app/Http/Controllers/CrmController.php; app/Services/DeclaredOriginStatistics.php — componentes do domínio encontrados; correspondência integral deste requisito não presumida,"InterestSubmissionTest, InvestmentWorkspaceTest, PortalOriginCompatibilityTest; suite global QA e GET por perfil disponíveis. Evidência do domínio não demonstra integralmente esta cláusula; UI individual pendente.",Não demonstrado individualmente; localizar critério e executar em QA.,,,,"0,5–2 h de análise/prova por cláusula sem correspondência, agrupada por contrato/consumidor; não somar estas faixas como projetos independentes. Implementação indeterminada até localizar lacuna.",,, RF-MN-002,"Gerir perfis, representantes, funções e organizações.",upgrade v4 SHA f0b9815…; p. 2961; requirements-audit.csv,governação/outros,/api/v1/investment/admin/interests/{interest}/status; /management/history; /management/interests; /management/interests/{interest}; /management/interests/{interest}/consent/contact; /management/interests/{interest}/consent/proponent; /management/opportunities; /management/opportunities/create; /management/opportunities/drafts; /management/opportunities/{id}/draft; /management/opportunities/{id}/edit; /management/opportunities/{id}/lifecycle,"Entidades, schema/configuração e serviços existentes; correspondência individual por estabelecer. Capacidade existente associada ao domínio; correspondência integral desta cláusula só quando demonstrada na prova individual.","Ativo investemoz:5432, 295 tabelas, COUNTRY02/030000/040000 instalados; seis categorias e administração habilitadas. Leitura atual não demonstra esta cláusula individual. Legado, percurso integrado, modulares/ofertas e notificações desabilitados.","outputs/investmoz-audit-current-20261012/runtime-active.json; runtime-qa18445.json. Inventário/GET do domínio não demonstra esta cláusula individual. | 02/11/2026: profile-json preserva erros por campo e reautenticacao em falhas HTML/419 e rejeita JSON invalido; contexto de investimento rejeita falso sucesso HTTP 200 e liberta keepalive, sem repetir escritas. 26 testes focados, TypeScript e build aprovados. Prova local parcial; sem dados/permissoes/publicacao. | Retoma 02/11/2026: profileJson GET independente de cookie CSRF malformado; POST recusa antes do transporte com mensagem de recuperacao. Preservadas validacao e credenciais. 14 testes focados aprovados, incluindo separadores de cookie sem espaco; sem mudanca de direitos.",Sem evidência suficiente,Localizar obrigação verificável e consumidores/contrato atuais antes de estimar implementação ou declarar conclusão. Critério individual original preservado; cobertura atual ainda não individualizada.,"Ler secção/versão correspondente; distinguir requisito funcional, decisão e exemplo. Fontes oficiais já aceites. Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.",RF-MN-002: Uma organização pode ter vários utilizadores; cada acção identifica o utilizador e a função.,P2,RF-MN-002: Relatório 25/09 conservado; estado histórico não promove conclusão,RF-MN-002,1,"[{""id"":""RF-MN-002"",""pedido"":""Gerir perfis, representantes, funções e organizações."",""fontes"":[""upgrade v4 SHA f0b9815…; p. 2961; requirements-audit.csv""],""aceitacao"":""Uma organização pode ter vários utilizadores; cada acção identifica o utilizador e a função."",""estado"":""parcial"",""variantes"":""Relatório 25/09 conservado; estado histórico não promove conclusão"",""modulo"":""governação/outros""}]",Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Texto exatamente equivalente no mesmo módulo; sem fusão semântica aproximada,parcial,Requisito transversal; consultar estado-ativo-after.json,"A evidência existente é do domínio ou uma declaração histórica; não identifica a demonstração integral desta cláusula. Não afirmar implementado/parcial apenas por componente, menu ou teste global.",Localizar obrigação verificável e consumidores/contrato atuais antes de estimar implementação ou declarar conclusão. Critério individual original preservado; cobertura atual ainda não individualizada.,"Entidades, schema/configuração e serviços existentes; correspondência individual por estabelecer","Ler secção/versão correspondente; distinguir requisito funcional, decisão e exemplo. Fontes oficiais já aceites.",Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.,Reconciliação e prova individual antes de estimar alteração,Estimativa partilhada no lote correspondente de visao-consolidada-modulos.md; não somar por linha/cláusula/campo. Grupo: governação e demais cláusulas. Esforço de código adicional só após lacuna reproduzida.,outputs/investmoz-audit-current-20261012/runtime-active.json; runtime-qa18445.json. Inventário/GET do domínio não demonstra esta cláusula individual.,governação e demais cláusulas,12/10/2026 — reconciliação e HTTP; nova inspeção visual bloqueada,,,Verificar individualmente este critério no contrato e percurso aplicável; a matriz inicial conserva texto/fonte sem o promover a aceitação.,"QA de UI quando browser disponível; reutilizar o domínio existente. Dados/decisões específicos no backlog, sem segunda validação de fonte oficial.",AGENTS.md — componentes do domínio encontrados; correspondência integral deste requisito não presumida,; suite global QA e GET por perfil disponíveis. Evidência do domínio não demonstra integralmente esta cláusula; UI individual pendente.,Não demonstrado individualmente; localizar critério e executar em QA.,,,,"0,5–2 h de análise/prova por cláusula sem correspondência, agrupada por contrato/consumidor; não somar estas faixas como projetos independentes. Implementação indeterminada até localizar lacuna.",,, -RF-MN-003,"Criar, guardar, editar e submeter ofertas e procuras.",upgrade v4 SHA f0b9815…; p. 2965; requirements-audit.csv | portal do investidor\upgrade do portal de investimentos.docx; SHA 471012abb0f56377956f4ab822fe3186e2561bee0471508a5d807091f2e0154f; w:p 581 | portal do investidor\Portal V4\upgrade do portal de investimentos v6 programacao.docx; SHA e1169a7743b13ff6c950d3ffd4672a2afea32e81da84885e11154b7d8de9241e; w:p 3417 | portal do investidor\Portal V4\upgrade do portal de investimentos v1.docx; SHA f70ce3075523c6278e00dce15b9c7acf2b690679e16959ae5f7d007e754cfc6e; w:p 588 | portal do investidor\Portal V4\upgrade do portal de investimentos v2.docx; SHA 51b8f0ad3296d1064a5c12b450874fc72640869f10a164319cd7f3153dd3a086; w:p 2222 | portal do investidor\Portal V4\upgrade do portal de investimentos v5 programacao.docx; SHA edece80c0519537244f8a8b86456af0aa92de1c526f791bb0ee9b85af7a7e8b2; w:p 3417,governação/outros,/api/v1/investment/admin/interests/{interest}/status; /management/history; /management/interests; /management/interests/{interest}; /management/interests/{interest}/consent/contact; /management/interests/{interest}/consent/proponent; /management/opportunities; /management/opportunities/create; /management/opportunities/drafts; /management/opportunities/{id}/draft; /management/opportunities/{id}/edit; /management/opportunities/{id}/lifecycle; /ofertas; /ofertas/{id}; /api/v1/offers; /api/v1/offers/{id}; /api/v1/offers/map; /api/v1/offers/export; /minhas-oportunidades/ofertas/{id}/recursos; /minhas-oportunidades/ofertas/{id}/confidencialidade; /minhas-oportunidades/ofertas/{id}/confidencial; /minhas-oportunidades/ofertas/{id}/nda; /minhas-oportunidades/ofertas/{id}/operacoes; /minhas-oportunidades/ofertas/{id}/denunciar; /minhas-oportunidades/ofertas/{id}/denuncias; /minhas-oportunidades/ofertas/{id}/destaque-administrativo,"Entidades, schema/configuração e serviços existentes; correspondência individual por estabelecer. Capacidade existente associada ao domínio; correspondência integral desta cláusula só quando demonstrada na prova individual. | 14/11/2026 - Ofertas F1: oportunidades como cadastro principal; criar, guardar, recuperar, editar e submeter em privado; autorizacao existente, CRM e localizacao reutilizados; sem menu, publicacao ou instalacao. | 14/11/2026 F2 autorizada: circuito editorial privado no workflow e Dashboard existentes; revisao/atribuicao/rejeicao/correccao/reenvio/aprovacao interna; decisoes por snapshot/hash/ciclo, tarefas, auditoria e notificacoes. Sem publicacao ou instalacao. | 14/11/2026 F3 autorizada: consulta read-only da projeccao publica separada, filtros por categoria/localizacao/preco/moeda, grelha/lista/mapa administrativo e CSV. Barreira efectiva fechada; apenas amostras sinteticas em QA isolado podem ser consumidas por injeccao de teste. | 14/11/2026 F4 autorizada, campos propostos confirmados: particularidades opcionais de empresas/imoveis no mesmo rascunho; seis fotografias activas com edicao/retirada/restauro; originais cifrados e scan existente; comprovativos exclusivamente para validadores internos com ambito/MFA. NDA e autorizacao expressa do vendedor vinculados a versao/hash, revogacao e auditoria; exemplar juridico real pendente, configuracao nula. Sem publicacao ou instalacao. | 14/11/2026 F5 autorizada em QA: retirada imediata pelo dono, novo ciclo/v4 e aprovacao antes da reactivacao; validade congelada por versao, prazo configuravel nulo por defeito, expiracao independente de NDA/cron; denuncias cifradas com origem versionada, tarefas e Dashboard existentes; planos administrativos de destaque sem efeitos comerciais. Publicacao e instalacao bloqueadas.","Ativo investemoz:5432, 295 tabelas, COUNTRY02/030000/040000 instalados; seis categorias e administração habilitadas. Leitura atual não demonstra esta cláusula individual. Legado, percurso integrado, modulares/ofertas e notificações desabilitados.","outputs/investmoz-audit-current-20261012/runtime-active.json; runtime-qa18445.json. Inventário/GET do domínio não demonstra esta cláusula individual. | QA: PrivateOfferPreparationTest.php; regressao 35 testes/548 assercoes e passagem final focada 16/172 (grupos sobrepostos, nao somar). outputs/offers-private-phase1-20261114/qa-result.json. | F2 QA: 35 testes/499 assercoes; final focado 11/160; complemento contribuidores 1/14 e HTML compilado 1/10 aprovados, grupos sobrepostos. tests/Feature/PrivateOfferEditorialTest.php; outputs/offers-private-phase2-20261114/qa-result.json. | F3 QA: tests/Feature/OfferPublicCatalogueTest.php; outputs/offers-public-phase3-20261114/qa-result.json; sem instalacao ou publicacao real. | F4: tests/Feature/PrivateOfferConfidentialityTest.php; outputs/offers-private-phase4-20261114/qa-result.json. Cinco casos territoriais desbloqueados com fixture sintetico guardado em transaccao QA; sem dados operacionais. | F5: tests/Feature/PrivateOfferOperationsTest.php; outputs/offers-private-phase5-20261114/qa-result.json. Matriz de aceitacao das cinco fases no relatorio ofertas-fase5-fecho-qa-20261114.md; provas sinteticas e regressao afectada, sem testes gerais de outros modulos.",Parcial - Ofertas F1-F4 tecnicamente aceites com ressalvas; F5 implementada em QA para apreciacao; cinco fases nao instaladas; publicacao efectiva bloqueada,"Dependencias para uso publico: exemplar/versao/aprovacao do NDA; tipos juridicos de comprovativos; prazo/politica final de validade; avisos juridicos, visual/movel, GD/WebP e email real F2. Politica comercial/destaque activo nao definidos e nao activados. Publicacao/instalacao exigem autorizacao expressa. Procuras permanece por implementar.",Autorizacao expressa para Fase 3 e para instalacao; designacoes operacionais e configuracao de entrega reais apenas quando aplicaveis. Sem novos papeis ou permissoes por inferencia.,"RF-MN-003: Rascunho persiste; submissão bloqueia campos essenciais e gera número de protocolo. | MASTER-471012ab-0052: Criar, guardar, editar e submeter ofertas e procuras. | MASTER-e1169a77-0202: Criar, guardar, editar e submeter ofertas e procuras. | MASTER-f70ce307-0058: Criar, guardar, editar e submeter ofertas e procuras. | MASTER-51b8f0ad-0138: Criar, guardar, editar e submeter ofertas e procuras. | MASTER-edece80c-0202: Criar, guardar, editar e submeter ofertas e procuras.",P2,RF-MN-003: Relatório 25/09 conservado; estado histórico não promove conclusão | MASTER-471012ab-0052: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-e1169a77-0202: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-f70ce307-0058: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-51b8f0ad-0138: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-edece80c-0202: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,RF-MN-003 | MASTER-471012ab-0052 | MASTER-e1169a77-0202 | MASTER-f70ce307-0058 | MASTER-51b8f0ad-0138 | MASTER-edece80c-0202,6,"[{""id"":""RF-MN-003"",""pedido"":""Criar, guardar, editar e submeter ofertas e procuras."",""fontes"":[""upgrade v4 SHA f0b9815…; p. 2965; requirements-audit.csv""],""aceitacao"":""Rascunho persiste; submissão bloqueia campos essenciais e gera número de protocolo."",""estado"":""parcial"",""variantes"":""Relatório 25/09 conservado; estado histórico não promove conclusão"",""modulo"":""governação/outros""},{""id"":""MASTER-471012ab-0052"",""pedido"":""Criar, guardar, editar e submeter ofertas e procuras."",""fontes"":[""portal do investidor\\upgrade do portal de investimentos.docx; SHA 471012abb0f56377956f4ab822fe3186e2561bee0471508a5d807091f2e0154f; w:p 581""],""aceitacao"":""Criar, guardar, editar e submeter ofertas e procuras."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-e1169a77-0202"",""pedido"":""Criar, guardar, editar e submeter ofertas e procuras."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v6 programacao.docx; SHA e1169a7743b13ff6c950d3ffd4672a2afea32e81da84885e11154b7d8de9241e; w:p 3417""],""aceitacao"":""Criar, guardar, editar e submeter ofertas e procuras."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-f70ce307-0058"",""pedido"":""Criar, guardar, editar e submeter ofertas e procuras."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v1.docx; SHA f70ce3075523c6278e00dce15b9c7acf2b690679e16959ae5f7d007e754cfc6e; w:p 588""],""aceitacao"":""Criar, guardar, editar e submeter ofertas e procuras."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-51b8f0ad-0138"",""pedido"":""Criar, guardar, editar e submeter ofertas e procuras."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v2.docx; SHA 51b8f0ad3296d1064a5c12b450874fc72640869f10a164319cd7f3153dd3a086; w:p 2222""],""aceitacao"":""Criar, guardar, editar e submeter ofertas e procuras."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-edece80c-0202"",""pedido"":""Criar, guardar, editar e submeter ofertas e procuras."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v5 programacao.docx; SHA edece80c0519537244f8a8b86456af0aa92de1c526f791bb0ee9b85af7a7e8b2; w:p 3417""],""aceitacao"":""Criar, guardar, editar e submeter ofertas e procuras."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""}]","QA - preparacao, revisao e historico privados; filtros e acesso pelo Dashboard existente; visual manual pendente",QA - operacoes autenticadas /minhas-oportunidades/ofertas e /{id}/revisao; MFA/ambito/conflitos verificados no servidor; publicacao recusada,"QA - cadastro Oportunidades, tarefas/auditoria/notificacoes nativos; snapshots e hashes por ciclo; sem tabelas ou migracoes novas","QA - recuperar oferta, correccao, decisao e versoes anteriores; conflitos, rollback e indisponibilidade sem downgrade | F4: guardar/recuperar particularidades; fotografias e provas no recurso existente; historico de aceitacao/autorizacao/retirada/revogacao por exemplar e versao, sem substituir dados ou decisoes. | F5: retirada sem perda de conteudo; revalidacao no mesmo identificador; historico de ciclos, expiracao, denuncias e planos administrativos com conflitos/etags.","QA - aprovacao interna privada distinta de publicacao; versao publica nativa anterior preservada | F3: projecao publica anterior preservada durante edicao/rejeicao da nova revisao; sem contacto, anexos ou coordenadas do negocio; moeda sem conversao. | F4: dados empresariais reservados so depois de NDA + autorizacao expressa do vendedor; comprovativos nunca entregues ao interessado. | F5: sem reexposicao de publicacao/grants antigos ao reactivar; exclusao uniforme em listas/filtros/mapas/API/CSV; validade opcional visivel quando definida; destaques administrativos privados.",Texto exatamente equivalente no mesmo módulo; sem fusão semântica aproximada,parcial,Requisito transversal; consultar estado-ativo-after.json,"Correcao 9a014268 tecnicamente aceite pela Tempo. Preparacao final: 48 hashes F5/correcao e ancestralidade confirmados; bundle local verificado. Integracao privada QA demonstrada; nao equivale a publicacao operacional. Delta visual posterior na previa QA: menus horizontais/dropdown de Ofertas uniformizados, preservando filtros e acessos; 29 testes de navegacao, tipos e build aprovados. Nao incluido no bundle anterior 9a014268.","Para instalar: nova autorizacao expressa, conferir revisao/schema/runtime actuais, backup/restauro actual e reversao compativel. GD/derivadas, visual/movel e email real pendentes; NDA/comprovativos/avisos/prazo e publicador operacional mantem bloqueios dependentes.","Opportunity, PortalProfileAccess, OpportunityProfileWorkflow, OpportunityWorkflowTasks, documents/recursos, auditoria, Dashboard, filtros/mapa/API/CSV existentes; sem cadastro paralelo.",Destino actual nao consultado. Zero diferencas migrations/composer.lock/package-lock.json face ao recibo historico 96f51096 nao comprova schema activo. Caixa de teste para email por designar; sem activar correio partilhado.,"Autorizacao exclusiva de preparacao; parecer favoravel condicionado para homologacao privada com publicacao fechada. Nenhuma instalacao, migracao operacional, concessao de permissoes, email real ou mne.",Tecnica QA aceite; instalacao nao autorizada/executada; publicacao operacional bloqueada. Estados separados no parecer e matriz final.,Sem nova estimativa temporal nesta entrega; testes sobrepostos nao devem ser somados. Historico de estimativas conservado nas colunas anteriores.,docs/investmoz/v2/auditoria-portal/ofertas-fase5-fecho-qa-20261114.md; outputs/offers-private-phase5-20261114/qa-result.json; provas aceites F1-F4 referenciadas na matriz de aceitacao das cinco fases.; docs/investmoz/v2/auditoria-portal/ofertas-aceitacao-final-20261114.md; outputs/offers-final-acceptance-20261114/receipt-verification.json; native-integration.xml; investment-acceptance.xml; docs/investmoz/v2/auditoria-portal/ofertas-correcao-contexto-qa-20261114.md; outputs/offers-investment-continuity-20261114/qa-result.json; docs/investmoz/v2/auditoria-portal/ofertas-prontidao-homologacao-20261114.md; outputs/offers-homologation-preparation-20261114/readiness-verification.json; offers-9a014268.bundle; outputs/offers-menu-qa-20261114/qa-result.json,governação e demais cláusulas,14/11/2026 - preparacao final para homologacao privada; sem instalacao ou publicacao,,,Verificar individualmente este critério no contrato e percurso aplicável; a matriz inicial conserva texto/fonte sem o promover a aceitação.,"QA de UI quando browser disponível; reutilizar o domínio existente. Dados/decisões específicos no backlog, sem segunda validação de fonte oficial.",AGENTS.md — componentes do domínio encontrados; correspondência integral deste requisito não presumida,; suite global QA e GET por perfil disponíveis. Evidência do domínio não demonstra integralmente esta cláusula; UI individual pendente.,Não demonstrado individualmente; localizar critério e executar em QA.,,,,"0,5–2 h de análise/prova por cláusula sem correspondência, agrupada por contrato/consumidor; não somar estas faixas como projetos independentes. Implementação indeterminada até localizar lacuna.",,, +RF-MN-003,"Criar, guardar, editar e submeter ofertas e procuras.",upgrade v4 SHA f0b9815…; p. 2965; requirements-audit.csv | portal do investidor\upgrade do portal de investimentos.docx; SHA 471012abb0f56377956f4ab822fe3186e2561bee0471508a5d807091f2e0154f; w:p 581 | portal do investidor\Portal V4\upgrade do portal de investimentos v6 programacao.docx; SHA e1169a7743b13ff6c950d3ffd4672a2afea32e81da84885e11154b7d8de9241e; w:p 3417 | portal do investidor\Portal V4\upgrade do portal de investimentos v1.docx; SHA f70ce3075523c6278e00dce15b9c7acf2b690679e16959ae5f7d007e754cfc6e; w:p 588 | portal do investidor\Portal V4\upgrade do portal de investimentos v2.docx; SHA 51b8f0ad3296d1064a5c12b450874fc72640869f10a164319cd7f3153dd3a086; w:p 2222 | portal do investidor\Portal V4\upgrade do portal de investimentos v5 programacao.docx; SHA edece80c0519537244f8a8b86456af0aa92de1c526f791bb0ee9b85af7a7e8b2; w:p 3417,governação/outros,/api/v1/investment/admin/interests/{interest}/status; /management/history; /management/interests; /management/interests/{interest}; /management/interests/{interest}/consent/contact; /management/interests/{interest}/consent/proponent; /management/opportunities; /management/opportunities/create; /management/opportunities/drafts; /management/opportunities/{id}/draft; /management/opportunities/{id}/edit; /management/opportunities/{id}/lifecycle; /ofertas; /ofertas/{id}; /api/v1/offers; /api/v1/offers/{id}; /api/v1/offers/map; /api/v1/offers/export; /minhas-oportunidades/ofertas/{id}/recursos; /minhas-oportunidades/ofertas/{id}/confidencialidade; /minhas-oportunidades/ofertas/{id}/confidencial; /minhas-oportunidades/ofertas/{id}/nda; /minhas-oportunidades/ofertas/{id}/operacoes; /minhas-oportunidades/ofertas/{id}/denunciar; /minhas-oportunidades/ofertas/{id}/denuncias; /minhas-oportunidades/ofertas/{id}/destaque-administrativo,"Entidades, schema/configuração e serviços existentes; correspondência individual por estabelecer. Capacidade existente associada ao domínio; correspondência integral desta cláusula só quando demonstrada na prova individual. | 14/11/2026 - Ofertas F1: oportunidades como cadastro principal; criar, guardar, recuperar, editar e submeter em privado; autorizacao existente, CRM e localizacao reutilizados; sem menu, publicacao ou instalacao. | 14/11/2026 F2 autorizada: circuito editorial privado no workflow e Dashboard existentes; revisao/atribuicao/rejeicao/correccao/reenvio/aprovacao interna; decisoes por snapshot/hash/ciclo, tarefas, auditoria e notificacoes. Sem publicacao ou instalacao. | 14/11/2026 F3 autorizada: consulta read-only da projeccao publica separada, filtros por categoria/localizacao/preco/moeda, grelha/lista/mapa administrativo e CSV. Barreira efectiva fechada; apenas amostras sinteticas em QA isolado podem ser consumidas por injeccao de teste. | 14/11/2026 F4 autorizada, campos propostos confirmados: particularidades opcionais de empresas/imoveis no mesmo rascunho; seis fotografias activas com edicao/retirada/restauro; originais cifrados e scan existente; comprovativos exclusivamente para validadores internos com ambito/MFA. NDA e autorizacao expressa do vendedor vinculados a versao/hash, revogacao e auditoria; exemplar juridico real pendente, configuracao nula. Sem publicacao ou instalacao. | 14/11/2026 F5 autorizada em QA: retirada imediata pelo dono, novo ciclo/v4 e aprovacao antes da reactivacao; validade congelada por versao, prazo configuravel nulo por defeito, expiracao independente de NDA/cron; denuncias cifradas com origem versionada, tarefas e Dashboard existentes; planos administrativos de destaque sem efeitos comerciais. Publicacao e instalacao bloqueadas.","Ativo investemoz:5432, 295 tabelas, COUNTRY02/030000/040000 instalados; seis categorias e administração habilitadas. Leitura atual não demonstra esta cláusula individual. Legado, percurso integrado, modulares/ofertas e notificações desabilitados.","outputs/investmoz-audit-current-20261012/runtime-active.json; runtime-qa18445.json. Inventário/GET do domínio não demonstra esta cláusula individual. | QA: PrivateOfferPreparationTest.php; regressao 35 testes/548 assercoes e passagem final focada 16/172 (grupos sobrepostos, nao somar). outputs/offers-private-phase1-20261114/qa-result.json. | F2 QA: 35 testes/499 assercoes; final focado 11/160; complemento contribuidores 1/14 e HTML compilado 1/10 aprovados, grupos sobrepostos. tests/Feature/PrivateOfferEditorialTest.php; outputs/offers-private-phase2-20261114/qa-result.json. | F3 QA: tests/Feature/OfferPublicCatalogueTest.php; outputs/offers-public-phase3-20261114/qa-result.json; sem instalacao ou publicacao real. | F4: tests/Feature/PrivateOfferConfidentialityTest.php; outputs/offers-private-phase4-20261114/qa-result.json. Cinco casos territoriais desbloqueados com fixture sintetico guardado em transaccao QA; sem dados operacionais. | F5: tests/Feature/PrivateOfferOperationsTest.php; outputs/offers-private-phase5-20261114/qa-result.json. Matriz de aceitacao das cinco fases no relatorio ofertas-fase5-fecho-qa-20261114.md; provas sinteticas e regressao afectada, sem testes gerais de outros modulos.",Parcial - Ofertas F1-F4 tecnicamente aceites com ressalvas; F5 implementada em QA para apreciacao; cinco fases nao instaladas; publicacao efectiva bloqueada,"Dependencias para uso publico: exemplar/versao/aprovacao do NDA; tipos juridicos de comprovativos; prazo/politica final de validade; avisos juridicos, visual/movel, GD/WebP e email real F2. Politica comercial/destaque activo nao definidos e nao activados. Publicacao/instalacao exigem autorizacao expressa. Procuras permanece por implementar.",Autorizacao expressa para Fase 3 e para instalacao; designacoes operacionais e configuracao de entrega reais apenas quando aplicaveis. Sem novos papeis ou permissoes por inferencia.,"RF-MN-003: Rascunho persiste; submissão bloqueia campos essenciais e gera número de protocolo. | MASTER-471012ab-0052: Criar, guardar, editar e submeter ofertas e procuras. | MASTER-e1169a77-0202: Criar, guardar, editar e submeter ofertas e procuras. | MASTER-f70ce307-0058: Criar, guardar, editar e submeter ofertas e procuras. | MASTER-51b8f0ad-0138: Criar, guardar, editar e submeter ofertas e procuras. | MASTER-edece80c-0202: Criar, guardar, editar e submeter ofertas e procuras.",P2,RF-MN-003: Relatório 25/09 conservado; estado histórico não promove conclusão | MASTER-471012ab-0052: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-e1169a77-0202: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-f70ce307-0058: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-51b8f0ad-0138: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-edece80c-0202: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,RF-MN-003 | MASTER-471012ab-0052 | MASTER-e1169a77-0202 | MASTER-f70ce307-0058 | MASTER-51b8f0ad-0138 | MASTER-edece80c-0202,6,"[{""id"":""RF-MN-003"",""pedido"":""Criar, guardar, editar e submeter ofertas e procuras."",""fontes"":[""upgrade v4 SHA f0b9815…; p. 2965; requirements-audit.csv""],""aceitacao"":""Rascunho persiste; submissão bloqueia campos essenciais e gera número de protocolo."",""estado"":""parcial"",""variantes"":""Relatório 25/09 conservado; estado histórico não promove conclusão"",""modulo"":""governação/outros""},{""id"":""MASTER-471012ab-0052"",""pedido"":""Criar, guardar, editar e submeter ofertas e procuras."",""fontes"":[""portal do investidor\\upgrade do portal de investimentos.docx; SHA 471012abb0f56377956f4ab822fe3186e2561bee0471508a5d807091f2e0154f; w:p 581""],""aceitacao"":""Criar, guardar, editar e submeter ofertas e procuras."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-e1169a77-0202"",""pedido"":""Criar, guardar, editar e submeter ofertas e procuras."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v6 programacao.docx; SHA e1169a7743b13ff6c950d3ffd4672a2afea32e81da84885e11154b7d8de9241e; w:p 3417""],""aceitacao"":""Criar, guardar, editar e submeter ofertas e procuras."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-f70ce307-0058"",""pedido"":""Criar, guardar, editar e submeter ofertas e procuras."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v1.docx; SHA f70ce3075523c6278e00dce15b9c7acf2b690679e16959ae5f7d007e754cfc6e; w:p 588""],""aceitacao"":""Criar, guardar, editar e submeter ofertas e procuras."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-51b8f0ad-0138"",""pedido"":""Criar, guardar, editar e submeter ofertas e procuras."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v2.docx; SHA 51b8f0ad3296d1064a5c12b450874fc72640869f10a164319cd7f3153dd3a086; w:p 2222""],""aceitacao"":""Criar, guardar, editar e submeter ofertas e procuras."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-edece80c-0202"",""pedido"":""Criar, guardar, editar e submeter ofertas e procuras."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v5 programacao.docx; SHA edece80c0519537244f8a8b86456af0aa92de1c526f791bb0ee9b85af7a7e8b2; w:p 3417""],""aceitacao"":""Criar, guardar, editar e submeter ofertas e procuras."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""}]","QA - preparacao, revisao e historico privados; filtros e acesso pelo Dashboard existente; visual manual pendente",QA - operacoes autenticadas /minhas-oportunidades/ofertas e /{id}/revisao; MFA/ambito/conflitos verificados no servidor; publicacao recusada,"QA - cadastro Oportunidades, tarefas/auditoria/notificacoes nativos; snapshots e hashes por ciclo; sem tabelas ou migracoes novas","QA - recuperar oferta, correccao, decisao e versoes anteriores; conflitos, rollback e indisponibilidade sem downgrade | F4: guardar/recuperar particularidades; fotografias e provas no recurso existente; historico de aceitacao/autorizacao/retirada/revogacao por exemplar e versao, sem substituir dados ou decisoes. | F5: retirada sem perda de conteudo; revalidacao no mesmo identificador; historico de ciclos, expiracao, denuncias e planos administrativos com conflitos/etags.","QA - aprovacao interna privada distinta de publicacao; versao publica nativa anterior preservada | F3: projecao publica anterior preservada durante edicao/rejeicao da nova revisao; sem contacto, anexos ou coordenadas do negocio; moeda sem conversao. | F4: dados empresariais reservados so depois de NDA + autorizacao expressa do vendedor; comprovativos nunca entregues ao interessado. | F5: sem reexposicao de publicacao/grants antigos ao reactivar; exclusao uniforme em listas/filtros/mapas/API/CSV; validade opcional visivel quando definida; destaques administrativos privados.",Texto exatamente equivalente no mesmo módulo; sem fusão semântica aproximada,parcial,Requisito transversal; consultar estado-ativo-after.json,"Pre-instalacao real autorizada: destino 96f51096, estruturas compativeis, PHP 8.4.27, GD/WebP CLI/FPM presentes. Backup actual/restauro isolado aprovados: 219 tabelas, 417 ficheiros, MFA recuperavel; 9873 ficheiros da release reconferidos. Parecer APTO COM CONDICOES; sem instalar.","Nova autorizacao expressa com SHA e inclusao ou nao dos menus 3558647e; garantir janela sem integracoes concorrentes porque executor segue ultimo main; reconferir salvaguarda/revisao antes de instalar. Visual/movel, processamento completo/galeria, email real e decisoes juridicas continuam pendentes; publicacao operacional bloqueada.","Opportunity, PortalProfileAccess, OpportunityProfileWorkflow, OpportunityWorkflowTasks, documents/recursos, auditoria, Dashboard, filtros/mapa/API/CSV existentes; sem cadastro paralelo.","Destino conferido e backup/restauro efectuado. Notificacoes desactivadas e allowlist vazia; caixa de teste por designar/autorizar. Nenhuma Oferta, projecao, rota ou job de Ofertas no activo. Ambiente local com debug falso e cookies seguros; sem alteracao de configuracao.","Autorizacao limitada a verificacao, backup actual e restauro isolado recebida e executada. Instalacao privada e publicacao continuam a exigir nova autorizacao. Reversao apos uso exige preservar guardas/estados; nao repor codigo antigo cegamente.","QA tecnica aceite; verificacao real APTO COM CONDICOES; sem instalacao, migracao, publicacao ou alteracao operacional. Apenas sessao concorrente nova, anteriores intactas.",Sem nova estimativa temporal nesta entrega; testes sobrepostos nao devem ser somados. Historico de estimativas conservado nas colunas anteriores.,docs/investmoz/v2/auditoria-portal/ofertas-fase5-fecho-qa-20261114.md; outputs/offers-private-phase5-20261114/qa-result.json; provas aceites F1-F4 referenciadas na matriz de aceitacao das cinco fases.; docs/investmoz/v2/auditoria-portal/ofertas-aceitacao-final-20261114.md; outputs/offers-final-acceptance-20261114/receipt-verification.json; native-integration.xml; investment-acceptance.xml; docs/investmoz/v2/auditoria-portal/ofertas-correcao-contexto-qa-20261114.md; outputs/offers-investment-continuity-20261114/qa-result.json; docs/investmoz/v2/auditoria-portal/ofertas-prontidao-homologacao-20261114.md; outputs/offers-homologation-preparation-20261114/readiness-verification.json; offers-9a014268.bundle; outputs/offers-menu-qa-20261114/qa-result.json; docs/investmoz/v2/auditoria-portal/ofertas-verificacao-preinstalacao-20261114.md; outputs/offers-preinstall-verification-20261114/qa-result.json,governação e demais cláusulas,"14/11/2026 - destino real, backup e restauro isolado verificados; APTO COM CONDICOES, sem instalacao",,,Verificar individualmente este critério no contrato e percurso aplicável; a matriz inicial conserva texto/fonte sem o promover a aceitação.,"QA de UI quando browser disponível; reutilizar o domínio existente. Dados/decisões específicos no backlog, sem segunda validação de fonte oficial.",AGENTS.md — componentes do domínio encontrados; correspondência integral deste requisito não presumida,; suite global QA e GET por perfil disponíveis. Evidência do domínio não demonstra integralmente esta cláusula; UI individual pendente.,Não demonstrado individualmente; localizar critério e executar em QA.,,,,"0,5–2 h de análise/prova por cláusula sem correspondência, agrupada por contrato/consumidor; não somar estas faixas como projetos independentes. Implementação indeterminada até localizar lacuna.",,, RF-MN-004,"Aplicar campos condicionais por tipo de pessoa, modalidade e fase.",upgrade v4 SHA f0b9815…; p. 2969; requirements-audit.csv,governação/outros,/api/v1/investment/admin/interests/{interest}/status; /management/history; /management/interests; /management/interests/{interest}; /management/interests/{interest}/consent/contact; /management/interests/{interest}/consent/proponent; /management/opportunities; /management/opportunities/create; /management/opportunities/drafts; /management/opportunities/{id}/draft; /management/opportunities/{id}/edit; /management/opportunities/{id}/lifecycle,"Entidades, schema/configuração e serviços existentes; correspondência individual por estabelecer. Capacidade existente associada ao domínio; correspondência integral desta cláusula só quando demonstrada na prova individual.","Ativo investemoz:5432, 295 tabelas, COUNTRY02/030000/040000 instalados; seis categorias e administração habilitadas. Leitura atual não demonstra esta cláusula individual. Legado, percurso integrado, modulares/ofertas e notificações desabilitados.",outputs/investmoz-audit-current-20261012/runtime-active.json; runtime-qa18445.json. Inventário/GET do domínio não demonstra esta cláusula individual.,Sem evidência suficiente,Localizar obrigação verificável e consumidores/contrato atuais antes de estimar implementação ou declarar conclusão. Critério individual original preservado; cobertura atual ainda não individualizada.,"Ler secção/versão correspondente; distinguir requisito funcional, decisão e exemplo. Fontes oficiais já aceites. Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.","RF-MN-004: Campos aparecem e tornam-se obrigatórios conforme regras da matriz, sem perda de dados válidos.",P2,RF-MN-004: Relatório 25/09 conservado; estado histórico não promove conclusão,RF-MN-004,1,"[{""id"":""RF-MN-004"",""pedido"":""Aplicar campos condicionais por tipo de pessoa, modalidade e fase."",""fontes"":[""upgrade v4 SHA f0b9815…; p. 2969; requirements-audit.csv""],""aceitacao"":""Campos aparecem e tornam-se obrigatórios conforme regras da matriz, sem perda de dados válidos."",""estado"":""parcial"",""variantes"":""Relatório 25/09 conservado; estado histórico não promove conclusão"",""modulo"":""governação/outros""}]",Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Texto exatamente equivalente no mesmo módulo; sem fusão semântica aproximada,parcial,Requisito transversal; consultar estado-ativo-after.json,"A evidência existente é do domínio ou uma declaração histórica; não identifica a demonstração integral desta cláusula. Não afirmar implementado/parcial apenas por componente, menu ou teste global.",Localizar obrigação verificável e consumidores/contrato atuais antes de estimar implementação ou declarar conclusão. Critério individual original preservado; cobertura atual ainda não individualizada.,"Entidades, schema/configuração e serviços existentes; correspondência individual por estabelecer","Ler secção/versão correspondente; distinguir requisito funcional, decisão e exemplo. Fontes oficiais já aceites.",Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.,Reconciliação e prova individual antes de estimar alteração,Estimativa partilhada no lote correspondente de visao-consolidada-modulos.md; não somar por linha/cláusula/campo. Grupo: governação e demais cláusulas. Esforço de código adicional só após lacuna reproduzida.,outputs/investmoz-audit-current-20261012/runtime-active.json; runtime-qa18445.json. Inventário/GET do domínio não demonstra esta cláusula individual.,governação e demais cláusulas,12/10/2026 — reconciliação e HTTP; nova inspeção visual bloqueada,,,Verificar individualmente este critério no contrato e percurso aplicável; a matriz inicial conserva texto/fonte sem o promover a aceitação.,"QA de UI quando browser disponível; reutilizar o domínio existente. Dados/decisões específicos no backlog, sem segunda validação de fonte oficial.",AGENTS.md — componentes do domínio encontrados; correspondência integral deste requisito não presumida,; suite global QA e GET por perfil disponíveis. Evidência do domínio não demonstra integralmente esta cláusula; UI individual pendente.,Não demonstrado individualmente; localizar critério e executar em QA.,,,,"0,5–2 h de análise/prova por cláusula sem correspondência, agrupada por contrato/consumidor; não somar estas faixas como projetos independentes. Implementação indeterminada até localizar lacuna.",,, RF-MN-005,"Validar formatos, intervalos, dependências, duplicados e consentimentos.",upgrade v4 SHA f0b9815…; p. 2973; requirements-audit.csv | portal do investidor\upgrade do portal de investimentos.docx; SHA 471012abb0f56377956f4ab822fe3186e2561bee0471508a5d807091f2e0154f; w:p 589 | portal do investidor\Portal V4\upgrade do portal de investimentos v6 programacao.docx; SHA e1169a7743b13ff6c950d3ffd4672a2afea32e81da84885e11154b7d8de9241e; w:p 3425 | portal do investidor\Portal V4\upgrade do portal de investimentos v1.docx; SHA f70ce3075523c6278e00dce15b9c7acf2b690679e16959ae5f7d007e754cfc6e; w:p 596 | portal do investidor\Portal V4\upgrade do portal de investimentos v2.docx; SHA 51b8f0ad3296d1064a5c12b450874fc72640869f10a164319cd7f3153dd3a086; w:p 2230 | portal do investidor\Portal V4\upgrade do portal de investimentos v5 programacao.docx; SHA edece80c0519537244f8a8b86456af0aa92de1c526f791bb0ee9b85af7a7e8b2; w:p 3425,governação/outros,/api/v1/investment/admin/interests/{interest}/status; /management/history; /management/interests; /management/interests/{interest}; /management/interests/{interest}/consent/contact; /management/interests/{interest}/consent/proponent; /management/opportunities; /management/opportunities/create; /management/opportunities/drafts; /management/opportunities/{id}/draft; /management/opportunities/{id}/edit; /management/opportunities/{id}/lifecycle,"Entidades, schema/configuração e serviços existentes; correspondência individual por estabelecer. Capacidade existente associada ao domínio; correspondência integral desta cláusula só quando demonstrada na prova individual.","Ativo investemoz:5432, 295 tabelas, COUNTRY02/030000/040000 instalados; seis categorias e administração habilitadas. Leitura atual não demonstra esta cláusula individual. Legado, percurso integrado, modulares/ofertas e notificações desabilitados.",outputs/investmoz-audit-current-20261012/runtime-active.json; runtime-qa18445.json. Inventário/GET do domínio não demonstra esta cláusula individual.,Sem evidência suficiente,Localizar obrigação verificável e consumidores/contrato atuais antes de estimar implementação ou declarar conclusão. Critério individual original preservado; cobertura atual ainda não individualizada.,"Ler secção/versão correspondente; distinguir requisito funcional, decisão e exemplo. Fontes oficiais já aceites. Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.","RF-MN-005: O sistema impede submissão inválida e apresenta mensagem associada ao campo e orientação de correcção. | MASTER-471012ab-0054: Validar formatos, intervalos, dependências, duplicados e consentimentos. | MASTER-e1169a77-0204: Validar formatos, intervalos, dependências, duplicados e consentimentos. | MASTER-f70ce307-0060: Validar formatos, intervalos, dependências, duplicados e consentimentos. | MASTER-51b8f0ad-0140: Validar formatos, intervalos, dependências, duplicados e consentimentos. | MASTER-edece80c-0204: Validar formatos, intervalos, dependências, duplicados e consentimentos.",P2,RF-MN-005: Relatório 25/09 conservado; estado histórico não promove conclusão | MASTER-471012ab-0054: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-e1169a77-0204: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-f70ce307-0060: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-51b8f0ad-0140: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-edece80c-0204: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,RF-MN-005 | MASTER-471012ab-0054 | MASTER-e1169a77-0204 | MASTER-f70ce307-0060 | MASTER-51b8f0ad-0140 | MASTER-edece80c-0204,6,"[{""id"":""RF-MN-005"",""pedido"":""Validar formatos, intervalos, dependências, duplicados e consentimentos."",""fontes"":[""upgrade v4 SHA f0b9815…; p. 2973; requirements-audit.csv""],""aceitacao"":""O sistema impede submissão inválida e apresenta mensagem associada ao campo e orientação de correcção."",""estado"":""parcial"",""variantes"":""Relatório 25/09 conservado; estado histórico não promove conclusão"",""modulo"":""governação/outros""},{""id"":""MASTER-471012ab-0054"",""pedido"":""Validar formatos, intervalos, dependências, duplicados e consentimentos."",""fontes"":[""portal do investidor\\upgrade do portal de investimentos.docx; SHA 471012abb0f56377956f4ab822fe3186e2561bee0471508a5d807091f2e0154f; w:p 589""],""aceitacao"":""Validar formatos, intervalos, dependências, duplicados e consentimentos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-e1169a77-0204"",""pedido"":""Validar formatos, intervalos, dependências, duplicados e consentimentos."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v6 programacao.docx; SHA e1169a7743b13ff6c950d3ffd4672a2afea32e81da84885e11154b7d8de9241e; w:p 3425""],""aceitacao"":""Validar formatos, intervalos, dependências, duplicados e consentimentos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-f70ce307-0060"",""pedido"":""Validar formatos, intervalos, dependências, duplicados e consentimentos."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v1.docx; SHA f70ce3075523c6278e00dce15b9c7acf2b690679e16959ae5f7d007e754cfc6e; w:p 596""],""aceitacao"":""Validar formatos, intervalos, dependências, duplicados e consentimentos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-51b8f0ad-0140"",""pedido"":""Validar formatos, intervalos, dependências, duplicados e consentimentos."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v2.docx; SHA 51b8f0ad3296d1064a5c12b450874fc72640869f10a164319cd7f3153dd3a086; w:p 2230""],""aceitacao"":""Validar formatos, intervalos, dependências, duplicados e consentimentos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-edece80c-0204"",""pedido"":""Validar formatos, intervalos, dependências, duplicados e consentimentos."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v5 programacao.docx; SHA edece80c0519537244f8a8b86456af0aa92de1c526f791bb0ee9b85af7a7e8b2; w:p 3425""],""aceitacao"":""Validar formatos, intervalos, dependências, duplicados e consentimentos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""}]",Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Texto exatamente equivalente no mesmo módulo; sem fusão semântica aproximada,parcial,Requisito transversal; consultar estado-ativo-after.json,"A evidência existente é do domínio ou uma declaração histórica; não identifica a demonstração integral desta cláusula. Não afirmar implementado/parcial apenas por componente, menu ou teste global. diff --git a/docs/investmoz/v2/auditoria-portal/ofertas-aceitacao-final-20261114.md b/docs/investmoz/v2/auditoria-portal/ofertas-aceitacao-final-20261114.md index f681ecfc..07be9d5a 100644 --- a/docs/investmoz/v2/auditoria-portal/ofertas-aceitacao-final-20261114.md +++ b/docs/investmoz/v2/auditoria-portal/ofertas-aceitacao-final-20261114.md @@ -8,6 +8,8 @@ **Preparação final após aceitação técnica expressa de `9a014268`:** [parecer de homologação privada](ofertas-prontidao-homologacao-20261114.md). Integridade das fases/correcção reconfirmada por 48 hashes; bundle local verificado. Aprovação técnica em QA aceite; instalação ainda não autorizada/executada, condicionada à conferência real do destino e backup/restauro actual. Publicação operacional permanece bloqueada. Visual, GD/derivadas e email real são critérios de avaliação pendentes, sem invalidar a prova integrada privada aceite. +**Destino e salvaguarda posteriormente verificados por autorização específica:** [verificação pré-instalação](ofertas-verificacao-preinstalacao-20261114.md), **APTO COM CONDIÇÕES**. Schema/runtime compatíveis, GD/WebP do destino disponível, restauro isolado de 219 tabelas/417 ficheiros aprovado. Instalação e publicação continuam não autorizadas; a instalação exige SHA expresso e janela sem integrações concorrentes porque o executor segue o último `main`. Menus `3558647e` constituem delta QA posterior à candidata `9a014268`. + ## Evidência e limites - Confirmada a ancestralidade dos commits F1 `1f2990e4`, plano `6e302db6`, F2 `91c18573`, F3 `7691cab3`, F4 `e431de8b` e F5 `528a2597`. diff --git a/docs/investmoz/v2/auditoria-portal/ofertas-prontidao-homologacao-20261114.md b/docs/investmoz/v2/auditoria-portal/ofertas-prontidao-homologacao-20261114.md index 095434df..0cdce3e8 100644 --- a/docs/investmoz/v2/auditoria-portal/ofertas-prontidao-homologacao-20261114.md +++ b/docs/investmoz/v2/auditoria-portal/ofertas-prontidao-homologacao-20261114.md @@ -2,6 +2,8 @@ 14/11/2026. Candidata técnica aceite: **`9a01426864a47dacd97db2b4da363aba86a94df6`**. Esta passagem autoriza exclusivamente preparação; não houve instalação, alteração operacional, migração, activação pública ou envio de email. +**Actualização da verificação autorizada posterior:** [parecer real pré-instalação](ofertas-verificacao-preinstalacao-20261114.md), **APTO COM CONDIÇÕES**. Destino `96f51096` conferido; backup/restauro de 219 tabelas e 417 ficheiros aprovado; GD/WebP disponível. As referências abaixo a destino/backup ainda por conferir descrevem a preparação original. Mantêm-se nova autorização de instalação e condição de revisão exclusiva em `main`; nenhuma instalação executada. + ## Parecer **Existe validação integrada em QA suficiente para recomendar uma instalação controlada de avaliação privada, com condições de entrada. Não existe aceitação integral para operação/publicação pública.** A autorização para instalar continua separada e ainda não foi recebida nesta passagem. diff --git a/docs/investmoz/v2/auditoria-portal/ofertas-verificacao-preinstalacao-20261114.md b/docs/investmoz/v2/auditoria-portal/ofertas-verificacao-preinstalacao-20261114.md new file mode 100644 index 00000000..9a6aeb2b --- /dev/null +++ b/docs/investmoz/v2/auditoria-portal/ofertas-verificacao-preinstalacao-20261114.md @@ -0,0 +1,101 @@ +# Ofertas — verificação real pré-instalação + +14/11/2026. Autorização exclusiva de inspeção, cópia actual e restauro isolado; nenhuma autorização de instalação ou publicação. Referências: preparação `d954c83b`, integração tecnicamente aceite `9a014268`, menus QA `3558647e`. + +## Parecer: APTO COM CONDIÇÕES + +O destino é compatível com a **avaliação privada** das Ofertas. As estruturas verificadas estão presentes, não há migrações/dependências novas no delta, GD/WebP está disponível no destino e o backup/restauro actual passou. A conclusão não autoriza instalar nem declarar aceite a publicação operacional. + +A instalação futura tem de identificar uma revisão exacta e ocorrer numa janela sem integrações concorrentes: o executor actual segue o último `main`, em vez de fixar o SHA indicado no pedido. Se essa condição não puder ser garantida, **não se cria o pedido de instalação nem se publica a candidata em `main`**. Nenhuma alteração ao executor ou às suas configurações foi feita. + +## Destino e compatibilidade comprovados + +| Item | Observado no destino real | +| --- | --- | +| Endereço / revisão instalada e servida | `https://teste-investidor.tempo.co.mz` — `96f510965f70581a4b41dd94463f598b77dd7cf5` | +| Release resolvida | `/var/www/investmoz-homologacao/releases/96f510965f70581a4b41dd94463f598b77dd7cf5-8ee72062` | +| Base real | `teste_investidor`, porta 5432; PostgreSQL 17.11; PostGIS 3.6.4; pgcrypto 1.3 | +| Aplicação | PHP CLI 8.4.27; Nginx, PHP-FPM 8.4, PostgreSQL, fila existente e ClamAV activos | +| Executor | `/var/www/deploy/investmoz-pipeline.py`, serviço inactivo, path activo, zero pedidos pendentes na observação | +| Configuração | Perfis e circuito editorial activos; notificações desactivadas; zero destinatários autorizados; nenhuma configuração alterada | +| Segurança observada | `APP_ENV=local`, debug falso; cookies Secure e HttpOnly, SameSite lax; URL HTTPS; sem overrides APP_ENV/APP_DEBUG encontrados no FPM/Nginx | +| Reserva global do ambiente | `homologation_only=false`, modo configurado `perimeter`; não se presume reserva global activa a partir do nome do modo. As Ofertas privadas continuam a exigir autenticação/perfis/âmbito próprios. Não foi alterada esta configuração. | +| Capacidade inicial | Cerca de 24,6 GB livres; condição de capacidade do executor a reconferir antes de instalar | + +Foram verificadas 19 relações de suporte, sem ausências nas colunas requeridas: Oportunidades/gestão/JSON/dono/organização, utilizadores e revisões de acesso, perfis, pedidos/aprovações de segurança, recursos, documentos/cifra/scan, edições/notificações, auditoria, tarefas, dossiers/contexto e rascunhos. Confirmados a FK das notificações, `portal_editorial_notice_subject` e `portal_editorial_op_notice_uq`. O destino regista 112 migrações. + +O delta `96f51096..3558647e` não contém diferenças em `database/migrations`, `composer.lock` ou `package-lock.json`. **Não se prevê executar qualquer migração operacional.** Não se aplicaram seeders, concessões de permissões, alterações a `mne` ou alterações de estrutura no destino. + +Evidências sanitizadas em `outputs/offers-preinstall-verification-20261114/target-runtime-schema.json`, `constraints-automatics.json`, `security-runtime.json` e `executor-status.json`. A release não é um checkout Git: a revisão foi confirmada pelo recibo do executor e pelo manifesto servido, não por um `git status` vazio. + +## Publicação e automatismos + +O módulo ainda não está instalado: não existem classes de preparação/operações/catálogo de Ofertas no destino, não há rotas `ofertas/offers` registadas, preparações privadas, projecções públicas ou registos publicados de Ofertas. Não há jobs ou falhas de `NotifyPrivateOfferDecision` na fila observada. + +Na candidata, mantêm-se as barreiras aceites em QA: o modelo conserva o marcador privado e exige `draft`/`published_at=null`; o scope nativo exclui Ofertas; aprovação não publica; os consumidores públicos normais permanecem fechados; listagem/detalhe/API/mapa/CSV só foram demonstrados com injecção sintética guardada pela identidade da base isolada. A base real 5432 não satisfaz essa guarda. Nenhuma rota ou variável de ambiente foi aberta para executar esta verificação. + +NDA permanece sem exemplar configurado e o agendamento optativo de expiração está desactivado. A instalação privada deve preservar estes estados. As provas de bloqueio das cinco fases e a correcção integrada foram reutilizadas; não se criaram Ofertas ou ensaios de publicação no destino real para voltar a testar o que já foi aceite. + +## Backup actual e restauro executados + +Backup criado em **14/11/2026 às 19:34:37, Africa/Johannesburg (17:34:37 UTC)**, em `/var/backups/investmoz-offers-preinstall-20261114/backup`, directório privado com modo 0700. A fonte foi lida por transacções `REPEATABLE READ READ ONLY`, com snapshot exportado partilhado entre o dump e as contagens/checksums. O arquivo inclui todos os schemas de aplicação; não se limitou a `public`. + +| Prova | Resultado | +| --- | --- | +| Dump consistente | 219 tabelas; SHA256 `662c5d9f1fe7e01a5ccf7529c8c8e8f27205907d60d2da75a580b7b6c37c252e` | +| Ficheiros persistentes e configuração | 417 ficheiros verificados; arquivo SHA256 `f438f31f212e516442526b609bf5ac02cfd2f630a6ac2a2b6fc12f1f88717a42` | +| Código, assets e dependências da release anterior | Arquivo de 9.873 ficheiros regulares, 25.862.122 bytes; SHA256 `914bc32627a51ec9001d07e5ac31d5717719a4a7f93e43dc26189865962735ca` | +| Restauro da base | Nova instância `offers_preinstall_restore_20261114_test`, porta interna 55441, imagem PostGIS já existente e fixada por digest, rede `none`, sem porta publicada | +| Comparação do restauro | 219/219 tabelas com contagens/checksums exactamente iguais ao snapshot | +| Recuperação dos ficheiros | 417/417 ficheiros extraídos apenas para directório privado de ensaio e verificados por tamanho/hash; nenhuma extracção sobre a aplicação | +| Chaves e envelopes | Dois conjuntos de MFA recuperados com as chaves preservadas; zero documentos activos com `storage_encryption=laravel` nesta fonte, pelo que não se inventa uma prova de decifragem de originais inexistentes | +| Limpeza do ensaio | Container/volume criado para a prova removido após validação do seu identificador e isolamento; sem tocar no cluster real ou no QA local da prévia | + +Configuração, segredos e dados do backup permaneceram no directório privado do servidor; não foram colocados no repositório ou nas respostas. Os recibos locais contêm apenas metadados e resultados sanitizados. O restauro usa `pg_restore` para reproduzir o snapshot na instância nova; não executa migrações do produto nem altera o schema `mne` operacional. + +Na verificação posterior, os 9.873 ficheiros da release e os ficheiros persistentes/configuração mantêm-se iguais. Das 219 tabelas, 218 permanecem iguais ao backup; a diferença é uma sessão adicional. A reconciliação confirmou **51 sessões anteriores integralmente preservadas e sem alteração de campos, zero removidas, 52 actuais**. Não se sobrescreveu esta actividade concorrente para obter igualdade artificial. As operações desta verificação não executaram escritas de dados operacionais. + +Recibos: `backup-summary.json`, `restore-summary.json`, `target-final-preservation.json`, `data-preservation.json`, `session-reconciliation.json`. Os procedimentos revistos de backup/restauro estão na mesma pasta de evidências. A cópia é válida para o instante registado; se a instalação ocorrer posteriormente, reconferir actividade e actualizar a salvaguarda quando necessário. + +## Imagens e email + +**GD/WebP:** presentes no CLI e no PHP-FPM do destino. O ensaio em memória de PNG 400×400 → descodificação → WebP → conferência de dimensões passou, sem ler/alterar uploads ou criar documentos operacionais. A ausência de GD continua a ser uma característica do PHP QA local, não um bloqueante de dependências deste destino. Isto não substitui a futura aceitação do processamento completo das Ofertas, quarentena/scans, derivadas, metadados e galeria com exemplos autorizados. + +**Email:** SMTP configurado, mas `portal.notifications_enabled=false` e lista autorizada vazia. Nenhum email foi enviado. O job de Ofertas reconfirma destinatário na allowlist e acesso à versão. Falta designar uma caixa de teste e autorizar o ensaio/configuração correspondente. Preferir instância isolada com dados sintéticos, destinatário controlado e apenas o job identificado; não activar a flag partilhada nem escoar a fila acumulada para provar entrega. As actuais condições impedem o envio de Ofertas a utilizadores operacionais. A recepção real continua por verificar. + +## Operações exactas previstas — não executadas + +1. **Decisão da Tempo:** autorizar um SHA concreto. A integração aceite é `9a014268`; a prévia com menus é `3558647e8d82092cd0e31eefbe8b9e5e9eacf573`. O bundle congelado da preparação anterior contém apenas `9a014268`. Para instalar o que foi mostrado na prévia, a autorização terá de incluir expressamente `3558647e`; não substituir silenciosamente uma candidata pela outra. +2. **Entrada controlada:** reconfirmar revisão instalada `96f51096`, ficheiros sem drift, estruturas/flags, capacidade, backup/restauro e fila vazia. Acordar janela sem integrações concorrentes em `main`. Se não houver garantia de revisão exclusiva, não iniciar o executor. +3. **Integração Git:** só depois da autorização, fazer chegar a revisão exacta ao ramo `main` de `nala/Investidor-V2` pelo processo existente; não publicar simplesmente o HEAD local, não criar pedidos nesta verificação. A actualização pode desencadear o path activo, pelo que o push faz parte da instalação autorizada, não desta preparação. +4. **Executor existente:** `fetch` do ramo, verificação de drift e capacidade; criar release nova sob `/var/www/investmoz-homologacao/releases`; usar base QA isolada e versões fixadas; executar Composer sem scripts, descoberta de pacotes, guarda da base, suite estrutural `phpunit.investmoz.xml`, `npm ci`, build, tipos, deployment guard, unitários e testes frontend. Depois instalar vendor de produção a partir do mesmo lock e conferir plataforma. Nenhum `artisan migrate` operacional está previsto. +5. **Preparação da release:** validar ausência de `public/hot`; ligar os caminhos persistentes `.env`, `storage` e `public/storage` existentes; gerar o manifesto da candidata e aplicar apenas as permissões técnicas já previstas pelo executor aos directórios novos. Não alterar papéis/permissões de negócio nem configuração funcional. +6. **Promoção:** reconfirmar SHA e ausência de drift, trocar `current` atomicamente e verificar manifesto/homepage; só depois reiniciar a fila pelo comando existente `queue:restart` e actualizar `previous.json`/`installed.json`. Estas escritas de runtime/cache devem constar do recibo, separadas dos dados de negócio. Não executar publicação, importações, agendamento ou ensaio de email. +7. **Pós-verificação:** confirmar revisão/assets, serviços, logs, portas e preservação; conferir fecho público por todas as vias, acesso privado autorizado/recusado, continuidade no dossier e uploads com exemplares sintéticos autorizados; reconciliar cache/sessões e contagens/hash de dados protegidos. Registar quem fez a conferência desktop/móvel, revisão e resultado. Não concluir visual ou entrega de correio por inferência. + +**Risco concreto do executor:** nas linhas 263–266, um avanço do ramo durante o build cria pedido para a revisão mais recente; nas linhas 290–298, os pedidos são processados seguindo `latest()`, não fixando o SHA nominal do ficheiro. Não se apresenta isto como uma recusa automática de revisões não autorizadas. É condição de utilização garantir a janela exclusiva e a coincidência do único `main` com a candidata autorizada; sem isso, a instalação fica bloqueada. A correcção do executor não foi iniciada nem autorizada nesta tarefa. + +## Interrupção e reversão + +Interromper antes de qualquer promoção se mudar a revisão/ramo, aparecer pedido concorrente ou drift, faltar espaço, divergir estrutura ou backup/restauro, falhar um teste/asset/guarda, houver proposta de migração/permissão/configuração não autorizada ou não for possível conservar publicação/notificações fechadas. Uma revisão nova de `main` invalida o âmbito concreto; não a aceitar como parte implícita da autorização. + +O executor existente preserva a release servida durante o build e verifica drift antes da troca. Dispõe de reversão por `previous.json` e troca atómica, com health-check e reinício da fila; **nenhuma reversão foi executada no destino nesta tarefa**. A inspecção da sua implementação não equivale a ensaio de comutação na homologação. + +- **Antes de qualquer uso das Ofertas:** o destino tem zero Ofertas; se isso se mantiver e não houver jobs/estados novos, uma reposição da release/configuração anterior compatível pode ser autorizada com os artefactos preservados, sem restaurar toda a base. +- **Depois de utilização:** não regressar cegamente a `96f51096` ou F4: desconhecem parte dos estados/guardas novos. Uma retirada, expiração, denúncia, versão ou grant novo exige recuperação compatível que mantenha os bloqueios e históricos, preferindo correcção em frente. Não apagar dados ou jobs para acomodar código antigo. +- **Incidente de dados:** preservar a actividade posterior e delimitar uma reconciliação/restauro próprio. Não substituir a base por este dump sem autorização adicional. Não usar migrações `down` nem rollback destrutivo de schema. + +## Saldo e autorização necessária + +| Dimensão | Estado | +| --- | --- | +| Integração técnica em QA e menus | Provas aceites preservadas; menus QA verificados; sem nova ronda geral | +| Destino, estruturas, GD e capacidade | Conferidos, compatíveis para avaliação privada | +| Backup actual e restauro isolado | Aprovados, com reconciliação da sessão nova e recuperação das chaves | +| Instalação privada | **Não autorizada/não executada; APTO COM CONDIÇÕES** | +| Condição operacional do Git | SHA autorizado e janela sem integrações concorrentes obrigatórios; caso contrário bloquear | +| Publicação operacional | Bloqueada; não autorizada; publicador efectivo não implementado | +| Visual/móvel, galeria e email real | Pendentes de conferência/ensaio específicos; sem utilizadores operacionais notificados | +| NDA, comprovativos e avisos legais/validade | Decisões pendentes, mantidas fechadas nos comportamentos dependentes | + +A decisão seguinte necessária é a autorização expressa de instalação privada do SHA escolhido, identificando se inclui os menus `3558647e`, mantendo publicação, NDA, notificações e agendamento nos estados acima e respeitando a janela exclusiva de Git. Esta verificação não concede essa autorização nem a solicita novamente para acções já executadas de backup/restauro. diff --git a/outputs/offers-preinstall-verification-20261114/backup-reviewed.sh b/outputs/offers-preinstall-verification-20261114/backup-reviewed.sh new file mode 100644 index 00000000..aab27433 --- /dev/null +++ b/outputs/offers-preinstall-verification-20261114/backup-reviewed.sh @@ -0,0 +1,68 @@ +set -eu +python3 - <<'PY' +from pathlib import Path +import os,subprocess,json,hashlib,tarfile,time +assert os.geteuid()==0 and os.environ.get('SSH_CONNECTION') +deadline=time.monotonic()+90 +while subprocess.run(['systemctl','is-active','teste-investidor-deploy.service'],capture_output=True,text=True).stdout.strip() not in ['inactive','failed']: + if time.monotonic()>deadline: raise RuntimeError('Existing deployment still active; no backup or schema change performed') + time.sleep(3) +assert json.loads(Path('/var/lib/investmoz-deploy/installed.json').read_text())['commit']=='96f510965f70581a4b41dd94463f598b77dd7cf5', 'Destination advanced; reconcile first' +assert subprocess.run(['systemctl','is-active','teste-investidor-deploy.service'],capture_output=True,text=True).stdout.strip() in ['inactive','failed'] +root=Path('/var/www/html/teste-investidor.tempo.co.mz/current').resolve() +stage=Path('/var/backups/investmoz-offers-preinstall-20261114/backup') +assert not stage.exists(), 'Existing backup must be reconciled before repeating' +assert __import__('shutil').disk_usage(root).free >= 5*1024**3, 'Capacity changed; active release preserved' +stage.mkdir(mode=0o700,parents=True) +php=r'''make(Illuminate\Contracts\Console\Kernel::class)->bootstrap(); +use Illuminate\Support\Facades\DB; +DB::beginTransaction(); DB::statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY'); DB::statement("SET TIME ZONE 'UTC'"); +try { + $identity=DB::selectOne('select current_database() as database,inet_server_port() as port'); + if($identity->database!=='teste_investidor'||(int)$identity->port!==5432)throw new RuntimeException('Wrong active source'); + $snapshot=DB::selectOne('select pg_export_snapshot() as id')->id; + $tables=[];$sql="BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY; SET TRANSACTION SNAPSHOT '".$snapshot."'; SET TIME ZONE 'UTC';\n"; + foreach(DB::select("select schemaname,tablename from pg_tables where schemaname not in ('pg_catalog','information_schema') and schemaname not like 'pg_%' order by schemaname,tablename") as $t){ + $name=$t->schemaname.'.'.$t->tablename;$quoted='"'.str_replace('"','""',$t->schemaname).'"."'.str_replace('"','""',$t->tablename).'"'; + $sql.="select json_build_object('name','".$name."','rows',count(*),'checksum',md5(coalesce(string_agg(row_to_json(t)::text,E'\\n' order by md5(row_to_json(t)::text) COLLATE \"C\"),''))) from ".$quoted." t;\n"; + } + $sql.='ROLLBACK;';$reader=new Symfony\Component\Process\Process(['runuser','-u','postgres','--','psql','-X','-q','-A','-t','-v','ON_ERROR_STOP=1','-d','teste_investidor']);$reader->setInput($sql)->setTimeout(90)->mustRun(); + foreach(explode("\n",trim($reader->getOutput())) as $line){$row=json_decode($line,true,512,JSON_THROW_ON_ERROR);$name=$row['name'];unset($row['name']);$tables[$name]=$row;} + $process=new Symfony\Component\Process\Process(['runuser','-u','postgres','--','pg_dump','--format=custom','--no-owner','--no-privileges','--lock-wait-timeout=10000','--snapshot='.$snapshot,'teste_investidor']); + $process->setTimeout(90)->mustRun();file_put_contents('/var/backups/investmoz-offers-preinstall-20261114/backup/database.dump',$process->getOutput()); + echo json_encode(['target'=>$identity,'tables'=>$tables,'consistent_exported_snapshot'=>true,'importer_fingerprint'=>app(App\Services\TerritoryGeometryImport::class)->fingerprint(),'scanner'=>['socket'=>config('services.malware.socket'),'host'=>config('services.malware.host'),'port'=>config('services.malware.port')]],JSON_THROW_ON_ERROR); +}finally{DB::rollBack();} +''' +answer=json.loads(subprocess.check_output(['php8.4'],input=php.encode(),cwd=root)) +dump=stage/'database.dump';os.chmod(dump,0o600) +listing=subprocess.check_output(['pg_restore','--list',str(dump)]).decode() +assert 'TABLE DATA public geo_locations' in listing and 'TABLE DATA public geo_geometries' in listing +storage=(root/'storage/app').resolve() +manifest={} +for p in sorted(storage.rglob('*')): + if p.is_file() and not p.is_symlink():manifest['storage/app/'+str(p.relative_to(storage))]={'bytes':p.stat().st_size,'sha256':hashlib.sha256(p.read_bytes()).hexdigest()} +env=(root/'.env').resolve();manifest['configuration/.env']={'bytes':env.stat().st_size,'sha256':hashlib.sha256(env.read_bytes()).hexdigest()} +archive=stage/'files-before.tar.gz' +with tarfile.open(archive,'w:gz') as tf: + tf.add(storage,arcname='storage/app',recursive=True);tf.add(env,arcname='configuration/.env',recursive=False) +os.chmod(archive,0o600) +verified=0 +with tarfile.open(archive,'r:gz') as tf: + for member in tf: + if member.isfile(): + expected=manifest[member.name];f=tf.extractfile(member);digest=hashlib.sha256(f.read()).hexdigest() + assert digest==expected['sha256'] and member.size==expected['bytes'];verified+=1 +assert verified==len(manifest) +answer.update({'created_at':time.strftime('%Y-%m-%dT%H:%M:%SZ',time.gmtime()),'dump_path':str(dump),'sha256':hashlib.sha256(dump.read_bytes()).hexdigest(),'files_path':str(archive),'files_sha256':hashlib.sha256(archive.read_bytes()).hexdigest(),'files_verified':True,'file_count':verified,'file_manifest':manifest,'active_data_writes':0,'restore_verified':False,'root':str(root)}) +receipt=stage/'backup-receipt.json';receipt.write_text(json.dumps(answer,indent=2));os.chmod(receipt,0o600) +assert json.loads(Path('/var/lib/investmoz-deploy/installed.json').read_text())['commit']=='96f510965f70581a4b41dd94463f598b77dd7cf5', 'Source revision changed during backup; do not install' +print(json.dumps({'stage':str(stage),'created_at':answer['created_at'],'target':answer['target'],'tables':len(answer['tables']),'files_verified':verified,'dump_sha256':answer['sha256'],'files_sha256':answer['files_sha256'],'restore_verified':False})) + +import shutil +for name in ['delivery.json','schema-contract.json','ci-environment.json']: + shutil.copy2('/etc/investmoz-deploy/'+name,stage/name) +shutil.copy2('/var/lib/investmoz-deploy/installed.json',stage/'installed.json') +shutil.copy2('/var/www/deploy/investmoz-pipeline.py',stage/'pipeline.py') + +PY diff --git a/outputs/offers-preinstall-verification-20261114/backup-summary.json b/outputs/offers-preinstall-verification-20261114/backup-summary.json new file mode 100644 index 00000000..018ec2a2 --- /dev/null +++ b/outputs/offers-preinstall-verification-20261114/backup-summary.json @@ -0,0 +1,13 @@ +{ + "stage": "/var/backups/investmoz-offers-preinstall-20261114/backup", + "created_at": "2026-11-14T17:34:37Z", + "target": { + "database": "teste_investidor", + "port": 5432 + }, + "tables": 219, + "files_verified": 417, + "dump_sha256": "662c5d9f1fe7e01a5ccf7529c8c8e8f27205907d60d2da75a580b7b6c37c252e", + "files_sha256": "f438f31f212e516442526b609bf5ac02cfd2f630a6ac2a2b6fc12f1f88717a42", + "restore_verified": false +} diff --git a/outputs/offers-preinstall-verification-20261114/constraints-automatics.json b/outputs/offers-preinstall-verification-20261114/constraints-automatics.json new file mode 100644 index 00000000..8a9814e9 --- /dev/null +++ b/outputs/offers-preinstall-verification-20261114/constraints-automatics.json @@ -0,0 +1,49 @@ +{ + "environment": "local", + "postgres_version": "17.11 (Debian 17.11-1.pgdg12+2)", + "extensions": [ + { + "extname": "postgis", + "extversion": "3.6.4" + }, + { + "extname": "pgcrypto", + "extversion": "1.3" + } + ], + "notification_constraints": [ + { + "conname": "editorial_native_notice_unique" + }, + { + "conname": "portal_editorial_notice_subject" + }, + { + "conname": "portal_editorial_notifications_edition_id_foreign" + }, + { + "conname": "portal_editorial_notifications_native_event_id_foreign" + }, + { + "conname": "portal_editorial_notifications_opportunity_id_foreign" + }, + { + "conname": "portal_editorial_notifications_pkey" + }, + { + "conname": "portal_editorial_notifications_user_id_foreign" + }, + { + "conname": "portal_editorial_op_notice_uq" + } + ], + "notification_unique_index_present": true, + "offer_code_present": { + "preparation": false, + "operations": false, + "public_catalogue": false + }, + "offer_notification_jobs": 0, + "offer_notification_failures": 0, + "operational_writes": 0 +} diff --git a/outputs/offers-preinstall-verification-20261114/data-preservation.json b/outputs/offers-preinstall-verification-20261114/data-preservation.json new file mode 100644 index 00000000..b818f0d8 --- /dev/null +++ b/outputs/offers-preinstall-verification-20261114/data-preservation.json @@ -0,0 +1,11 @@ +{ + "database_tables_rechecked": 219, + "tables_changed_since_backup": [ + "public.sessions" + ], + "release_files_rechecked": 9873, + "release_files_changed": [], + "installed_revision": "96f510965f70581a4b41dd94463f598b77dd7cf5", + "active_data_writes": 0, + "source_read_only_transactions": true +} diff --git a/outputs/offers-preinstall-verification-20261114/executor-operations.json b/outputs/offers-preinstall-verification-20261114/executor-operations.json new file mode 100644 index 00000000..a590eabf --- /dev/null +++ b/outputs/offers-preinstall-verification-20261114/executor-operations.json @@ -0,0 +1,34 @@ +[ + { + "file": "/var/www/deploy/investmoz-pipeline.py", + "range": [ + 190, + 213 + ], + "source": "190: (release/'storage'/directory).mkdir(parents=True,exist_ok=True)\n191: with self.qa_database():\n192: self.run(['composer','install','--no-interaction','--prefer-dist','--no-scripts'],cwd=release,env=env)\n193: self.run([self.c['php'],'artisan','package:discover','--ansi'],cwd=release,env=env)\n194: # The guard is verified against the actual PostgreSQL identity, not only environment strings.\n195: guard = \"require 'vendor/autoload.php'; $app=require 'bootstrap/app.php'; $app->make(Illuminate\\\\Contracts\\\\Console\\\\Kernel::class)->bootstrap(); if (!App\\\\Support\\\\IsolatedQaDatabase::allowsSchemaChanges()) exit(91);\"\n196: self.run([self.c['php'],'-r',guard],cwd=release,env=env)\n197: # The project intentionally gates later schemas behind nominal authenticated\n198: # installation. Reuse its accepted isolated baseline lifecycle test instead\n199: # of bypassing those guards with a generic migrate of every module.\n200: self.run([self.c['php'],'vendor/bin/phpunit','--configuration=phpunit.investmoz.xml','--do-not-cache-result'],cwd=release,env=env)\n201: self.run(['npm','ci','--no-audit','--no-fund'],cwd=release,env=env)\n202: self.run(['npm','run','build'],cwd=release,env=env)\n203: self.run(['npm','run','types:check'],cwd=release,env=env)\n204: self.run([self.c['php'],'tools/portal-diagnostics-deployment-guard.php'],cwd=release,env=env)\n205: # Unit fixtures mock DB/mail and select their own feature settings.\n206: # Do not inject the destination's enabled-module flags into those mocks.\n207: unit_env={k:v for k,v in env.items() if not k.startswith(('PORTAL_','INVESTMOZ_'))}\n208: self.run([self.c['php'],'vendor/bin/phpunit','--configuration=phpunit.xml','--testsuite','Unit','--do-not-cache-result'],cwd=release,env=unit_env)\n209: tests=sorted((release/'tests/frontend').glob('*.test.mjs'))\n210: self.run(['node','--test',*tests],cwd=release,env=env)\n211: # Final production vendor from the same lock, before linking any real configuration.\n212: self.run(['composer','install','--no-dev','--no-scripts','--no-interaction','--prefer-dist','--optimize-autoloader'],cwd=release,env=env)\n213: self.run(['composer','check-platform-reqs','--no-dev'],cwd=release,env=env)" + }, + { + "file": "/var/www/deploy/investmoz-pipeline.py", + "range": [ + 238, + 277 + ], + "source": "238: 'built_at':datetime.datetime.now(datetime.timezone.utc).isoformat(),\n239: 'manifest_sha256':digest(release/'public/build/manifest.json')})\n240: for directory in [release/'bootstrap/cache']:\n241: os.chmod(directory,0o2770)\n242: \n243: def health(self, target: str) -> None:\n244: request=urllib.request.Request(self.c['target_url']+'/build/revision.json',headers={'User-Agent':'Mozilla/5.0 InvesteMoz-Delivery','Cache-Control':'no-cache'})\n245: with urllib.request.urlopen(request,timeout=20) as response:info=json.load(response)\n246: if info.get('commit')!=target:raise RuntimeError('Served commit differs from built commit')\n247: request=urllib.request.Request(self.c['target_url']+'/',headers={'User-Agent':'Mozilla/5.0 InvesteMoz-Delivery','Cache-Control':'no-cache'})\n248: with urllib.request.urlopen(request,timeout=20) as response:\n249: if response.status!=200 or response.headers.get('WWW-Authenticate'):raise RuntimeError('Public access regression')\n250: \n251: def deliver(self, target: str) -> dict:\n252: installed_file=self.state/'installed.json'\n253: installed=json.loads(installed_file.read_text())\n254: if installed.get('commit')==target:\n255: refuse_drift(self.current.resolve(),installed['hashes'])\n256: return {'status':'already_installed','commit':target}\n257: refuse_drift(self.current.resolve(),installed['hashes'])\n258: self.releases.mkdir(parents=True,exist_ok=True)\n259: if shutil.disk_usage(self.releases).free < 5 * 1024**3:\n260: raise RuntimeError('Insufficient free space for an isolated build; active release preserved')\n261: release=self.releases/(target+'-'+uuid.uuid4().hex[:8]);release.mkdir(mode=0o750)\n262: self.build(target,release)\n263: if self.latest()!=target:\n264: latest=self.git('rev-parse','refs/heads/delivery-target')\n265: write_json(self.state/'requests'/(latest+'.json'),{'commit':latest,'reason':'newer integration during build'})\n266: return {'status':'superseded','commit':target,'newer_commit':latest}\n267: # A programmer may have edited the served release while CI was running.\n268: # Recheck immediately before the atomic switch, preserving that work.\n269: refuse_drift(self.current.resolve(),installed['hashes'])\n270: previous=promote(self.current,release,lambda:self.health(target))\n271: try:\n272: self.run([self.c['php'],str(release/'artisan'),'queue:restart'],cwd=release)\n273: write_json(self.state/'previous.json',installed)\n274: info={'status':'installed','commit':target,'release':str(release),'previous_release':str(previous),\n275: 'hashes':tree_hashes(release),'database_migrations':0,'content_imports':0}\n276: write_json(installed_file,info)\n277: except Exception:" + }, + { + "file": "/var/www/deploy/investmoz-pipeline.py", + "range": [ + 287, + 308 + ], + "source": "287: fcntl.flock(lock,fcntl.LOCK_EX)\n288: succeeded=True\n289: for _ in range(3):\n290: requests=sorted((self.state/'requests').glob('*.json'))\n291: if not requests:return succeeded\n292: for path in requests:\n293: if not SHA.fullmatch(path.stem):raise RuntimeError('Unexpected queue entry')\n294: path.rename(self.state/'processed'/(path.stem+'-'+uuid.uuid4().hex[:8]+'.json'))\n295: target=None;report={}\n296: with (self.state/'logs'/(str(time.time_ns())+'.log')).open('wb') as self.log:\n297: try:\n298: target=self.latest();report=self.deliver(target)\n299: except Exception as error:\n300: succeeded=False\n301: report={'status':'failed','commit':target,'error':str(error),'active_release_preserved':True}\n302: finally:self.log=None\n303: report.update(requested_commits=[p.stem for p in requests],finished_at=datetime.datetime.now(datetime.timezone.utc).isoformat())\n304: write_json(self.state/'reports'/((target or 'fetch-failed')+'-'+str(time.time_ns())+'.json'),report)\n305: print(json.dumps(report),flush=True)\n306: return succeeded\n307: \n308: def rollback(self) -> dict:" + }, + { + "file": "/var/www/deploy/investmoz-pipeline.py", + "range": [ + 310, + 327 + ], + "source": "310: with (self.state/'delivery.lock').open('a') as lock:\n311: fcntl.flock(lock,fcntl.LOCK_EX)\n312: installed=json.loads((self.state/'installed.json').read_text())\n313: previous=json.loads((self.state/'previous.json').read_text())\n314: refuse_drift(self.current.resolve(),installed['hashes'])\n315: prior=Path(previous['release'])\n316: refuse_drift(prior,previous['hashes'])\n317: current=self.current.resolve()\n318: promote(self.current,prior,lambda:self.health(previous['commit']))\n319: try:\n320: self.run([self.c['php'],str(prior/'artisan'),'queue:restart'],cwd=prior)\n321: write_json(self.state/'installed.json',previous)\n322: write_json(self.state/'previous.json',installed)\n323: except Exception:\n324: point_current(self.current,current)\n325: write_json(self.state/'installed.json',installed)\n326: raise\n327: report={'status':'rolled_back','commit':previous['commit'],'release':str(prior)," + } +] diff --git a/outputs/offers-preinstall-verification-20261114/executor-status.json b/outputs/offers-preinstall-verification-20261114/executor-status.json new file mode 100644 index 00000000..9430cae5 --- /dev/null +++ b/outputs/offers-preinstall-verification-20261114/executor-status.json @@ -0,0 +1,152 @@ +[ + { + "executor_entrypoints": [ + "/var/www/deploy/investmoz-pipeline.py", + "/etc/investmoz-deploy/delivery.json" + ] + }, + { + "file": "/var/www/deploy/investmoz-pipeline.py", + "operations": [ + { + "line": 4, + "code": "Runs as the existing deployment OS user. Never resets the served checkout," + }, + { + "line": 33, + "code": "folders[:] = [name for name in folders if not (parent/name).is_symlink()" + }, + { + "line": 38, + "code": "if path.is_symlink() or rel=='.env' or rel.startswith('public/build/assets/'):continue" + }, + { + "line": 55, + "code": "if item.name == '.env' or item.name == 'public/hot' or (item.isfile()" + }, + { + "line": 72, + "code": "if link.exists() and not link.is_symlink():" + }, + { + "line": 73, + "code": "raise RuntimeError('Current path is not a symlink; existing directory preserved')" + }, + { + "line": 75, + "code": "temporary.symlink_to(target, target_is_directory=True)" + }, + { + "line": 79, + "code": "previous = link.resolve() if link.is_symlink() else None" + }, + { + "line": 121, + "code": "self.git('fetch', '--no-tags', 'origin', '+refs/heads/'+branch+':refs/heads/delivery-target')" + }, + { + "line": 124, + "code": "raise RuntimeError('Invalid fetched revision')" + }, + { + "line": 185, + "code": "if not php.exists(): php.symlink_to(self.c['php'])" + }, + { + "line": 199, + "code": "# of bypassing those guards with a generic migrate of every module." + }, + { + "line": 201, + "code": "self.run(['npm','ci','--no-audit','--no-fund'],cwd=release,env=env)" + }, + { + "line": 202, + "code": "self.run(['npm','run','build'],cwd=release,env=env)" + }, + { + "line": 203, + "code": "self.run(['npm','run','types:check'],cwd=release,env=env)" + }, + { + "line": 214, + "code": "if (release/'public/hot').exists(): raise RuntimeError('Development Vite marker must not ship')" + }, + { + "line": 225, + "code": "(release/'storage').symlink_to(self.legacy/'storage',target_is_directory=True)" + }, + { + "line": 226, + "code": "(release/'.env').symlink_to(self.legacy/'.env')" + }, + { + "line": 228, + "code": "(release/'public/storage').symlink_to(self.legacy/'storage/app/public',target_is_directory=True)" + }, + { + "line": 252, + "code": "installed_file=self.state/'installed.json'" + }, + { + "line": 265, + "code": "write_json(self.state/'requests'/(latest+'.json'),{'commit':latest,'reason':'newer integration during build'})" + }, + { + "line": 272, + "code": "self.run([self.c['php'],str(release/'artisan'),'queue:restart'],cwd=release)" + }, + { + "line": 287, + "code": "fcntl.flock(lock,fcntl.LOCK_EX)" + }, + { + "line": 290, + "code": "requests=sorted((self.state/'requests').glob('*.json'))" + }, + { + "line": 291, + "code": "if not requests:return succeeded" + }, + { + "line": 292, + "code": "for path in requests:" + }, + { + "line": 303, + "code": "report.update(requested_commits=[p.stem for p in requests],finished_at=datetime.datetime.now(datetime.timezone.utc).isoformat())" + }, + { + "line": 304, + "code": "write_json(self.state/'reports'/((target or 'fetch-failed')+'-'+str(time.time_ns())+'.json'),report)" + }, + { + "line": 311, + "code": "fcntl.flock(lock,fcntl.LOCK_EX)" + }, + { + "line": 312, + "code": "installed=json.loads((self.state/'installed.json').read_text())" + }, + { + "line": 320, + "code": "self.run([self.c['php'],str(prior/'artisan'),'queue:restart'],cwd=prior)" + }, + { + "line": 321, + "code": "write_json(self.state/'installed.json',previous)" + }, + { + "line": 325, + "code": "write_json(self.state/'installed.json',installed)" + } + ] + }, + { + "file": "/etc/investmoz-deploy/delivery.json", + "operations": [] + }, + { + "pending_request_count": 0 + } +] diff --git a/outputs/offers-preinstall-verification-20261114/preflight-reviewed.sh b/outputs/offers-preinstall-verification-20261114/preflight-reviewed.sh new file mode 100644 index 00000000..4fe78a33 --- /dev/null +++ b/outputs/offers-preinstall-verification-20261114/preflight-reviewed.sh @@ -0,0 +1,30 @@ +set -eu +python3 - <<'REMOTE' +import json,pathlib,subprocess,os +assert os.environ.get('SSH_CONNECTION') +root=pathlib.Path('/var/www/html/teste-investidor.tempo.co.mz/current').resolve() +php=r"""make(Illuminate\Contracts\Console\Kernel::class)->bootstrap(); +use Illuminate\Support\Facades\DB;use Illuminate\Support\Facades\Schema; +DB::beginTransaction();DB::statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY'); +try{ +$id=DB::selectOne('select current_database() as database,inet_server_port() as port');if($id->database!=='teste_investidor'||(int)$id->port!==5432)throw new RuntimeException('Wrong source'); +$tables=['opportunities'=>['management_version','workflow_context','owner_user_id','organization_id','management_draft'], 'users'=>['portal_access_revision'],'portal_role_assignments'=>['role','opportunity_types'],'portal_superuser_pair'=>[], 'portal_profile_requests'=>[], 'portal_security_approvals'=>[], 'opportunity_images'=>[], 'opportunity_documents'=>[], 'opportunity_locations'=>[], 'documents'=>['storage_encryption','malware_scan_status','malware_scanned_sha256'],'portal_editorial_editions'=>[], 'portal_editorial_notifications'=>['opportunity_id','opportunity_version','content_hash'],'audit_events'=>[], 'tasks'=>[], 'desk_cases'=>['payload','simulation_id'], 'investor_navigation_preferences'=>[], 'simulation_form_drafts'=>[]]; +$schema=[];foreach($tables as $table=>$columns){$exists=Schema::hasTable($table);$schema[$table]=['exists'=>$exists,'missing_columns'=>$exists?array_values(array_filter($columns,fn($c)=>!Schema::hasColumn($table,$c))):$columns];} +$offer=Schema::hasColumn('opportunities','workflow_context')?DB::selectOne("select count(*) filter(where workflow_context->'private_offer_preparation' is not null) as private_offers,count(*) filter(where workflow_context->'private_offer_preparation' is not null and (status='published' or published_at is not null)) as unexpected_published,count(*) filter(where workflow_context->'offer_publication' is not null) as stored_projections from opportunities"):null; +$routes=[];foreach($app->make('router')->getRoutes() as $route){if(str_contains($route->uri(),'ofertas')||str_contains($route->uri(),'offers'))$routes[]=['uri'=>$route->uri(),'methods'=>$route->methods(),'action'=>$route->getActionName()];} +$recipients=config('portal.notification_recipients',[]);sort($recipients); +echo json_encode(['database'=>$id,'php'=>PHP_VERSION,'gd'=>extension_loaded('gd'),'imagecreatefromstring'=>function_exists('imagecreatefromstring'),'imagewebp'=>function_exists('imagewebp'),'schema'=>$schema,'offers'=>$offer,'offer_routes'=>$routes,'flags'=>['profile_access'=>(bool)config('portal.profile_access_enabled'),'editorial'=>(bool)config('editorial_workflow.enabled'),'notifications'=>(bool)config('portal.notifications_enabled'),'homologation_only'=>(bool)config('portal.homologation_only'),'homologation_access_mode'=>config('portal.homologation_access_mode'),'nda_present'=>config('private_offers.nda')!==null,'expiry_scheduler'=>(bool)config('private_offers.expiry_scheduler_enabled',false)],'mail'=>['mailer'=>config('mail.default'),'smtp_host_configured'=>(bool)config('mail.mailers.smtp.host'),'recipient_count'=>count($recipients),'recipient_set_sha256'=>hash('sha256',json_encode($recipients))],'runtime'=>['queue'=>config('queue.default'),'private_disk'=>config('filesystems.default'),'scanner_host_configured'=>(bool)config('services.malware.host'),'scanner_socket_configured'=>(bool)config('services.malware.socket')],'migration_count'=>DB::table('migrations')->count(),'config_cached'=>$app->configurationIsCached(),'operational_data_writes'=>0],JSON_THROW_ON_ERROR); +}finally{DB::rollBack();} +""" +q=json.loads(subprocess.check_output(['php8.4'],input=php.encode(),cwd=root,timeout=90)) +info=subprocess.run(['php-fpm8.4','-i'],capture_output=True,text=True,timeout=20).stdout +q['fpm_image_capabilities']={'gd_enabled':'GD Support => enabled' in info,'webp_enabled':'WebP Support => enabled' in info} +units=subprocess.run(['systemctl','list-units','--type=service','--all','--plain','--no-legend'],capture_output=True,text=True,timeout=15).stdout.splitlines() +q['service_units']=[' '.join(line.split()[:4]) for line in units if any(x in line.lower() for x in ['teste-investidor','investmoz','nginx','php8.4','postgresql','clamav'])] +config=json.loads(pathlib.Path('/etc/investmoz-deploy/delivery.json').read_text());q['restore_image']=config.get('postgis_image');q['executor_config_keys']=sorted(config) +installed=json.loads(pathlib.Path('/var/lib/investmoz-deploy/installed.json').read_text());q['installed_revision']=installed.get('commit') +rev=root/'public/build/revision.json';q['served_build_revision']=json.loads(rev.read_text()).get('commit') if rev.exists() else None +q['executor_service_state']=subprocess.run(['systemctl','is-active','teste-investidor-deploy.service'],capture_output=True,text=True).stdout.strip() +print(json.dumps(q)) +REMOTE diff --git a/outputs/offers-preinstall-verification-20261114/preservation-reviewed.sh b/outputs/offers-preinstall-verification-20261114/preservation-reviewed.sh new file mode 100644 index 00000000..af023eeb --- /dev/null +++ b/outputs/offers-preinstall-verification-20261114/preservation-reviewed.sh @@ -0,0 +1,15 @@ +set -eu +python3 - <<'REMOTE' +from pathlib import Path +import subprocess,json,hashlib,os +assert os.environ.get('SSH_CONNECTION') +root=Path('/var/www/html/teste-investidor.tempo.co.mz/current').resolve() +stage=Path('/var/backups/investmoz-offers-preinstall-20261114/backup') +php='make(Illuminate\\Contracts\\Console\\Kernel::class)->bootstrap();\nuse Illuminate\\Support\\Facades\\DB;\nDB::beginTransaction(); DB::statement(\'SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY\'); DB::statement("SET TIME ZONE \'UTC\'");\ntry {\n $identity=DB::selectOne(\'select current_database() as database,inet_server_port() as port\');\n if($identity->database!==\'teste_investidor\'||(int)$identity->port!==5432)throw new RuntimeException(\'Wrong active source\');\n $snapshot=DB::selectOne(\'select pg_export_snapshot() as id\')->id;\n $tables=[];$sql="BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY; SET TRANSACTION SNAPSHOT \'".$snapshot."\'; SET TIME ZONE \'UTC\';\\n";\n foreach(DB::select("select schemaname,tablename from pg_tables where schemaname not in (\'pg_catalog\',\'information_schema\') and schemaname not like \'pg_%\' order by schemaname,tablename") as $t){\n $name=$t->schemaname.\'.\'.$t->tablename;$quoted=\'"\'.str_replace(\'"\',\'""\',$t->schemaname).\'"."\'.str_replace(\'"\',\'""\',$t->tablename).\'"\';\n $sql.="select json_build_object(\'name\',\'".$name."\',\'rows\',count(*),\'checksum\',md5(coalesce(string_agg(row_to_json(t)::text,E\'\\\\n\' order by md5(row_to_json(t)::text) COLLATE \\"C\\"),\'\'))) from ".$quoted." t;\\n";\n }\n $sql.=\'ROLLBACK;\';$reader=new Symfony\\Component\\Process\\Process([\'runuser\',\'-u\',\'postgres\',\'--\',\'psql\',\'-X\',\'-q\',\'-A\',\'-t\',\'-v\',\'ON_ERROR_STOP=1\',\'-d\',\'teste_investidor\']);$reader->setInput($sql)->setTimeout(90)->mustRun();\n foreach(explode("\\n",trim($reader->getOutput())) as $line){$row=json_decode($line,true,512,JSON_THROW_ON_ERROR);$name=$row[\'name\'];unset($row[\'name\']);$tables[$name]=$row;}\n echo json_encode([\'target\'=>$identity,\'tables\'=>$tables],JSON_THROW_ON_ERROR);\n}finally{DB::rollBack();}\n' +actual=json.loads(subprocess.check_output(['php8.4'],input=php.encode(),cwd=root,timeout=120)) +receipt=json.loads((stage/'backup-receipt.json').read_text()) +changes=[name for name in set(receipt['tables'])|set(actual['tables']) if receipt['tables'].get(name)!=actual['tables'].get(name)] +files=json.loads((stage/'release-files-private.json').read_text());code_changes=[name for name,want in files.items() if not (root/name).is_file() or hashlib.sha256((root/name).read_bytes()).hexdigest()!=want] +proof={'database_tables_rechecked':len(actual['tables']),'tables_changed_since_backup':sorted(changes),'release_files_rechecked':len(files),'release_files_changed':code_changes,'installed_revision':json.loads(Path('/var/lib/investmoz-deploy/installed.json').read_text())['commit'],'active_data_writes':0,'source_read_only_transactions':True} +(stage/'preservation-receipt.json').write_text(json.dumps(proof,indent=2));print(json.dumps(proof)) +REMOTE diff --git a/outputs/offers-preinstall-verification-20261114/qa-result.json b/outputs/offers-preinstall-verification-20261114/qa-result.json new file mode 100644 index 00000000..7fffe0a9 --- /dev/null +++ b/outputs/offers-preinstall-verification-20261114/qa-result.json @@ -0,0 +1,86 @@ +{ + "verdict": "APTO COM CONDIÇÕES", + "scope": "controlled private homologation; no operational publication", + "installed_revision": "96f510965f70581a4b41dd94463f598b77dd7cf5", + "accepted_integration_candidate": "9a014268", + "candidate_including_preview_menu_delta": "3558647e8d82092cd0e31eefbe8b9e5e9eacf573", + "installation_authorized": false, + "installation_performed": false, + "operational_migrations": false, + "operational_data_or_permissions_or_configuration_changes": false, + "real_email_sent": false, + "backup": { + "stage": "/var/backups/investmoz-offers-preinstall-20261114/backup", + "created_at": "2026-11-14T17:34:37Z", + "target": { + "database": "teste_investidor", + "port": 5432 + }, + "tables": 219, + "files_verified": 417, + "dump_sha256": "662c5d9f1fe7e01a5ccf7529c8c8e8f27205907d60d2da75a580b7b6c37c252e", + "files_sha256": "f438f31f212e516442526b609bf5ac02cfd2f630a6ac2a2b6fc12f1f88717a42", + "restore_verified": false + }, + "restore": { + "restore_verified": true, + "all_table_counts_and_checksums_match": true, + "tables": 219, + "isolated_network": "none", + "isolated_database": "offers_preinstall_restore_20261114_test", + "isolated_port": 55441, + "schema_changes": 0, + "operational_data_writes": 0, + "files_restored_and_verified": 417, + "encryption": { + "encrypted_originals_recovered": 0, + "mfa_envelopes_recovered": 2, + "preserved_keys_verified": true + } + }, + "dependencies_missing": [], + "gd_cli_and_fpm_available": true, + "in_memory_codec_passed": true, + "destination_offer_routes": [], + "destination_offer_counts": { + "private_offers": 0, + "unexpected_published": 0, + "stored_projections": 0 + }, + "notifications_enabled": false, + "authorized_email_recipient_count": 0, + "condition_for_exact_revision": "Existing executor follows latest main, not requested SHA. Exclusive integration window with only the expressly authorised revision is required; otherwise do not enqueue/push. No executor changes were made.", + "remaining_conditions": [ + "explicit installation authorisation naming candidate and menu delta", + "recheck installed revision, main head, empty deploy queue and current backup before actual installation", + "preserve operational publication/NDA/scheduler closure and notifications disabled", + "desktop/mobile authenticated acceptance pending", + "real controlled mailbox not designated; no real-email test yet", + "NDA, property-proof and legal-publication decisions pending" + ], + "guarded_restore": "new container, pinned existing image, network none, no published port, removed after verification", + "evidence_sha256": { + "backup-reviewed.sh": "ddea0084d9cc2e593e4b2836d27a39fac22f5bfa20e9432545de48d56c5a9a71", + "backup-summary.json": "ca1f9fd3aebbf26059a9b015e71a3864172a3fe1e479b3ea57d97a6e3807167c", + "constraints-automatics.json": "5b3a1b979c7fd06d4f745ed3814115ba4727982ea0b7e321226499bdfcda163f", + "data-preservation.json": "a27f55d2e75ef8f14981d126482cb1d33b24a7132400f22f914d8dc51f5f389f", + "executor-operations.json": "448d1abbe9ed6c44b775a09dd7198e1915bd4cd388e241213221d96b5cac96bb", + "executor-status.json": "3cac96822ff3a24d135ba12dca93ba2203f6b308f3e465ba38600d7045899efc", + "preflight-reviewed.sh": "7a0c701cfa27345268475594604f0d9db19037358e4a55cd6b6151b8b34cc199", + "preservation-reviewed.sh": "d735500fb90581a73839478f1e3642584470a79368f69a8bebc9b251853b30d1", + "restore-reviewed.sh": "4b2f16350f65b01e9105c5f0a31bad7b4bcaaf92cf67fb296903ca55c1f9b6ad", + "restore-summary.json": "c9e96c7fd4eb94b02f9d80508970ceb5413510910ad86e37d12f3edeb928b82f", + "security-runtime.json": "564da2cd07c0937687bb579c2e91395b4f598af2f3e765ca9122f39f495df7f2", + "session-reconciliation.json": "caa6dc326d1e0dfc6d0c594ea96e935f9303d39b1fb6b8871ef97fe9ca4c2008", + "target-final-preservation.json": "e6e6daccdef1568b93f889312d76c5d651aec3a3b85231624ac832385a0a4d35", + "target-identity.json": "b276128c113ab7b34a6c9ab8f08f5a39a5d8d710f94dbd6e597a3283e6877eb2", + "target-runtime-schema.json": "c6fce61c40a791dae53c625592b6d8f8b9457ca8a6f0de99bf1c2c9b5a6dc876" + }, + "preservation": { + "unchanged_tables": 218, + "concurrent_new_sessions": 1, + "prior_sessions_unchanged": 51, + "release_files_unchanged": 9873 + }, + "evidence_hash_encoding": "SHA256 of UTF-8 text with Git LF normalisation" +} diff --git a/outputs/offers-preinstall-verification-20261114/restore-reviewed.sh b/outputs/offers-preinstall-verification-20261114/restore-reviewed.sh new file mode 100644 index 00000000..51fa529a --- /dev/null +++ b/outputs/offers-preinstall-verification-20261114/restore-reviewed.sh @@ -0,0 +1,80 @@ +set -eu +python3 - <<'PY' +from pathlib import Path +import subprocess,json,time,uuid,os +assert os.environ.get('SSH_CONNECTION') +stage=Path('/var/backups/investmoz-offers-preinstall-20261114/backup') +receipt=json.loads((stage/'backup-receipt.json').read_text()) +config=json.loads((stage/'delivery.json').read_text()) +name='investmoz-preservation-restore-'+uuid.uuid4().hex[:10] +def run(args,**kw):return subprocess.check_output(args,stderr=subprocess.STDOUT,**kw) +run(['docker','image','inspect',config['postgis_image']]) # Reuse an existing pinned image; never pull software. +cid=run(['docker','run','-d','--name',name,'--label','investmoz.task=offers-preinstall-20261114','--network','none','--memory','768m','--env','POSTGRES_HOST_AUTH_METHOD=trust','--env','POSTGRES_DB=offers_preinstall_restore_20261114_test',config['postgis_image'],'postgres','-p','55441']).decode().strip() +assert len(cid)==64 +try: + for _ in range(40): + if subprocess.run(['docker','exec',cid,'pg_isready','-p','55441','-U','postgres'],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL).returncode==0:break + time.sleep(1) + else:raise RuntimeError('Restore database not ready') + def sql(text):return run(['docker','exec','-i',cid,'psql','-p','55441','-U','postgres','-d','offers_preinstall_restore_20261114_test','-X','-q','-A','-t','-v','ON_ERROR_STOP=1'],input=text.encode()).decode() + assert sql('select current_database(),inet_server_port(),current_setting(\'port\');').strip()=='offers_preinstall_restore_20261114_test||55441' + # Preserve policy role names, without copying credentials or grants into the test instance. + roles=run(['runuser','-u','postgres','--','psql','-XAt','-d','teste_investidor','-c',"select rolname from pg_roles where rolname !~ '^pg_' and rolname <> 'postgres'"]).decode().splitlines() + for role in roles:sql('CREATE ROLE "'+role.replace('"','""')+'" NOLOGIN;') + run(['docker','cp',str(stage/'database.dump'),cid+':/tmp/database.dump']) + run(['docker','exec',cid,'pg_restore','-p','55441','-U','postgres','-d','offers_preinstall_restore_20261114_test','--no-owner','--no-privileges','--exit-on-error','/tmp/database.dump']) + text="SET TIME ZONE 'UTC';\n" + for table in receipt['tables']: + quoted='.'.join('"'+part.replace('"','""')+'"' for part in table.split('.',1)) + text+="select json_build_object('name','"+table+"','rows',count(*),'checksum',md5(coalesce(string_agg(row_to_json(t)::text,E'\\n' order by md5(row_to_json(t)::text) COLLATE \"C\"),''))) from "+quoted+" t;\n" + restored={} + for line in sql(text).splitlines(): + row=json.loads(line);table=row.pop('name');restored[table]=row + assert restored==receipt['tables'], 'Restore rows/checksums differ' + + # Extract regular files only into a new private directory, never into the active release. + import tarfile,hashlib,shutil + files=stage/'restore-files-rehearsal' + files.mkdir(mode=0o700) + recovered=0 + with tarfile.open(stage/'files-before.tar.gz','r:gz') as archive: + for member in archive: + if not member.isfile():continue + part=Path(member.name) + assert not part.is_absolute() and '..' not in part.parts + assert member.name in receipt['file_manifest'] + destination=files/part;destination.parent.mkdir(mode=0o700,parents=True,exist_ok=True) + with archive.extractfile(member) as source, destination.open('xb') as output:shutil.copyfileobj(source,output) + expected=receipt['file_manifest'][member.name] + assert destination.stat().st_size==expected['bytes'] and hashlib.sha256(destination.read_bytes()).hexdigest()==expected['sha256'] + recovered+=1 + assert recovered==receipt['file_count'] + documents=json.loads(sql("select coalesce(json_agg(row_to_json(d)),'[]'::json) from (select storage_key,storage_encryption,sha256 from documents where deleted_at is null and storage_encryption='laravel' and storage_key is not null) d;").strip()) + mfa=json.loads(sql("select coalesce(json_agg(row_to_json(u)),'[]'::json) from (select two_factor_secret,two_factor_recovery_codes from users where two_factor_secret is not null) u;").strip()) + (files/'encrypted-records.json').write_text(json.dumps({'documents':documents,'mfa':mfa})) + root=Path('/var/www/html/teste-investidor.tempo.co.mz/current').resolve() + php=r"""make(Illuminate\Contracts\Console\Kernel::class)->bootstrap(); + $files='/var/backups/investmoz-offers-preinstall-20261114/backup/restore-files-rehearsal'; + $env=Dotenv\Dotenv::parse(file_get_contents($files.'/configuration/.env')); + $keys=array_values(array_unique(array_filter([$env['APP_KEY']??null,...explode(',',$env['APP_PREVIOUS_KEYS']??'')]))); + $encrypters=array_map(fn($key)=>new Illuminate\Encryption\Encrypter(str_starts_with($key,'base64:')?base64_decode(substr($key,7),true):$key,config('app.cipher')),$keys); + if(!$encrypters)throw new RuntimeException('Preserved encryption configuration missing'); + $decrypt=function($bytes)use($encrypters){foreach($encrypters as $encrypter){try{return $encrypter->decryptString($bytes);}catch(Throwable $error){}}throw new RuntimeException('Preserved envelope cannot be recovered');}; + $records=json_decode(file_get_contents($files.'/encrypted-records.json'),true,512,JSON_THROW_ON_ERROR); + $source=realpath(base_path('storage/app'));$disk=realpath(config('filesystems.disks.local.root')); + if(!$source||!$disk||!str_starts_with($disk,$source))throw new RuntimeException('Unexpected private disk mapping'); + $relative=ltrim(substr($disk,strlen($source)),'/');$count=0;$mfa=0; + foreach($records['documents'] as $doc){$key=$doc['storage_key'];if(str_contains($key,'..')||str_starts_with($key,'/'))throw new RuntimeException('Unsafe storage key');$path=realpath($files.'/storage/app/'.$relative.'/'.$key);if(!$path||!str_starts_with($path,$files.'/storage/app/'))throw new RuntimeException('Missing recovered original');$bytes=$decrypt(file_get_contents($path));if(!hash_equals(trim($doc['sha256']),hash('sha256',$bytes)))throw new RuntimeException('Recovered original hash mismatch');$count++;} + foreach($records['mfa'] as $row){foreach(['two_factor_secret','two_factor_recovery_codes'] as $field){if(!empty($row[$field]))$decrypt($row[$field]);}$mfa++;} + echo json_encode(['encrypted_originals_recovered'=>$count,'mfa_envelopes_recovered'=>$mfa,'preserved_keys_verified'=>true],JSON_THROW_ON_ERROR); + """ + envelope=json.loads(subprocess.check_output(['php8.4'],input=php.encode(),cwd=root)) + proof={'restore_verified':True,'all_table_counts_and_checksums_match':True,'tables':len(restored),'isolated_network':'none','isolated_database':'offers_preinstall_restore_20261114_test','isolated_port':55441,'schema_changes':0,'operational_data_writes':0,'files_restored_and_verified':recovered,'encryption':envelope} + (stage/'restore-receipt.json').write_text(json.dumps(proof,indent=2)) + print(json.dumps(proof)) +finally: + details=json.loads(run(['docker','inspect',cid]))[0] + assert details['Config']['Labels'].get('investmoz.task')=='offers-preinstall-20261114' and details['HostConfig']['NetworkMode']=='none' + run(['docker','rm','-f','-v',cid]) +PY diff --git a/outputs/offers-preinstall-verification-20261114/restore-summary.json b/outputs/offers-preinstall-verification-20261114/restore-summary.json new file mode 100644 index 00000000..899a13ac --- /dev/null +++ b/outputs/offers-preinstall-verification-20261114/restore-summary.json @@ -0,0 +1,16 @@ +{ + "restore_verified": true, + "all_table_counts_and_checksums_match": true, + "tables": 219, + "isolated_network": "none", + "isolated_database": "offers_preinstall_restore_20261114_test", + "isolated_port": 55441, + "schema_changes": 0, + "operational_data_writes": 0, + "files_restored_and_verified": 417, + "encryption": { + "encrypted_originals_recovered": 0, + "mfa_envelopes_recovered": 2, + "preserved_keys_verified": true + } +} diff --git a/outputs/offers-preinstall-verification-20261114/security-runtime.json b/outputs/offers-preinstall-verification-20261114/security-runtime.json new file mode 100644 index 00000000..9844aca7 --- /dev/null +++ b/outputs/offers-preinstall-verification-20261114/security-runtime.json @@ -0,0 +1,10 @@ +{ + "environment": "local", + "debug": false, + "session_secure": true, + "session_http_only": true, + "session_same_site": "lax", + "app_url": "https://teste-investidor.tempo.co.mz", + "fpm_environment_overrides": [], + "nginx_app_environment_overrides": [] +} diff --git a/outputs/offers-preinstall-verification-20261114/session-reconciliation.json b/outputs/offers-preinstall-verification-20261114/session-reconciliation.json new file mode 100644 index 00000000..69e32104 --- /dev/null +++ b/outputs/offers-preinstall-verification-20261114/session-reconciliation.json @@ -0,0 +1,10 @@ +{ + "prior_sessions": 51, + "current_sessions": 52, + "prior_identifiers_preserved": true, + "removed_prior_sessions": 0, + "new_sessions": 1, + "changed_fields_counts": {}, + "operational_data_writes": 0, + "conclusion": "Live session activity observed during a read-only verification; no session content or identifiers exported." +} diff --git a/outputs/offers-preinstall-verification-20261114/target-final-preservation.json b/outputs/offers-preinstall-verification-20261114/target-final-preservation.json new file mode 100644 index 00000000..829b8207 --- /dev/null +++ b/outputs/offers-preinstall-verification-20261114/target-final-preservation.json @@ -0,0 +1,29 @@ +{ + "installed_revision_unchanged": "96f510965f70581a4b41dd94463f598b77dd7cf5", + "preexisting_files_changed": [], + "release_archive_sha256": "914bc32627a51ec9001d07e5ac31d5717719a4a7f93e43dc26189865962735ca", + "release_regular_files_verified": 9873, + "release_archive_bytes": 25862122, + "backup_directory_mode": "0o700", + "codec": { + "in_memory_codec_passed": true, + "original_files_touched": 0, + "network_requests": 0 + }, + "executor": { + "branch": "main", + "current_link": "/var/www/html/teste-investidor.tempo.co.mz/current", + "legacy_root": "/var/www/html/teste-investidor.tempo.co.mz", + "php": "/usr/bin/php8.4", + "release_root": "/var/www/investmoz-homologacao/releases", + "repository": "nala/Investidor-V2", + "retention": "No automatic deletion of releases or data", + "schema_contract": "/etc/investmoz-deploy/schema-contract.json", + "state_root": "/var/lib/investmoz-deploy", + "target_url": "https://teste-investidor.tempo.co.mz" + }, + "deploy_service": "inactive", + "deploy_path": "active", + "operational_writes": 0, + "services_reconfigured": false +} diff --git a/outputs/offers-preinstall-verification-20261114/target-identity.json b/outputs/offers-preinstall-verification-20261114/target-identity.json new file mode 100644 index 00000000..060f3379 --- /dev/null +++ b/outputs/offers-preinstall-verification-20261114/target-identity.json @@ -0,0 +1,14 @@ +{ + "root": "/var/www/investmoz-homologacao/releases/96f510965f70581a4b41dd94463f598b77dd7cf5-8ee72062", + "git_revision": "", + "working_tree": [], + "installed_revision": "96f510965f70581a4b41dd94463f598b77dd7cf5", + "services": { + "nginx": "active", + "php8.4-fpm": "active", + "teste-investidor-deploy.service": "inactive", + "teste-investidor-deploy.path": "active" + }, + "disk_free_bytes": 24556748800, + "read_only": true +} diff --git a/outputs/offers-preinstall-verification-20261114/target-runtime-schema.json b/outputs/offers-preinstall-verification-20261114/target-runtime-schema.json new file mode 100644 index 00000000..c237212e --- /dev/null +++ b/outputs/offers-preinstall-verification-20261114/target-runtime-schema.json @@ -0,0 +1,146 @@ +{ + "database": { + "database": "teste_investidor", + "port": 5432 + }, + "php": "8.4.27", + "gd": true, + "imagecreatefromstring": true, + "imagewebp": true, + "schema": { + "opportunities": { + "exists": true, + "missing_columns": [] + }, + "users": { + "exists": true, + "missing_columns": [] + }, + "portal_role_assignments": { + "exists": true, + "missing_columns": [] + }, + "portal_superuser_pair": { + "exists": true, + "missing_columns": [] + }, + "portal_profile_requests": { + "exists": true, + "missing_columns": [] + }, + "portal_security_approvals": { + "exists": true, + "missing_columns": [] + }, + "opportunity_images": { + "exists": true, + "missing_columns": [] + }, + "opportunity_documents": { + "exists": true, + "missing_columns": [] + }, + "opportunity_locations": { + "exists": true, + "missing_columns": [] + }, + "documents": { + "exists": true, + "missing_columns": [] + }, + "portal_editorial_editions": { + "exists": true, + "missing_columns": [] + }, + "portal_editorial_notifications": { + "exists": true, + "missing_columns": [] + }, + "audit_events": { + "exists": true, + "missing_columns": [] + }, + "tasks": { + "exists": true, + "missing_columns": [] + }, + "desk_cases": { + "exists": true, + "missing_columns": [] + }, + "investor_navigation_preferences": { + "exists": true, + "missing_columns": [] + }, + "simulation_form_drafts": { + "exists": true, + "missing_columns": [] + } + }, + "offers": { + "private_offers": 0, + "unexpected_published": 0, + "stored_projections": 0 + }, + "offer_routes": [], + "flags": { + "profile_access": true, + "editorial": true, + "notifications": false, + "homologation_only": false, + "homologation_access_mode": "perimeter", + "nda_present": false, + "expiry_scheduler": false + }, + "mail": { + "mailer": "smtp", + "smtp_host_configured": true, + "recipient_count": 0, + "recipient_set_sha256": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945" + }, + "runtime": { + "queue": "database", + "private_disk": "local", + "scanner_host_configured": true, + "scanner_socket_configured": true + }, + "migration_count": 112, + "config_cached": false, + "operational_data_writes": 0, + "fpm_image_capabilities": { + "gd_enabled": true, + "webp_enabled": true + }, + "service_units": [ + "clamav-daemon.service loaded active running", + "clamav-freshclam.service loaded active running", + "nginx.service loaded active running", + "php8.4-fpm.service loaded active running", + "postgresql.service loaded active exited", + "postgresql@17-main.service loaded active running", + "teste-investidor-deploy.service loaded inactive dead", + "teste-investidor-queue.service loaded active running" + ], + "restore_image": "postgis/postgis@sha256:01a6a70e41e6c4467c8f55f6063555ed72db2d6662cd0d571040d42eadaeb6f6", + "executor_config_keys": [ + "branch", + "ci_environment", + "current_link", + "legacy_root", + "php", + "postgis_image", + "qa_port", + "release_root", + "repository", + "retention", + "schema_contract", + "shared_assets", + "state_root", + "target_url", + "test_database", + "webhook_secret_file" + ], + "installed_revision": "96f510965f70581a4b41dd94463f598b77dd7cf5", + "served_build_revision": "96f510965f70581a4b41dd94463f598b77dd7cf5", + "executor_service_state": "inactive" +}