docs: confirm private offers QA and plan phase two

This commit is contained in:
2026-11-14 13:58:33 +02:00
parent 1f2990e4da
commit 6e302db62e
2 changed files with 574 additions and 0 deletions
@@ -0,0 +1,168 @@
# InvesteMoz — encerramento técnico da Fase 1 e plano da Fase 2
Data: 14/11/2026. Referência funcional: RF-MN-003 da matriz existente.
**Revisão examinada: `1f2990e4`. Fase 1 verificada tecnicamente, com ressalva de conferência visual não realizada. Fase 2 apenas planeada, não autorizada para implementação. Nenhuma instalação em homologação ou produção.**
## 1. Resultado do encerramento técnico
A implementação recebida mantém Oportunidades como cadastro principal. Criar, guardar, recuperar, editar e submeter são operações privadas, com os perfis, contactos, localização, versões e auditoria existentes. A submissão não abre automaticamente revisão, aprovação ou publicação. Não foram encontradas alterações posteriores nos 16 ficheiros identificados pelos hashes do recibo, incluindo código, testes e documentação da entrega. Os 17 ficheiros do commit incluem também o próprio recibo.
O encerramento técnico não constitui aceite visual, aprovação da verificação global de formatação ou autorização de instalação. O módulo Ofertas integral e RF-MN-003, que também abrange Procuras, não ficam encerrados por esta fase.
### Evidências confirmadas
| Evidência | Resultado e limite |
| --- | --- |
| `outputs/offers-private-phase1-20261114/qa-result.json` | Recibo recebido conferido; 16 hashes correspondem aos ficheiros examinados |
| Regressão recebida | 35 testes / 548 asserções aprovados, segundo o recibo; prova anterior reutilizada, sem repetir a bateria nesta passagem |
| `tests-final.log` | Resultado bruto confere com o recibo: 16 testes / 172 asserções aprovados |
| Portabilidade do HTML compilado | Recibo regista 1 teste / 10 asserções aprovado; utiliza o directório do executor e `build` por defeito |
| Pint, PHPStan, tipos, lint focado e build QA | Aprovações do recibo reutilizadas, pois os ficheiros e dependências da candidata não mudaram |
| Verificação global de formatação | Executada novamente nesta passagem: 61 ficheiros; todos exteriores ao delta e com o mesmo blob Git da revisão anterior |
| Preservação | Nenhuma alteração de código, testes, configurações, permissões ou base de dados nesta passagem |
Os grupos de testes sobrepõem-se: não somar os totais nem tratar o número de testes como funcionalidades concluídas. A bateria canónica extensa interrompida na implementação anterior não é considerada aprovada. O teste antigo de divulgação vinculado a outra base QA conserva a sua guarda; essa base não foi alterada para repetir a prova.
### Cobertura de autorização e isolamento
| Comportamento | Evidência existente |
| --- | --- |
| Anónimo, email não verificado e conta sem atribuição não criam ofertas | `test_other_accounts_ungranted_unverified_and_revoked_actors_cannot_operate` |
| Outro promotor individual não lê, edita ou submete; a listagem não revela o registo | Mesmo teste, com pedidos HTTP aos três percursos e listagem vazia |
| Atribuição expirada deixa de autorizar acesso | Mesmo teste; a preparação específica usa expiração, não simula revogação administrativa |
| Revogação administrativa invalida a sessão; gestor legado não contorna o âmbito | `PortalProfileAccessTest::test_revocation_invalidates_existing_session_and_legacy_manager_cannot_bypass_scope`, na regressão recebida |
| Organização não pode ser trocada e perda da representação impede acesso/edição | `test_organization_representation_is_rechecked_and_other_organizations_are_not_assignable` |
| Categoria não atribuída e campos de proprietário/estado/contexto forjados são recusados | `test_contact_scope_and_malicious_fields_roll_back_without_parallel_records` |
| Contacto de outro titular é recusado; falha reverte a criação | Mesmo teste; sem oportunidade residual |
| Gravar contacto exige MFA; contacto eliminado impede a submissão | `test_existing_contacts_registration_still_requires_fresh_mfa_and_submission_rechecks_contact` |
| Edição concorrente não substitui dados; repetição da criação conserva a identidade | `test_private_creation_recovery_edit_conflict_and_idempotency_use_one_opportunity` |
| Submissão exige confirmação e versão actual; não cria edição ou notificação editorial | `test_submit_is_private_exact_version_confirmed_and_recoverable_without_editorial_actions` |
| Rotas nativas não permitem editar/divulgar/publicar a oferta; detalhe e pesquisa públicos não a mostram | `test_no_public_exposure_and_native_routes_cannot_edit_or_publish_offer` |
| Modelo impede publicar ou retirar a marca privada; filtro público exclui estado incoerente introduzido directamente em QA | Mesmo teste e `test_model_cannot_publish_or_remove_private_phase_marker` |
| Oportunidades anteriores não são convertidas pelo adaptador; âmbito nativo de divulgação mantém-se | `test_existing_opportunity_cannot_be_converted_through_offer_adapter` |
| HTML privado usa entradas compiladas reais e resposta `private, no-store` | `test_full_html_uses_real_compiled_private_page_entries` e teste de recuperação |
São provas com actores e dados sintéticos na base isolada `offers_phase1_20261114_test:55441`. Não comprovam autenticação real observada num navegador, configuração de entrega de email, revisão humana de conteúdo ou funcionamento de NDA. Esses resultados não são alegados.
### Conferência visual e incidente da ferramenta
Foi tentada a observação, preservando as sessões existentes. O inventário da ferramenta identifica Edge e Firefox como aplicações, mas devolve `browsers: []` e o erro exacto `Browsers: Error: The browser access host is unavailable`. Não foi possível identificar uma aba nem ler o seu URL pelo conector.
Foi também tentado o acesso nativo de Windows através de Computer Use. A enumeração de aplicações respondeu, mas a captura da janela Edge falhou: `encode latest capture frame failed: copy Direct3D surface to SoftwareBitmap failed: A instância de dispositivo GPU foi suspensa. Utilize GetDeviceRemovedReason para determinar a ação apropriada. (0x887A0005)`.
Não foram repetidos reinícios, alteradas configurações ou desactivadas verificações. Não se observou a candidata e não se atribui a causa ao Edge ou ao portal. A conferência visual continua pendente por impedimento da ferramenta. Quando o acesso estiver disponível, conferir apenas a candidata: lista/criação, recuperação, preço «A negociar»/montante, contacto, estado após submissão, erros preservando os campos e apresentação estreita, sem voltar a executar a bateria técnica aceite.
### Os 61 ficheiros de formatação
`outputs/offers-private-phase1-20261114/closure-confirmation.json` contém a lista completa, o blob Git de cada ficheiro e a comparação individual. Resultado: **61/61 fora do delta; 61/61 idênticos à revisão base `e8408c27`; intersecção com a entrega vazia**. Os ficheiros de dependências também não integram o delta.
Não foram corrigidos ou reformatados nesta instrução. A falha global mantém-se registada, sem ser atribuída à Fase 1 nem declarada dispensada. Uma regularização desses ficheiros seria uma alteração separada; não é incluída por conveniência neste plano.
## 2. Plano técnico proposto para a Fase 2 — aguarda autorização
### Resultado e fronteira
Permitir que uma oferta submetida entre nas filas do Dashboard Colaborativo existente, seja revista, devolvida ou rejeitada com fundamento, corrigida e reenviada, ou aprovada internamente na sua versão exacta. Conservar decisões, responsáveis, tarefas, notificações e histórico. **A Fase 2 proposta termina na aprovação privada; a publicação pública permanece bloqueada para Ofertas.** A aprovação não certifica o negócio, a titularidade, o financiamento ou uma validação jurídica que não tenha ocorrido.
Sem novo dashboard, cadastro, autenticação ou motor editorial. Sem alterar negócios existentes, `mne`, menu principal ou regras dos perfis completos de Oportunidades. Não inclui catálogo público, fotografias, NDA, comprovativos de propriedade, validade, destaque comercial ou SMS novo. As decisões desses ramos não bloqueiam a revisão dos cinco campos comuns em privado.
### Arquitectura e persistência
1. Manter `opportunities.id`, `owner_user_id`, `organization_id`, `management_version`, `management_draft`, a marca `workflow_context.private_offer_preparation` e o histórico da Fase 1. A marca permanece e o filtro público continua a excluí-la.
2. Acrescentar um adaptador delimitado ao percurso privado para fornecer validação comum, snapshot, hash e estado ao workflow nativo de Oportunidades. Reutilizar `OpportunityProfileWorkflow`, `PortalProfileAccess`, `OpportunityWorkflowTasks` e a integração de oportunidades em `PortalEditorialWorkflow`; não duplicar as transições num segundo motor.
3. Não encaminhar simplesmente o formulário reduzido para `OpportunityCanonicalContract::validateSubmission`: esse contrato exige os perfis completos, evidências e verificações especializadas. Não fabricar esses campos nem enfraquecer as validações dos restantes investimentos. O adaptador distingue explicitamente a preparação comercial comum da validação integral do projecto.
4. Separar estado editorial privado e disponibilidade pública. `working_state` pode representar a revisão; `status` mantém a barreira privada da oferta e `published_at` permanece nulo. O workflow existente actualmente escreve ambos: a implementação terá de delimitar esse comportamento às Ofertas, preservando-o nos restantes registos.
5. Evitar estados divergentes entre `private_offer_preparation.state` e `working_state`: definir uma projecção única de estado para apresentação, autorização e filas, com compatibilidade de leitura da Fase 1. Não converter todos os registos por uma escrita em lote nem criar tarefas ao abrir uma página. A vinculação inicial de uma submissão anterior deve ser explícita, idempotente e auditada, conservando autor, data e revisão originais.
6. Usar os registos existentes de `tasks`, `audit_events` e `portal_editorial_notifications`. Esta última já tem referência a oportunidade, versão e índice único por utilizador/oportunidade/versão/acção. Não se prevê uma nova tabela ou migração para o núcleo proposto. Se uma necessidade concreta de persistência não couber nos contratos existentes, apresentar o delta aditivo antes de o executar; não instalar estrutura por esta instrução.
### Estados e operações propostas
| Origem | Operação e responsável | Destino e garantia |
| --- | --- | --- |
| Rascunho | Promotor submete a versão conferida | Submetida; snapshot e tarefa de revisão vinculados à versão/ciclo |
| Submetida | Validador autorizado inicia/recebe revisão | Em revisão; responsável explícito, sem atribuição inventada |
| Submetida ou em revisão | Validador pede correcção, com fundamento e campos a rever | Correcções solicitadas; edição permitida apenas ao promotor autorizado |
| Submetida ou em revisão | Validador rejeita, com fundamento obrigatório | Rejeitada; decisão e versão conservadas |
| Correcções solicitadas ou rejeitada | Promotor escolhe «Corrigir e reenviar» | Nova preparação privada ligada à anterior; a rejeição não é apagada nem revertida em aprovação |
| Nova preparação corrigida | Promotor guarda e submete | Novo ciclo de revisão; sem duplicação de oportunidade, tarefa ou notificação por repetição do pedido |
| Em revisão | Validador independente aprova o snapshot actual | Aprovada internamente; nenhuma exposição pública |
| Aprovada internamente | Promotor prepara alteração | Nova revisão privada; aprovação anterior histórica, sem aplicação à nova versão |
| Qualquer estado de Ofertas nesta fase | Qualquer tentativa de publicar ou divulgar | Recusada no servidor, mesmo por Publicador ou através de rotas alternativas |
Reutilizar a regra nativa de fundamento de decisões (`reason`, 20–1000 caracteres), sem substituir fundamentação por um checkbox. Campos de correcção são referências aos dados da oferta, não exigências de um perfil especializado ainda por aprovar. A proposta de retoma após rejeição é própria das Ofertas; não alterar o recurso administrativo dos outros percursos.
### Autorizações e independência
| Actor existente | Âmbito proposto |
| --- | --- |
| Promotor | Criar/editar nos estados permitidos, submeter e preparar correcção, dentro das categorias e representação efectivas; nunca aprovar a sua oferta |
| Validador | Consultar/rever/devolver/rejeitar/aprovar dentro da atribuição actual; decisões com MFA recente e verificação de conflitos |
| Publicador | Não recebe aprovação por inferência e não publica Ofertas nesta fase; futura publicação continua operação e actor distintos da aprovação |
| Auditor | Consulta autorizada e histórico nos limites existentes; sem decisões operacionais |
| Gestor/Superutilizador | Conserva os controlos existentes; a condição administrativa não concede automaticamente acesso operacional, âmbito ou dispensa de independência |
Rever autorização no servidor em cada pedido, API, pré-visualização, tarefa e ligação de notificação; repetir dentro da transacção antes da decisão. Autor, contribuidores e representantes com conflito não aprovam a mesma oferta, mesmo acumulando papéis. Não estender a designação jurídica da legislação a Ofertas. Sem designação jurídica expressa e texto aprovado não se regista «validação jurídica» fictícia.
### Versões, auditoria e concorrência
Cada submissão congela um snapshot dos campos comuns, preço/modo/moeda, contacto referenciado, categoria, promotor e localização, com versão do contrato, revisão e hash determinístico. As referências de contacto e localização devem identificar também a versão/hash dos dados efectivamente conferidos; uma alteração posterior na fonte sinaliza reconferência, sem substituir silenciosamente a conclusão. Não criar outro cadastro de contactos ou territórios. Os recursos que futuramente entrem no âmbito terão identificador/versão/hash próprios; a aceitação deles não é antecipada nesta fase.
Decisões guardam actor, instante UTC, fundamento, transição, ciclo, versão e hash. Edição posterior exige novo ciclo e nova aprovação; a decisão anterior permanece consultável, associada ao snapshot anterior. Reutilizar snapshots cifrados da auditoria e restrições de leitura existentes; não copiar contactos ou documentos para registos públicos.
Usar `etag`, bloqueio da oportunidade e incremento de versão na mesma transacção. Decisão com versão desactualizada responde a conflito, sem sobrescrever o trabalho do promotor. Repetições da mesma operação não repetem decisões, tarefas ou notificações. Falha na operação reverte conjuntamente os seus efeitos de estado, tarefa e auditoria.
### Dashboard, tarefas e notificações
Integrar na secção de Oportunidades já acessível pelo Dashboard Colaborativo, com filtro para Ofertas e estados da revisão. Não criar outro item no menu principal. O controlador editorial actual lista artigos/legislação/documentos e liga Oportunidades separadamente; não assumir que acrescentar um tipo de conteúdo às tabelas editoriais resolve automaticamente esta integração.
As contagens devem considerar cada oportunidade uma vez: o contador actual já abrange rascunhos de Ofertas. Mostrar ofertas por rever sem duplicar o total entre uma nova vista filtrada e a vista geral. Tarefas sem responsável são visíveis na fila autorizada; não escolher um funcionário ou prazo por inferência. Reenvio cria novo ciclo e conserva tarefas/decisões encerradas.
Usar a caixa de notificações editorial existente, com ligações para o detalhe privado correcto e revalidação de acesso na listagem e no destino. Notificar autor e responsáveis efectivos sobre submissão, atribuição, correcção, rejeição, reenvio e aprovação, conforme o papel de cada destinatário; não enviar conteúdo confidencial a uma lista geral de gestores. O mecanismo actual de oportunidades cobre autor e responsáveis atribuídos: a escolha de coordenação inicial deve respeitar as designações existentes e o âmbito, deixando a fila por atribuir quando não houver destinatário elegível.
Para email, reutilizar o transporte e a fila existentes, após confirmação da transacção, com tentativa/erro rastreáveis e revalidação de destinatário. `NotifyInterestReceived` é de manifestações de interesse: reutiliza-se o padrão de transporte, não o seu texto ou evento como se fossem Ofertas. Não prometer entrega exactamente uma vez, leitura da mensagem ou SMS configurado. Em QA, manter mail fake/array e dados sintéticos; não enviar mensagens reais nesta preparação.
### Componentes existentes provavelmente afectados
| Componente | Alteração delimitada proposta |
| --- | --- |
| `app/Services/PrivateOfferPreparation.php` | Validação/snapshot comuns, compatibilidade da submissão F1 e preparação de correcção/reenvio |
| `app/Services/OpportunityProfileWorkflow.php` | Adaptar revisão, decisões, ciclos e persistência privada apenas quando a oportunidade for Oferta |
| `app/Services/PortalProfileAccess.php` | Estado único e operações/edição permitidas nos estados de correcção, conservando âmbito e conflito |
| `app/Services/OpportunityManagement.php` | Preservar versões e invalidação de aprovação da revisão alterada, sem converter registos anteriores |
| `app/Services/OpportunityWorkflowTasks.php` | Reutilizar vinculação idempotente por oportunidade/revisão/ciclo e tarefas anteriores |
| `app/Services/PortalEditorialWorkflow.php` | Filas/contagens, destinatários e ligações das notificações de Ofertas, sem duplicar o cadastro editorial |
| `app/Http/Controllers/PortalEditorialWorkflowController.php` e `OpportunityManagementController.php` | Vista filtrada e dados autorizados de revisão, reaproveitando a entrada de Oportunidades |
| `app/Http/Controllers/PrivateOfferPreparationController.php` e `PortalProfileController.php` | Pedidos privados e decisões encaminhados ao serviço autorizado; sem lógica de permissão apenas no cliente |
| `app/Http/Middleware/ProtectProfileOpportunity.php` | Permitir somente as operações F2 expressamente suportadas; manter recusados os restantes atalhos |
| `app/Models/Opportunity.php` e `app/Services/OpportunityPromotion.php` | Conservar exclusão pública, marca privada e recusa de divulgação; não remover as barreiras ao habilitar revisão |
| `resources/js/pages/offers/preparation.tsx`, `preparation-index.tsx` e `components/opportunity-profile-actions.tsx` | Estado, correcção, reenvio, fundamento e acções de cada papel, com campos preservados em conflito |
| `resources/js/pages/management/editorial-workflow.tsx` e `management/opportunities.tsx` | Reutilizar secção existente e filtros, sem reorganizar o menu |
| `routes/web.php` | Só as rotas privadas necessárias, com middleware de autenticação, perfis e resposta privada |
| Testes e os mesmos manual/matriz | Provas focadas, saldo de RF-MN-003 e distinção QA/instalado/pendente |
Os caminhos acima existem e foram inspeccionados. Não são uma autorização para modificar todos os ficheiros: usar apenas os que o delta demonstrar necessários. Novos auxiliares ou testes deverão ter nomes definidos na implementação, sem serem apresentados aqui como componentes já existentes.
### Ordem de implementação proposta e aceitação
1. **Estado e versão:** adaptar a submissão F1 sem perder os seus identificadores/datas; congelar snapshot, ciclo e permissões. Demonstrar que uma Oferta e uma oportunidade normal mantêm os respectivos contratos.
2. **Revisão privada:** integrar tarefas e fila existente; iniciar, devolver, rejeitar e aprovar com fundamento, MFA, âmbito e independência. Aprovação continua sem publicação.
3. **Correcção e reenvio:** demonstrar rejeição → nova revisão privada → guardar/recuperar → novo ciclo → aprovação, conservando rejeição, comentários, snapshots e tarefas antigos.
4. **Notificações:** emitir uma ocorrência por evento/destinatário na caixa existente; testar repetição, falha/rollback, permissão revogada e URL de destino. Email real não é condição para aprovar o transporte fake de QA nem é activado por ele.
5. **Aceitação:** testes HTTP de sucesso, erro e concorrência, autorização por organização/categoria/estado, perda de representação, auditor e acumulação de papéis, motivo ausente, hash desactualizado, reenvio sem duplicações e nenhuma exposição por catálogo/API/rotas de divulgação. Regressão focada dos percursos nativos afectados, tipos, análise PHP, lint e compilação conforme o executor existente. Conferência visual quando a ferramenta permitir.
Percurso de aceitação principal: promotor prepara/submete → validador consulta na fila → rejeita com fundamento → promotor corrige e reenvia a nova revisão → validador independente aprova → autor recupera decisão e histórico → tentativa de publicação permanece recusada.
### Decisões para a autorização da Fase 2
Solicitar aprovação do recorte acima, incluindo: aprovação exclusivamente interna; correcção/reenvio após rejeição como nova revisão; estado privado compatível com os dados F1; integração pela área existente de Oportunidades no Dashboard; notificações internas e email apenas simulado em QA. Não são necessárias novas contas ou permissões por defeito.
Uma designação operacional ausente bloqueia apenas a atribuição a essa pessoa; não justifica inventar destinatários nem suspender as restantes filas autorizadas. NDA, comprovativos, textos jurídicos, formatos/tamanhos das seis fotografias e validade continuam nas fases dependentes. A regra de separação aprovação/publicação mantém-se; publicação, instalação e qualquer alteração operacional exigem autorização expressa própria.
### Preservação e reversão da futura preparação QA
Preparar o delta F2 num commit/pacote próprio, após a sua autorização, conservando a referência F1. A revisão técnica deve mostrar alterações apenas nos componentes afectados e revalidar as barreiras de exposição. Uma reversão de código ou desactivação do percurso F2 deve deixar as ofertas e decisões existentes legíveis em privado e bloqueadas para publicação; não eliminar eventos, tarefas, notificações ou snapshots para regressar à versão anterior. Não executar rollback destrutivo de estrutura com histórico.
Qualquer futura entrega em homologação terá a sua autorização, conferência do delta e processo Git de backup/restauro e recuperação. Este plano não executa nem autoriza essas operações.
**Estado final desta instrução:** relatório e plano entregues para decisão. Nenhuma implementação F2, migração, instalação, publicação, envio de notificações ou modificação da implementação F1.