commit 30/9
tests / ci (push) Canceled after 0s

This commit is contained in:
2026-09-30 09:05:43 +02:00
parent 67dad461d9
commit 3dc7c5c649
75 changed files with 4123 additions and 107 deletions
@@ -0,0 +1,71 @@
# Script skills_edu — execução local de 30/09/2026
Executado o anexo de tabelas recebido em 30/09/2026, adaptado aos mestres existentes como exigido no próprio documento. O prompt funcional anterior foi cancelado e não foi retomado. Esta entrega é de estrutura relacional; não activa formulários, matching, publicação, acesso a documentos ou integrações.
## Resultado
- PostgreSQL **17.11**, pgcrypto/PostGIS já geridos pelas migrations existentes; nenhuma extensão reinstalada.
- Seis migrations Laravel registadas: `2026_09_30_100100_skills_education_1` até `2026_09_30_100600_skills_education_6`.
- **24 tabelas, 83 FK e 111 índices** no schema `skills_edu`. Todas as FK têm cobertura B-tree completa; índices parciais adicionais mantidos.
- Novas tabelas vazias. Nenhuma pessoa, competência, curso, procura ou oportunidade inventada/importada.
- Backup com restauro de **210 tabelas**: `storage/app/private/skills-education-backup-20260930-034646/`. A reconciliação posterior não encontrou diferenças, descontando os seis registos novos na tabela Laravel `migrations`.
- Evidência e checksums SQL: `applied-verification.json` no mesmo directório privado.
## Adaptação aos mestres
As seguintes tabelas são extensões com **PK também FK obrigatória** para o mestre. Não geram um segundo identificador da entidade. Nomes/códigos e outros campos indicados continuam a ser lidos no mestre; não foram copiados.
| Extensão em skills_edu | Mestre em public | Informação mantida no mestre |
|---|---|---|
| skill_details | skills | UUID, código e nome PT |
| occupation_details | occupations | UUID, código e nome PT |
| program_details | courses | UUID, código, título PT, instituição/provider e estado |
| human_resource_need_details | labour_demands | UUID, projecto, localização, ocupação, quantidade, qualificação, período, fonte e estado |
| educational_opportunity_details | education_investment_proposals | UUID da proposta; título, descrição, valor e moeda continuam na oportunidade ligada por opportunity_id |
| qualification_document_details | documents | UUID, nome do ficheiro, MIME, storage_key e SHA-256 |
| consent_details | consents | UUID, finalidade, estado, concessão e retirada |
Os estados destas entidades seguem o mestre existente: por exemplo, consentimento retirado usa `withdrawn`, não uma segunda coluna `revoked`. A unicidade de códigos de competências/ocupações/cursos conserva as regras actuais dos mestres, incluindo soft delete. A versão do grupo documental na extensão refere-se ao comprovativo; `documents.version_no` continua a ser a versão do registo mestre.
O consentimento deve ter `public.consents.subject_ref` igual ao UUID do perfil. O trigger da extensão valida esta ligação e que a expiração é posterior à concessão. Não converte consentimento de outra finalidade nem muda o mestre.
| Referência do anexo | Mestre e tipo confirmados |
|---|---|
| user_master_id e campos de actores *_by/*_user_id | public.users.id — bigint |
| sector_master_id | public.sectors.id — bigint |
| company_master_id / organisation_master_id | public.organizations.id — UUID |
| institution_master_id | public.training_providers.id — UUID |
| program_master_id | public.courses.id — UUID |
| project_master_id | public.projects.id — UUID |
| cluster_master_id | public.economic_clusters.id — UUID |
| corridor_master_id | public.development_corridors.id — UUID |
| zee_master_id / zfi_master_id | public.investment_zones.id — UUID |
| location_master_id | public.geo_locations.id — UUID |
As FK novas para mestres usam DELETE RESTRICT. Relações internas mantêm CASCADE/SET NULL/RESTRICT definidos no anexo. Não há conversão bigint–UUID. A escolha ZEE versus ZFI exige ainda validação de categoria na futura aplicação: a FK confirma a existência da zona, não a sua categoria.
## Migrations e segurança
SQL adaptado em `database/sql/skills-education/01.sql` a `06.sql`; execução por `App\Support\SkillsEducationMigration`. Cada migration é transaccional no PostgreSQL, adquire bloqueio consultivo, valida versão/extensões/tipos dos mestres e exige o bloco anterior. A primeira recusa um schema preexistente não registado. A repetição via Artisan terminou em `Nothing to migrate`; não se deve repetir o SQL bruto por fora do ledger.
O DOWN verifica tabelas vazias e remove somente as tabelas/funções/schema novos em ordem inversa, com RESTRICT. Recusa perda de dados e não apaga mestres nem extensões PostgreSQL. O rollback integral e a reaplicação foram executados na base isolada. Em produção/local com dados, preparar arquivo e reconciliação antes de qualquer reversão.
O anexo não define novas colunas geométricas; a localização aponta para o mestre geográfico existente. Não foram criadas coordenadas nem alterada a sua visibilidade. Perfis e necessidades conservam os valores privados por defeito.
A auditoria recusa UPDATE, DELETE e TRUNCATE, incluindo comandos sem linhas afectadas. Isto complementa o REVOKE do anexo; um administrador da BD continua capaz de alterar DDL. Cifragem de contactos, scanner, autorização de downloads e verificação humana são obrigações da futura aplicação, não capacidades fornecidas pelo DDL.
## Verificação
- Suite completa: **183 testes, 1871 verificações aprovadas**.
- PHPStan e Pint aprovados.
- Testes específicos: tipos/FK, extensão sem mestre recusada, utilizador inexistente, valores privados, intervalo inválido, auditoria imutável, cobertura de índices, rollback vazio, reaplicação e recusa de rollback com dados.
- O teste de rollback das zonas passou a reverter primeiro as extensões dependentes. O reset PHPUnit do schema fica restrito às bases conhecidas de teste em localhost:55441; não ocorre no ambiente local operacional.
- Não houve alterações de API ou frontend, nem commit Git.
Para inspeccionar o resultado sem modificar dados:
```powershell
php tools/verify-skills-education.php
```
Para novos ambientes, executar a cadeia Laravel normal após as migrations dos mestres: `php artisan migrate`. `tools/prepare-skills-education.py` documenta a transformação inicial do anexo; não deve ser usado para reescrever migrations já aplicadas. Correcções futuras exigem novas migrations.
@@ -0,0 +1,71 @@
# Matriz de competências — diagnóstico do novo prompt
> Actualização de 30/09/2026: o anexo posterior `ce3b9157-6463-4c3f-af50-6d53cba78a23` determina assumir todas as fases aprovadas e solicita reconciliação transversal do modelo de dados. A pendência de autorização genérica abaixo foi ultrapassada. Ver [reconciliação do capítulo 22](../reference-schema-reconciliation.md) para o âmbito e estado técnico actuais; este diagnóstico é histórico, não um pedido renovado de autorização.
Data: 30/09/2026. Fonte: anexo `bf1d310a-d3a8-48fc-953f-0648622a6c85/Pasted text.txt`. Este pedido retoma o âmbito funcional com uma nova especificação; não retoma automaticamente o prompt anteriormente cancelado.
A autorização do utilizador para executar está registada na conversa. Não se solicita novamente autorização genérica. O novo documento exige diagnóstico antes de codificar e determina: «Não iniciar desenvolvimento se não houver mapeamento aos RF aplicáveis, proprietário funcional, base legal/finalidade de tratamento, matriz de permissões, critérios de aceitação, plano de testes, estratégia de migração/rollback, definição de evidências, integração com o CRM, tratamento de consentimento e decisão Go/No-Go». Este diagnóstico não substitui esses elementos por aprovações inventadas.
## Estado observado no código
- `routes/web.php`: seis rotas internas Education, incluindo cadastro, procura/capacidade, revisão, análise e preparação de oportunidade. Não constituem a API REST versionada requerida.
- `EducationController`: pesquisa por texto, estado e localização; catálogos de competências, ocupações, instituições e cursos; não fornece o conjunto de filtros contextuais do novo pedido.
- `EducationEnabled` e `config/education.php`: uma flag global; não existem flags independentes para os novos fluxos.
- `EducationGapCalculator`: conserva desconhecido, expirado e incomparável; não converte vagas formativas em trabalhadores disponíveis.
- `education/index.tsx` e `education/analysis.tsx`: gestão interna; não são painéis individuais, institucionais e de investidores.
- As seis migrations `2026_09_30_100100`–`100600` fornecem as extensões `skills_edu`, descritas em [skills-education-schema.md](skills-education-schema.md). A existência das tabelas não fornece serviços de candidatura, documentos, consentimento ou correspondência.
- `labour_demands` exige campos preenchidos e admite apenas submitted/reviewed/rejected. Não suporta rascunho incompleto nem arquivo. Uma implementação de CA-01 exige evolução explícita deste contrato; não se deve criar outra tabela concorrente de procura.
- A integração `app/Integrations/SuiteCrm` existe. A sua existência não prova contrato nem autorização para transmitir candidatos.
- Os testes `EducationTest`, `EducationGapCalculatorTest` e `SkillsEducationSchemaTest` cobrem partes existentes. Não comprovam os novos CT-01–CT-16. Nenhuma nova execução de testes funcionais foi realizada neste diagnóstico documental.
## Conservar–Melhorar–Substituir–Descontinuar
| Decisão | Componentes | Justificação |
|---|---|---|
| Conservar | users, organizations, sectors, projects, economic_clusters, development_corridors, investment_zones, geo_locations | Identidades mestre; não converter bigint em UUID nem duplicar taxonomias. |
| Conservar | skills, occupations, courses, training_providers, consents, documents e extensões skills_edu | Reutilizar PK/FK e estados do mestre; novos dados por migrations aditivas. |
| Conservar | Fluxos internos, revisão independente, cálculo de lacunas e oportunidade canónica | Compatibilidade; capacidade de formação não equivale a oferta laboral. |
| Melhorar | labour_demands e serviços de procura | Rascunhos, arquivo, várias competências, contexto, versões e autorização por organização. |
| Melhorar | Interfaces e pesquisa | Painéis por actor, filtros combinados, PT/EN, acessibilidade e precisão territorial autorizada. |
| Melhorar | Consentimento, auditoria e documentos | Finalidades separadas, revogação, scanner real, quarentena e autorização em cada leitura. |
| Substituir | Nenhum componente nesta fase | Não há evidência que justifique reconstrução integral. |
| Descontinuar | Nenhum contrato nesta fase | Preservar consumidores e dados existentes. |
## Plano por ficheiro — proposto, ainda não executado
| Ordem | Caminhos | Alteração e verificação necessária |
|---|---|---|
| 1 | `docs/investmoz/education/requirements.csv`, nova matriz funcional | Relacionar RF-CAN, CA/CT, RF-EDU/CRM/EII/GEO e A.31 com entidades, APIs, ecrãs, evidências e responsáveis. Não atribuir IDs normativos sem conferir a fonte. |
| 2 | `config/education.php`, `app/Http/Middleware/EducationEnabled.php` | Flags independentes; desligar sem apagar dados. Testar acesso negado e contratos actuais. |
| 3 | Nova migration Laravel posterior a `100600` | Evoluir rascunho/arquivo, concorrência e escopos conforme desenho aprovado; não reescrever SQL aplicado. Testar ida/volta em base isolada e recusa de perda de dados. |
| 4 | Novos serviços em `app/Services/Education/`, requests e policies | Escritas transaccionais, segregação, organização vigente, validação contextual e auditoria sem conteúdo pessoal. |
| 5 | Novos controllers e rotas versionadas | Perfis, necessidades, instituições, consentimentos e documentos; paginação, limites, conflitos de versão e respostas sem enumeração. Manter rotas internas. |
| 6 | `resources/js/pages/education/` e componentes partilhados | Formulários por actor, rascunho/retoma, confirmação, revisão e estados; PT/EN, teclado e móvel. |
| 7 | Serviços de pesquisa/correspondência e jobs | Autorização antes de resultados; condições aprovadas antes da pontuação; versão e explicação; revogação verificada também na execução de jobs. Sem encaminhamento automático. |
| 8 | `app/Integrations/SuiteCrm/`, contratos e reconciliação | Só campos e finalidades aprovados; deduplicação, idempotência e recuperação; alternativa manual enquanto contrato externo pendente. |
| 9 | `tests/Feature/`, `tests/Unit/`, ferramentas de navegador e contratos | CT-01–CT-16: fluxos, fronteiras, acesso, documentos maliciosos, revogação, concorrência, PT/EN, recuperação e carga com linha de base real. |
## Regras de desenho propostas para revisão
Proprietário consulta e edita o próprio rascunho; operador institucional precisa de vínculo vigente à instituição verificada e autorização do candidato; revisor não pode validar a própria submissão; investidor só pesquisa perfis validados, consentidos e no âmbito permitido. Contactos e documentos exigem autorização própria, não herdada da pesquisa. Gestão global não comprova mandato institucional.
Separar consentimentos de pesquisa, correspondência, partilha de contactos e transmissão ao CRM. Não reutilizar consentimento comercial. Revogação deve impedir a próxima leitura e execução de tarefa dependente; tempos de propagação externa, retenção e backups precisam de política aprovada. Esta proposta não declara base legal nem prazo de retenção.
Sem mudança da ligação principal de dados nesta etapa. Antes de futuras migrations: backup/restauro, contagens e checksums dos mestres, teste isolado, aplicação aditiva e reconciliação. Reversão operacional por flags, preservando registos; reversão estrutural apenas sem perda de dados. Sem deploy nem commit nesta entrega.
## Gate de diagnóstico
**Não concluído. Não iniciar desenvolvimento funcional sob este prompt até resolver os bloqueadores materiais expressamente definidos nele.**
| Elemento | Evidência disponível / falta concreta |
|---|---|
| Autorização para executar | Recebida; não precisa de repetição. |
| Rastreabilidade | Há `requirements.csv`, `requirements-audit.csv` e especificação anterior. Há referências a A.31, mas não foi identificada nesta pesquisa a secção normativa integral com versão e correspondência ao novo anexo. |
| Âmbito | O novo pedido inclui indivíduos, instituições e correspondência; a decisão antiga D-EDU-06 excluía dados individuais. Actualizar formalmente o registo de âmbito com o novo pedido, sem tratar a autorização de desenvolvimento como consentimento dos candidatos. |
| Responsabilidade e tratamento | Faltam identificação do responsável funcional e política aplicável de finalidade, retenção, menores e destinatários. Papéis genéricos não são designação de pessoas/entidades. |
| Correspondência | Faltam regras, pesos e conjunto de referência aprovados; não adoptar exemplos fictícios como dados operacionais. |
| Desempenho | Falta linha de base da Fase Zero; não criar limites arbitrários para declarar CT-14 aprovado. |
| Integração | Falta contrato específico de candidatos/consentimento com CRM e confirmação da aplicabilidade de AsyncAPI. |
| Aceite | UAT com quatro perfis e decisões dos responsáveis não foram realizados; nenhuma aprovação fabricada. |
Próxima entrada necessária: localização da versão normativa de A.31 e do pacote de decisões da Fase Zero, incluindo os responsáveis e políticas acima. Se esses documentos ainda não existirem, é necessário resolver a divergência entre a ordem de implementar e a proibição explícita de iniciar desenvolvimento contida neste anexo.
@@ -0,0 +1,23 @@
# Reexecução do prompt — anexos D/E — 29/09/2026
1. **Fase e estado:** correcção incremental dos comandos de validação e do bloqueio de leitura canónica, aplicada localmente. O módulo completo e os anexos D/E continuam parcialmente implementados.
2. **Diagnóstico:** os nomes históricos são `001_mne_schema.sql` e `002_mne_existing_territories.sql`, não os nomes ilustrados no novo anexo. Os SHA-256 de 001–004 foram comparados com os SQL conservados nas quatro evidências de aplicação e coincidem. Não foram renomeados, divididos ou reescritos ficheiros já aplicados.
3. **Plano aplicado:** adicionar simulação transaccional ao backfill; dar código de saída não-zero à reconciliação bloqueada; criar 005 para recusar activação/rollout canónico; testar com papel sem superuser.
4. **Ficheiros:** criados `005_mne_compatibility_guards.sql` e respectivo DOWN; alterados `BackfillMneCompatibility`, `ReconcileMneCompatibility` e `tools/test-mne-compatibility.php`. Documentação actualizada. Nenhum ficheiro removido; nenhum commit Git criado.
5. **Migrations/reversão:** 005 acrescenta somente uma constraint aos controlos; exige 004 e recusa um controlo canónico já activo em vez de o desligar silenciosamente. Usa transacção e advisory lock próprio. DOWN remove apenas essa constraint. UP repetido, DOWN e reaplicação passaram em QA. Depois de 005, não reaplicar 004 como se fosse uma migration pendente: a sua verificação de estrutura detectará a constraint adicional.
6. **Impacto:** nenhuma mudança de API, interface, integração externa ou autoridade de escrita. `public` permanece mestre. Nenhuma pessoa ou negócio canónico criado.
7. **Testes:** teste SQL de compatibilidade aprovado, incluindo dry-run sem persistência e activação canónica recusada; 11 testes MNE / 91 verificações aprovados. Papel sintético NOLOGIN/NOSUPERUSER conseguiu inserir auditoria e teve 12 mutações de históricos e duas operações DDL recusadas. Papel e dados de teste revertidos integralmente. Não equivale à mudança das credenciais runtime reais.
8. **Evidência:** backup integral com restauro de 210 tabelas em `storage/app/private/mne-integrity-backup-20260929-194732/`; aplicação em `storage/app/private/mne-guards-20260929-194806/`; contagens de 97 tabelas anteriores `mne`/`investmoz` preservadas. Execução local de dry-run: 1 identidade, 12 sectores, 0 negócios, rollback confirmado. Reconciliação com `--fail-on-difference` devolveu 1, conforme esperado.
9. **Lacunas:** continuam a faltar convergência de todas as colunas do anexo D, histórico persistente de migrations SQL, instalador da cadeia com ordem/idempotência completas, testes de concorrência em duas sessões, navegador, SLA/notificações e deduplicação. O runtime real ainda é superuser, a identidade permanece pendente e não existem negócios reais reconciliados. Não se considera a cadeia 001–005 integralmente certificada.
10. **Rastreabilidade:** D.3/E.1 dry-run → comando de backfill → teste de rollback; E.1 fail-on-difference → comando de reconciliação → saída local 1; D.1 canonical_read desactivado → 005 → dois testes negativos; E.2 privilégios → papel isolado → 14 recusas. Separação efectiva dos papéis de operação permanece pendente.
11. **Decisão:** Go condicionado para este incremento técnico local; **No-Go** para leitura/escrita canónica, publicação ou produção. Os controlos shadow/canonical_read locais continuam desligados.
12. **Próxima fase proposta, não iniciada neste incremento:** completar o registo e o executor da cadeia SQL preservando os checksums históricos, seguido da separação efectiva de owner/migrator/runtime e dos testes de concorrência. As aprovações de desenvolvimento existentes não são consideradas prova de identidade ou reconciliação dos dados.
Comandos disponíveis:
```powershell
php artisan mne:backfill-compatibility --batch=100 --dry-run
php artisan mne:reconcile-compatibility --fail-on-difference
```
O dry-run executa as mesmas validações numa transacção exterior e reverte tudo; pode adquirir locks durante a simulação. Sem `--dry-run`, continua a gravar apenas vínculos/checkpoints/rejeições. `--fail-on-difference` é estrito: enquanto os gates de activação estiverem abertos, devolve falha mesmo com contagens e checksums iguais.
@@ -0,0 +1,25 @@
# Integridade do schema canónico — 29/09/2026
## Diagnóstico e âmbito entregue
A aplicação continua a usar `public.existing_businesses` e os serviços de intake/revisão privados. As 70 tabelas `mne` são um modelo físico separado, ainda sem ligação aos fluxos da aplicação. A presença das tabelas não significa que o mercado completo esteja implementado.
Foi aplicado `migrations/003_mne_integrity.sql` ao PostgreSQL 17 local. Acrescenta 81 índices onde nenhuma chave de índice B-tree completo cobre o prefixo da chave estrangeira. Índices parciais não asseguram cobertura de todos os registos. Não cria tabelas, copia negócios ou altera dados.
Os históricos `audit_events`, `business_status_history`, `document_versions` e `negotiation_events` passam a recusar UPDATE, DELETE e TRUNCATE, incluindo operações sem linhas afectadas. Novas evidências continuam a ser inseridas. Correcções devem ser novos eventos. Os triggers não impedem um administrador da base de dados de os desactivar; a separação de papéis operacionais permanece necessária.
## Validação e reversibilidade
- Teste reproduzível: `tools/test-mne-integrity.php`, exclusivamente em `portal_services_test`, porta 55441, `APP_ENV=testing`.
- Criação isolada de 001 e 002; aplicação dupla de 003 sem índices duplicados; todas as FK com cobertura; 12 mutações recusadas; DOWN seguido de reaplicação; rollback integral da transacção de teste.
- Backup completo e restauro isolado com contagens reconciliadas de 204 tabelas: `storage/app/private/mne-integrity-backup-20260929-185518/`.
- Aplicação local com contagens preservadas de 91 tabelas `mne`/`investmoz`: `storage/app/private/mne-integrity-20260929-185534/`.
- Reversão deste incremento: `migrations/003_mne_integrity_down.sql`. Remove apenas os triggers, a função e os índices identificados como pertencentes a 003. Não executar o DOWN integral 001 para reverter este incremento.
Nenhuma mudança de UI, API ou eventos externos; o OpenAPI mantém o contrato privado existente. Não foi executada a suite completa da aplicação neste incremento SQL. Nenhum commit Git foi criado.
## Trabalho ainda necessário
A fase de modelo e integração continua incompleta. Antes de adoptar o schema `mne` nos serviços é necessário resolver a identidade dos utilizadores (`public.users.id` bigint versus `mne.parties.user_id` UUID), o vínculo dos sectores e a migração reconciliada dos negócios existentes, sem criar cópias concorrentes. Publicação com duas aprovações, documentos/scanner, catálogo anonimizado, CRM, sala segura, negociação e facturação continuam pendentes conforme a matriz de implementação. Este incremento não constitui aprovação de produção nem entrega integral do prompt.
Incremento seguinte iniciado e aplicado: [ponte de compatibilidade 004](compatibility.md), com vínculos, backfill e repositório legado. A integração canónica completa continua pendente; consultar os gates e resultados nesse documento.
@@ -0,0 +1,56 @@
# Ponte de compatibilidade — 29/09/2026
Actualização: [reexecução dos anexos D/E](annex-de-execution.md), com migration 005 aplicada, dry-run, saída estrita de reconciliação e testes de privilégios numa conta isolada não-superuser. As limitações de activação abaixo mantêm-se.
## Estado da entrega
Incremento aplicado e testado localmente. **O anexo D ainda não está integralmente concluído.** `public` continua como autoridade de leitura e escrita dos formulários. Não há escrita dupla, migração de negócios, publicação nem activação de leitura canónica.
## Inventário confirmado
`public.users.id` e `public.sectors.id` são bigint; `public.existing_businesses.id`, `mne.parties.id` e `mne.existing_businesses.id` são UUID. O formulário privado não possui campo sectorial. A associação territorial de `mne.locations` continua a usar os identificadores inteiros de `investmoz`, sem nova taxonomia.
`004_mne_compatibility.sql` cria seis tabelas: identity_links, sector_links, business_links, compatibility_checkpoints, compatibility_rejections e compatibility_controls. Todas as novas FK têm cobertura por índices completos das constraints UNIQUE. O script verifica pré-condições, usa bloqueio consultivo, recusa tabelas de ponte com definição divergente e pode ser reaplicado. Não altera os ficheiros 001–003.
A coluna UUID `mne.parties.user_id` é mantida para reversibilidade, mas a nova constraint exige NULL. Contas autenticadas passam a ser resolvidas exclusivamente através de identity_links. A migration recusa dados antigos nesse campo em vez de tentar converter bigint em UUID. Pessoas podem existir sem conta; IdentityResolver só devolve uma ligação activa, vigente, a uma pessoa não arquivada. O resolver não autentica nem atribui permissões.
## Aplicação e backfill
BusinessRepository, LegacyBusinessRepository e CompatibleBusinessRepository estão ligados aos pontos de consulta dos controladores e dos quatro serviços privados. Políticas, bloqueios de escrita, versões e payloads HTTP existentes são preservados. Criação e gravação continuam nos modelos legados.
MneBusinessRepository é, por enquanto, um adaptador **apenas para comparação de cinco campos**: código, modalidade, estado, classificação e versão. Não implementa ainda a hidratação completa de um negócio nem substitui a autorização legada. SectorResolver v1 devolve UUIDs estáveis de ligação à taxonomia existente; não copia nomes ou cria sectores canónicos concorrentes.
```powershell
php artisan mne:backfill-compatibility --batch=100
php artisan mne:reconcile-compatibility
```
Usar um PHP com extensão pgsql. O backfill faz transacção por lote, bloqueio consultivo, IDs determinísticos, checksums e checkpoint do último lote não vazio. Cada nova execução percorre novamente a origem, para detectar também alterações anteriores ao checkpoint. Dados modificados invalidam a reconciliação. Vínculos sem destino canónico ficam pendentes, com código, causa e responsável `data-steward` na fila de excepções. A resolução não é aceite automaticamente. O backfill não cria pessoas ou negócios canónicos.
Resultado local: **1 vínculo de identidade pendente, 12 vínculos sectoriais activos, 0 negócios**, nenhuma chave em falta nem checksum divergente na execução. Há uma excepção de identidade não resolvida. A inexistência de negócios não conta como validação de uma migração real.
## Comparação sombra e reversão operacional
A comparação só é agendada após autorização de consulta da ficha. Requer `INVESTMOZ_MNE_COMPATIBILITY_SHADOW=true` e o controlo `shadow` em compatibility_controls activado com referência de evidência e percentagem. Ambos estão desligados na instalação local. O consumidor é `php artisan queue:work database --queue=mne-shadow`; filas síncronas são recusadas. O evento contém apenas resultado e latência, sem payload, identificador ou mensagem de excepção. Desligar o controlo interrompe novos agendamentos; trabalhos já enfileirados continuam somente de leitura.
Comparação parcial igual não abre o gate de leitura canónica. `canonical_read` está reservado, sem adaptador de resposta completo habilitado. Alterar a flag não transfere leitura ou escrita. Não se apresentam métricas de fallback canónico como se essa capacidade já existisse.
O DOWN 004 recusa qualquer vínculo, checkpoint, rejeição, controlo activo ou pessoa dependente. Esta é a situação local após backfill: para reverter a estrutura, é necessário arquivar/reconciliar primeiro essas dependências. Para continuar a operar pelo legado não é necessário executar DOWN. O script não remove dados de negócios nem históricos.
## Evidência
- Backup/restauro verificado de 204 tabelas antes de 004: `storage/app/private/mne-integrity-backup-20260929-191334/`.
- Aplicação: `storage/app/private/mne-compatibility-20260929-191400/`; contagens de 91 tabelas anteriores preservadas; schema mne passa de 70 para 76 tabelas.
- `tools/test-mne-compatibility.php`: 001–004 em base isolada, repetição, DOWN/reaplicação, backfill repetido, negócio sintético, acesso proprietário/terceiro, unicidade, FK, identidade revogada, sector desconhecido/alterado e recusa de estrutura divergente. Transacção integralmente revertida.
- Suite completa: 178 testes / 1820 verificações aprovados antes do teste adicional de isolamento da sombra. Esse teste adicional passou com 5 verificações.
- PHPStan aprovado. Contrato OpenAPI privado preservado. Sem alterações de interface; navegador não validado neste incremento.
## Gates ainda não satisfeitos
1. Correspondência canónica da identidade pendente e resolução formal das excepções.
2. Campo/relação sectorial nos negócios e reconciliação real de proprietários, estados e conteúdo completo.
3. Adaptador de leitura canónica completo, comparação integral, rollout e fallback testados.
4. Separação owner/migrator/runtime e testes de privilégios: a ligação local actual é superuser. Os triggers existem, mas esta conta pode desactivá-los. Nenhuma garantia de privilégio mínimo é declarada.
5. Concorrência com duas sessões, jornadas de navegador nos dois modos, notificações/SLA e deduplicação ponta a ponta.
Permanecem bloqueadas a transferência de escrita, a publicação e a produção. Não foi criado commit Git.
@@ -0,0 +1,187 @@
{
"proponent_type": {
"step": 1,
"pt": "Tipo de proponente",
"en": "Applicant type",
"type": "select",
"max": 30,
"options": {
"person": "Pessoa \/ Person",
"organization": "Organização \/ Organization"
}
},
"proponent_name": {
"step": 1,
"pt": "Nome ou denominação",
"en": "Name",
"type": "text",
"max": 200
},
"tax_identifier": {
"step": 1,
"pt": "NUIT ou identificador",
"en": "Tax identifier",
"type": "text",
"max": 50
},
"contact_email": {
"step": 1,
"pt": "Email de contacto",
"en": "Contact email",
"type": "email",
"max": 254
},
"contact_phone": {
"step": 1,
"pt": "Telefone",
"en": "Phone",
"type": "tel",
"max": 40
},
"country": {
"step": 1,
"pt": "País (código de duas letras)",
"en": "Country (two-letter code)",
"type": "text",
"max": 2
},
"acting_capacity": {
"step": 2,
"pt": "Qualidade em que actua",
"en": "Acting capacity",
"type": "select",
"max": 30,
"options": {
"holder": "Titular \/ Holder",
"representative": "Representante \/ Representative"
}
},
"declared_holder": {
"step": 2,
"pt": "Titular declarado",
"en": "Declared holder",
"type": "text",
"max": 200
},
"mandate_reference": {
"step": 2,
"pt": "Referência do mandato",
"en": "Mandate reference",
"type": "text",
"max": 500
},
"mandate_valid_until": {
"step": 2,
"pt": "Validade do mandato",
"en": "Mandate expiry",
"type": "date",
"max": 10
},
"legitimacy_declaration": {
"step": 2,
"pt": "Declaro legitimidade para esta submissão",
"en": "I declare authority to submit",
"type": "select",
"max": 3,
"options": {
"yes": "Sim \/ Yes",
"no": "Não \/ No"
}
},
"title": {
"step": 3,
"pt": "Título proposto",
"en": "Proposed title",
"type": "text",
"max": 200
},
"description": {
"step": 3,
"pt": "Descrição reservada",
"en": "Private description",
"type": "textarea",
"max": 10000
},
"declared_reason": {
"step": 3,
"pt": "Motivo e objectivo",
"en": "Reason and objective",
"type": "textarea",
"max": 2000
},
"valid_until": {
"step": 3,
"pt": "Validade declarada",
"en": "Declared expiry",
"type": "date",
"max": 10
},
"asking_value": {
"step": 4,
"pt": "Valor pedido",
"en": "Asking value",
"type": "decimal",
"max": 24
},
"currency": {
"step": 4,
"pt": "Moeda (código de três letras)",
"en": "Currency (three-letter code)",
"type": "text",
"max": 3
},
"value_nature": {
"step": 4,
"pt": "Natureza do valor",
"en": "Value basis",
"type": "select",
"max": 30,
"options": {
"asking": "Pedido \/ Asking",
"indicative": "Indicativo \/ Indicative",
"appraised": "Avaliado, sujeito a prova \/ Appraised, evidence required"
}
},
"equity_percentage": {
"step": 4,
"pt": "Participação disponível (%)",
"en": "Available equity (%)",
"type": "decimal",
"max": 8
},
"funds_use": {
"step": 4,
"pt": "Utilização dos fundos",
"en": "Use of funds",
"type": "textarea",
"max": 2000
},
"location_notes": {
"step": 5,
"pt": "Localização declarada (reservada)",
"en": "Declared location (private)",
"type": "textarea",
"max": 2000
},
"asset_notes": {
"step": 6,
"pt": "Resumo dos activos e direitos declarados",
"en": "Declared assets and rights summary",
"type": "textarea",
"max": 2000
},
"source_reference": {
"step": 7,
"pt": "Referência da fonte",
"en": "Source reference",
"type": "textarea",
"max": 2000
},
"document_reference": {
"step": 7,
"pt": "Referências documentais (não são ficheiros validados)",
"en": "Document references (not validated files)",
"type": "textarea",
"max": 2000
}
}
@@ -1,5 +1,13 @@
# Estado actual — Mercado de Negócios Existentes
29/09/2026 — [Formulário privado por oito etapas](wizard-implementation.md) disponível com autosave, retoma, sector e confirmação no servidor. Testado por API e navegador; os anexos A–C completos continuam pendentes.
29/09/2026 — [Anexos D/E: incremento 005](annex-de-execution.md) aplicado. Simulação do backfill, reconciliação com saída de falha e bloqueio SQL de activação canónica testados; a entrega integral continua pendente.
29/09/2026 — [Ponte de compatibilidade 004](compatibility.md) aplicada: seis tabelas de vínculo/controlo, backfill, resolvers e interface de repositório nos serviços privados. Legado permanece autoridade; identidade pendente, leitura canónica completa e privilégios mínimos ainda impedem a activação.
29/09/2026 — [Integridade do schema canónico](canonical-integrity.md): aplicado incremento SQL com 81 índices e protecção de quatro históricos; teste isolado de idempotência/reversão aprovado. As 70 tabelas `mne` ainda não alimentam a interface privada. A integração do modelo e as fases seguintes continuam incompletas.
Actualização 29/09/2026: [revisão granular com evidência, validade e invalidação por alteração](verification-evidence.md) implementada na ficha privada. Suite: 173 testes / 1785 verificações aprovadas. Os limites e contagens abaixo descrevem a entrega anterior; o módulo integral continua incompleto.
28/09/2026. A instrução mais recente autoriza executar todas as fases e avançar automaticamente. Substitui a paragem entre fases do prompt inicial. Não presume evidências jurídicas, nomeação de revisores, publicação ou integração externa efectiva.
+635 -16
View File
@@ -2,7 +2,7 @@
"openapi": "3.1.0",
"info": {
"title": "InvestMoz — private existing-business intake",
"version": "1.1.0",
"version": "1.2.0",
"description": "Internal preparation and review only. No publication, certification, legal transfer, NDA, payment or external CRM operation."
},
"servers": [
@@ -165,37 +165,241 @@
"properties": {
"title": {
"type": "string",
"maxLength": 200
"maxLength": 200,
"description": "Proposed title"
},
"description": {
"type": [
"string",
"null"
],
"maxLength": 10000
"maxLength": 10000,
"description": "Private description"
},
"declared_reason": {
"type": [
"string",
"null"
],
"maxLength": 2000
"maxLength": 2000,
"description": "Reason and objective"
},
"source_reference": {
"type": [
"string",
"null"
],
"maxLength": 2000
"maxLength": 2000,
"description": "Source reference"
},
"valid_until": {
"type": [
"string",
"null"
],
"format": "date"
"maxLength": 10,
"format": "date",
"description": "Declared expiry"
},
"proponent_type": {
"type": [
"string",
"null"
],
"maxLength": 30,
"enum": [
"person",
"organization",
null
],
"description": "Applicant type"
},
"proponent_name": {
"type": [
"string",
"null"
],
"maxLength": 200,
"description": "Name"
},
"tax_identifier": {
"type": [
"string",
"null"
],
"maxLength": 50,
"description": "Tax identifier"
},
"contact_email": {
"type": [
"string",
"null"
],
"maxLength": 254,
"description": "Contact email"
},
"contact_phone": {
"type": [
"string",
"null"
],
"maxLength": 40,
"description": "Phone"
},
"country": {
"type": [
"string",
"null"
],
"maxLength": 2,
"description": "Country (two-letter code)"
},
"acting_capacity": {
"type": [
"string",
"null"
],
"maxLength": 30,
"enum": [
"holder",
"representative",
null
],
"description": "Acting capacity"
},
"declared_holder": {
"type": [
"string",
"null"
],
"maxLength": 200,
"description": "Declared holder"
},
"mandate_reference": {
"type": [
"string",
"null"
],
"maxLength": 500,
"description": "Mandate reference"
},
"mandate_valid_until": {
"type": [
"string",
"null"
],
"maxLength": 10,
"format": "date",
"description": "Mandate expiry"
},
"legitimacy_declaration": {
"type": [
"string",
"null"
],
"maxLength": 3,
"enum": [
"yes",
"no",
null
],
"description": "I declare authority to submit"
},
"asking_value": {
"type": [
"string",
"null"
],
"maxLength": 24,
"description": "Asking value"
},
"currency": {
"type": [
"string",
"null"
],
"maxLength": 3,
"description": "Currency (three-letter code)"
},
"value_nature": {
"type": [
"string",
"null"
],
"maxLength": 30,
"enum": [
"asking",
"indicative",
"appraised",
null
],
"description": "Value basis"
},
"equity_percentage": {
"type": [
"string",
"null"
],
"maxLength": 8,
"description": "Available equity (%)"
},
"funds_use": {
"type": [
"string",
"null"
],
"maxLength": 2000,
"description": "Use of funds"
},
"location_notes": {
"type": [
"string",
"null"
],
"maxLength": 2000,
"description": "Declared location (private)"
},
"asset_notes": {
"type": [
"string",
"null"
],
"maxLength": 2000,
"description": "Declared assets and rights summary"
},
"document_reference": {
"type": [
"string",
"null"
],
"maxLength": 2000,
"description": "Document references (not validated files)"
},
"wizard_version": {
"type": [
"string",
"null"
],
"enum": [
"1",
null
]
},
"wizard_step": {
"type": [
"string",
"null"
],
"pattern": "^[1-8]$"
}
}
},
"sector_id": {
"type": [
"integer",
"null"
],
"description": "Existing active public.sectors identifier; required on wizard submission."
}
}
}
@@ -516,41 +720,245 @@
"properties": {
"title": {
"type": "string",
"maxLength": 200
"maxLength": 200,
"description": "Proposed title"
},
"description": {
"type": [
"string",
"null"
],
"maxLength": 10000
"maxLength": 10000,
"description": "Private description"
},
"declared_reason": {
"type": [
"string",
"null"
],
"maxLength": 2000
"maxLength": 2000,
"description": "Reason and objective"
},
"source_reference": {
"type": [
"string",
"null"
],
"maxLength": 2000
"maxLength": 2000,
"description": "Source reference"
},
"valid_until": {
"type": [
"string",
"null"
],
"format": "date"
"maxLength": 10,
"format": "date",
"description": "Declared expiry"
},
"proponent_type": {
"type": [
"string",
"null"
],
"maxLength": 30,
"enum": [
"person",
"organization",
null
],
"description": "Applicant type"
},
"proponent_name": {
"type": [
"string",
"null"
],
"maxLength": 200,
"description": "Name"
},
"tax_identifier": {
"type": [
"string",
"null"
],
"maxLength": 50,
"description": "Tax identifier"
},
"contact_email": {
"type": [
"string",
"null"
],
"maxLength": 254,
"description": "Contact email"
},
"contact_phone": {
"type": [
"string",
"null"
],
"maxLength": 40,
"description": "Phone"
},
"country": {
"type": [
"string",
"null"
],
"maxLength": 2,
"description": "Country (two-letter code)"
},
"acting_capacity": {
"type": [
"string",
"null"
],
"maxLength": 30,
"enum": [
"holder",
"representative",
null
],
"description": "Acting capacity"
},
"declared_holder": {
"type": [
"string",
"null"
],
"maxLength": 200,
"description": "Declared holder"
},
"mandate_reference": {
"type": [
"string",
"null"
],
"maxLength": 500,
"description": "Mandate reference"
},
"mandate_valid_until": {
"type": [
"string",
"null"
],
"maxLength": 10,
"format": "date",
"description": "Mandate expiry"
},
"legitimacy_declaration": {
"type": [
"string",
"null"
],
"maxLength": 3,
"enum": [
"yes",
"no",
null
],
"description": "I declare authority to submit"
},
"asking_value": {
"type": [
"string",
"null"
],
"maxLength": 24,
"description": "Asking value"
},
"currency": {
"type": [
"string",
"null"
],
"maxLength": 3,
"description": "Currency (three-letter code)"
},
"value_nature": {
"type": [
"string",
"null"
],
"maxLength": 30,
"enum": [
"asking",
"indicative",
"appraised",
null
],
"description": "Value basis"
},
"equity_percentage": {
"type": [
"string",
"null"
],
"maxLength": 8,
"description": "Available equity (%)"
},
"funds_use": {
"type": [
"string",
"null"
],
"maxLength": 2000,
"description": "Use of funds"
},
"location_notes": {
"type": [
"string",
"null"
],
"maxLength": 2000,
"description": "Declared location (private)"
},
"asset_notes": {
"type": [
"string",
"null"
],
"maxLength": 2000,
"description": "Declared assets and rights summary"
},
"document_reference": {
"type": [
"string",
"null"
],
"maxLength": 2000,
"description": "Document references (not validated files)"
},
"wizard_version": {
"type": [
"string",
"null"
],
"enum": [
"1",
null
]
},
"wizard_step": {
"type": [
"string",
"null"
],
"pattern": "^[1-8]$"
}
}
},
"version_no": {
"type": "integer",
"minimum": 1
},
"sector_id": {
"type": [
"integer",
"null"
],
"description": "Existing active public.sectors identifier; required on wizard submission."
}
}
}
@@ -641,6 +1049,13 @@
"review_consent": {
"type": "boolean",
"description": "Must be true for submission. Stored in encrypted review evidence. Does not authorize publication."
},
"confirmation_code": {
"type": [
"string",
"null"
],
"description": "Exact business code required for wizard_version=1 submission, in addition to internal review consent."
}
}
}
@@ -1359,37 +1774,241 @@
"properties": {
"title": {
"type": "string",
"maxLength": 200
"maxLength": 200,
"description": "Proposed title"
},
"description": {
"type": [
"string",
"null"
],
"maxLength": 10000
"maxLength": 10000,
"description": "Private description"
},
"declared_reason": {
"type": [
"string",
"null"
],
"maxLength": 2000
"maxLength": 2000,
"description": "Reason and objective"
},
"source_reference": {
"type": [
"string",
"null"
],
"maxLength": 2000
"maxLength": 2000,
"description": "Source reference"
},
"valid_until": {
"type": [
"string",
"null"
],
"format": "date"
"maxLength": 10,
"format": "date",
"description": "Declared expiry"
},
"proponent_type": {
"type": [
"string",
"null"
],
"maxLength": 30,
"enum": [
"person",
"organization",
null
],
"description": "Applicant type"
},
"proponent_name": {
"type": [
"string",
"null"
],
"maxLength": 200,
"description": "Name"
},
"tax_identifier": {
"type": [
"string",
"null"
],
"maxLength": 50,
"description": "Tax identifier"
},
"contact_email": {
"type": [
"string",
"null"
],
"maxLength": 254,
"description": "Contact email"
},
"contact_phone": {
"type": [
"string",
"null"
],
"maxLength": 40,
"description": "Phone"
},
"country": {
"type": [
"string",
"null"
],
"maxLength": 2,
"description": "Country (two-letter code)"
},
"acting_capacity": {
"type": [
"string",
"null"
],
"maxLength": 30,
"enum": [
"holder",
"representative",
null
],
"description": "Acting capacity"
},
"declared_holder": {
"type": [
"string",
"null"
],
"maxLength": 200,
"description": "Declared holder"
},
"mandate_reference": {
"type": [
"string",
"null"
],
"maxLength": 500,
"description": "Mandate reference"
},
"mandate_valid_until": {
"type": [
"string",
"null"
],
"maxLength": 10,
"format": "date",
"description": "Mandate expiry"
},
"legitimacy_declaration": {
"type": [
"string",
"null"
],
"maxLength": 3,
"enum": [
"yes",
"no",
null
],
"description": "I declare authority to submit"
},
"asking_value": {
"type": [
"string",
"null"
],
"maxLength": 24,
"description": "Asking value"
},
"currency": {
"type": [
"string",
"null"
],
"maxLength": 3,
"description": "Currency (three-letter code)"
},
"value_nature": {
"type": [
"string",
"null"
],
"maxLength": 30,
"enum": [
"asking",
"indicative",
"appraised",
null
],
"description": "Value basis"
},
"equity_percentage": {
"type": [
"string",
"null"
],
"maxLength": 8,
"description": "Available equity (%)"
},
"funds_use": {
"type": [
"string",
"null"
],
"maxLength": 2000,
"description": "Use of funds"
},
"location_notes": {
"type": [
"string",
"null"
],
"maxLength": 2000,
"description": "Declared location (private)"
},
"asset_notes": {
"type": [
"string",
"null"
],
"maxLength": 2000,
"description": "Declared assets and rights summary"
},
"document_reference": {
"type": [
"string",
"null"
],
"maxLength": 2000,
"description": "Document references (not validated files)"
},
"wizard_version": {
"type": [
"string",
"null"
],
"enum": [
"1",
null
]
},
"wizard_step": {
"type": [
"string",
"null"
],
"pattern": "^[1-8]$"
}
}
},
"sector_id": {
"type": [
"integer",
"null"
],
"description": "Existing active public.sectors identifier; required on wizard submission."
}
}
}
@@ -0,0 +1,24 @@
{
"passed": true,
"observations": [
{
"scenario": "autosave_and_reload",
"passed": true
},
{
"scenario": "responsive_summary",
"width": 390,
"passed": true
},
{
"scenario": "responsive_summary",
"width": 1440,
"passed": true
},
{
"scenario": "private_submission",
"passed": true
}
],
"errors": []
}
@@ -0,0 +1,43 @@
# Formulário por etapas — entrega local, 29/09/2026
**Disponível em `/investidor/negocios`: criar rascunho e abrir a ficha. O prompt A–C completo ainda não está entregue.** Este incremento implementa a preparação privada por etapas, autosave, retoma, sector e confirmação. Não implementa ainda publicação, uploads seguros, manifestações MNE ou operação integral.
1. **Diagnóstico tecnológico:** Laravel 13.30.1, PHP de validação 8.5.11, React 19.2.8, TypeScript 5.9.3, Inertia, Vite Plus, Leaflet e PostgreSQL 17.11/PostGIS. Mantidos componentes e autenticação por sessão/email verificado. Filas database/Redis e disco S3 são configuráveis; conectividade Redis/S3/ClamAV e CRM não ensaiada neste incremento. Sem novas dependências.
2. **Pressupostos:** formulário autenticado, classificação reservada e `public.existing_businesses` como autoridade. Identidade e titularidade são declarações pendentes. Referências documentais não são uploads, mandatos validados ou autorizações de transmissão. O preenchimento permite rascunhos incompletos; a submissão aplica validações adicionais. Clientes anteriores sem wizard_version conservam o contrato legado; converter todos os clientes exige uma migração de contrato posterior.
3. **Mapa A–C:** ver matriz abaixo. Não há equivalência entre oito etapas visíveis e implementação de todos os requisitos dessas etapas.
4. **Plano por fase:** responsável técnico da implementação: Codex. Incremento A1 executado: schema/validação, FK sectorial, wizard e testes. A2 pendente: localização normalizada/cartografia, contexto económico, anexos/scanner, consentimentos separados e acessibilidade integral. B pendente: manifestação MNE ligada a anúncio publicado, perfil e CRM idempotente. C pendente: aprovações, tarefas, SLA, alertas e dashboard. Dependências e gates mantidos em `implementation-status.md`; autorizações de desenvolvimento existentes preservadas.
5. **Modelo PostgreSQL:** coluna nullable bigint `existing_businesses.sector_id`, FK RESTRICT para a taxonomia existente e índice B-tree completo. Dados declarativos privados ficam no payload cifrado já existente; activos/direitos continuam nas entidades tipadas existentes. Não foram criadas pessoas, sectores ou negócios no schema mne. Localização deste formulário é ainda descritiva, não georreferenciada.
6. **Migration/modelos:** `2026_09_29_200000_existing_business_form_sector.php` aplicada localmente. DOWN remove a coluna somente se não houver associações sectoriais. Modelo ExistingBusiness existente preservado; `BusinessFormSchema` centraliza campos, rótulos PT/EN e validação. Snapshot/auditoria cifrados existentes conservados.
7. **API:** mesmos nove endpoints privados `/api/v1/existing-business`. `sector_id` é adicional no draft; `confirmation_code` exigido na submissão de wizard_version=1. GET da ficha fornece formSchema e sectores activos. Erros 403/409/422 e ausência de publicação preservados. OpenAPI actualizado para 1.2.0; schema exportável por `tools/export-business-form-schema.php`.
8. **Permissões:** proprietário verificado cria/edita rascunho ou pedido de informação; revisor designado, independente e com MFA consulta submissões. Terceiros recusados. Revisores recebem os novos campos apenas após a policy de consulta. Autosave usa as mesmas autorizações e versões da gravação manual.
9. **Estados:** rascunho → submetido; revisão/pedido de informação/retirada existentes. Confirmação explícita por código e consentimento de análise interna no servidor. Repetir submissão com versão anterior dá 409 sem duplicar histórico; ainda não há comprovativo idempotente por chave de pedido. As duas aprovações e os demais estados de A–C continuam pendentes.
10. **Serviços/eventos/Jobs:** reutilizados ExistingBusinessDrafts, ExistingBusinessIntake, objectos, snapshots e ponte legada. Nenhum Job externo, tarefa de triagem, notificação ou envio CRM novo. O consentimento actual é de revisão interna; não é o catálogo completo de consentimentos versionados do anexo.
11. **React:** `business-wizard.tsx` oferece oito etapas, progresso, PT/EN nos controlos principais, autosave após três segundos, retoma no servidor, resumo e confirmação. Navegação/fecho avisam sobre alterações não gravadas; conflitos não substituem silenciosamente dados. Representante revela campos de mandato. Activos/direitos reutilizam BusinessObjects; estes componentes existentes ainda não estão integralmente traduzidos. Criação/consulta administrativa por representação de terceiros permanece pendente.
12. **Código por ficheiro:** novos `app/Services/BusinessFormSchema.php`, migration sectorial, `resources/js/pages/investment/business-wizard.tsx`, `tests/Feature/ExistingBusinessWizardTest.php`, exportador de schema e ferramentas de navegador. Alterados controlador, Drafts, Intake, business.tsx, businesses.tsx, business-types.ts e gerador OpenAPI. Código completo nesses ficheiros; sem ficheiros removidos nem commit Git.
13. **Testes:** suite completa executada: 180 testes / 1845 verificações. Depois do controlo adicional por modalidade e do teste de rollback, suite específica do wizard: 2 testes / 30 verificações aprovados. Cobertura inclui cifragem, terceiro recusado, retoma, sector, dados inválidos, conflito de versão, mandato, activo obrigatório, confirmação, submissão duplicada e rollback. Navegador: autosave/recarga, resumo em 390/1440 px e submissão, sem excepções JS. Isto não é uma auditoria WCAG completa.
14. **Comandos:** abaixo. Não foi necessário instalar pacotes Composer/npm. Backend requer PHP com pgsql. Migration local aplicada após backup/restauro, usando `--path` específico. Os scripts de navegador e fixtures só usam QA em 55441/18027, nunca a base local operacional.
15. **Resultados:** PHPStan, Pint, TypeScript, lint do wizard e build aprovados. Backup com restauro de 210 tabelas: `storage/app/private/mne-integrity-backup-20260929-200103/`. Evidência do navegador: `wizard-browser-validation.json`. Auditoria de dependências e ensaios de serviços externos não executados neste incremento.
16. **Riscos residuais:** restantes campos e relações A; identidade normalizada; coordenadas/geometria; CRM MNE; scanner/upload; redis em operação; consentimentos separados; comprovativo/notificações; SLA; aprovações/publicação; tradução integral; testes de acessibilidade, carga e serviços externos. A conta de BD local ainda é superuser. Nenhuma alegação de prontidão para produção.
17. **Go/No-Go:** Go para testar preparação privada local; **No-Go para entrega integral A–C ou Go-Live**. Os fluxos incompletos não foram expostos como funcionais.
| Requisito | Implementação actual | Estado |
|---|---|---|
| A: proponente/representação | Campos cifrados, mandato condicional, declaração e validação | Parcial; sem verificação de identidade/documentos |
| A: modalidade/sector/valor | Taxonomia existente, FK, limites, moeda e natureza na submissão | Parcial; faltam intervalos e estruturas completas |
| A: localização/contexto | Descrição privada | Geografia estruturada e ligações pendentes |
| A: activos/direitos | Editor tipado existente; objecto aplicável exigido por modalidade | Parcial |
| A: fontes/documentos | Referências declaradas | Upload/scanner pendentes |
| A: rascunho/retoma/resumo | Autosave, versão, oito etapas, confirmação no servidor | Testado |
| B: interesse/CRM | Serviço geral existente preservado | Integração MNE pendente |
| C: revisão | Revisão privada e histórico existentes | Tarefas/SLA/dupla aprovação pendentes |
```powershell
php artisan migrate --path=database/migrations/2026_09_29_200000_existing_business_form_sector.php
php tools/export-business-form-schema.php
node tools/existing-business-openapi.mjs
php vendor/bin/phpunit --filter ExistingBusinessWizard --do-not-cache-result
npm.cmd run types:check
npm.cmd run build
```
Em ambiente QA isolado (`APP_ENV=testing`, base `portal_services_test`, porta 55441): `php tools/business-wizard-browser-seed.php`, seguido de `node tools/business-wizard-browser.mjs`, com servidor QA 18027 e navegador CDP 9224. As fixtures sintéticas ficam apenas nessa base de teste.
@@ -0,0 +1,55 @@
# Reconciliação do DDL de referência — 30/09/2026
Origem: anexo `ce3b9157-6463-4c3f-af50-6d53cba78a23/Pasted text.txt`, capítulo 22. A instrução «Assumir que todas as fase estão aprovadas» substitui a pendência de autorização de fases do diagnóstico anterior. Não se exige nova aprovação genérica. O requisito de concluir a correspondência antes do DDL continua aplicável.
## Evidência executada
`tools/inventory-reference-schema.php` consulta PostgreSQL numa transacção REPEATABLE READ, READ ONLY. Inventaria tabelas e vistas, colunas/tipos/defaults, constraints, índices, triggers e contagens, sem exportar registos pessoais nem credenciais.
Resultado: **237 tabelas, 2.343 colunas, 806 índices, 1.171 constraints**. Inventário privado: `storage/app/private/reference-reconciliation-20260930-042147/inventory.json`; SHA-256 `bbd0cd93d590be0d4121efad6009e66f1a545c280f2491fd845831070712d723`.
O total inclui schemas legados e tabelas de extensões. Não significa 237 entidades aplicacionais distintas. A evidência é um inventário, não um backup restaurável nem certificado de qualidade dos dados.
## Matriz de correspondência
As decisões abaixo seleccionam objectos reais. «Estender» não significa que a alteração já foi aplicada. Nenhuma equivalência parcial é declarada cobertura integral.
| Conceito proposto | Objecto existente / evidência no repositório | Cobertura e lacunas | Decisão técnica |
|---|---|---|---|
| Fontes / data_sources | `public.geo_sources` (migration territorial_registry); `public.investment_legal_sources` (investment_zone_foundation); `public.ai_sources`; `investmoz.fontes_dados`; `mne.source_records` | Há cinco mecanismos especializados, com identidades, validações e consumidores diferentes. A fonte jurídica não é automaticamente autorização para IA nem proveniência cartográfica. Não existe correspondência transversal comprovada. | Mapear por compatibilidade, preservando tipo e schema na identidade. Não criar `data_sources` nem fundir registos por URL apenas. A definição do adaptador e da referência transversal ainda falta. |
| Diplomas / legal_instruments | `public.investment_legal_sources`; modelo InvestmentLegalSource; serviço InvestmentZones | Diploma, artigo, entidade competente, URL, vigência, versão, proprietário e revisor já existem. Tipo, número estruturado e publicação não têm colunas próprias. | Estender este mestre com os campos efectivamente necessários; não duplicar diplomas em legal_instruments. Documentos legais do portal conservam a referência ao ficheiro. |
| Organizações / organisations | `public.organizations`, `memberships`; migration investmoz_identity; `mne.organizations` separado | Mestre público UUID; pertenças existentes. MNE não está integrado como autoridade. Tax identifier, contactos e site não devem ser copiados silenciosamente entre módulos. | Reutilizar public.organizations; mapear MNE apenas por vínculo comprovado. Preservar IDs e contas bigint. |
| Áreas administrativas | `public.geo_locations`, `geo_territory_profiles`, `geo_names`; seis tabelas `investmoz.provincia` a `aldeia_quarteirao`; TerritorialRegistry | Mestre aplicacional UUID e cadastro administrativo inteiro coexistem: 1.990 geo_locations e 21.410 unidades nas seis tabelas. Códigos/níveis precisam de correspondência, não de conversão por posição ou nome. | Mapear por compatibilidade; não criar administrative_areas. Preservar a hierarquia completa, IDs e precisão. |
| Entidades territoriais | `economic_clusters`, `development_corridors`, `investment_zones`, `value_chains`, `value_chain_links`; migrations clusters/corridors/zone_foundation | Tipos especializados e relações já existem. Subcluster/nó territorial não têm um mestre identificado; não confundir elo de cadeia com nó geográfico. | Reutilizar mestres especializados e estender relações explícitas; não criar territorial_entities como segundo cadastro. |
| Investimentos | `investment_items`, `investment_item_projects`, `investment_item_opportunities`, `projects`, `opportunities`, `existing_businesses`; InvestmentIdentity | 6 items ligados a 6 oportunidades, 16 projectos e 0 negócios. O item é identidade de ligação, não cópia completa da ficha. IDs UUID e bigint têm contratos distintos. | Reutilizar InvestmentIdentity e os mestres. Não criar investments; não cadastrar projectos em massa só para preencher a ponte. |
| Relações | `cluster_projects`, `corridor_projects`, `corridor_clusters`, `opportunity_*`, `investment_zone_links`, `investment_zone_relations` | FK explícitas; relações de zona já têm fonte, vigência, validação, associação, prioridade e justificação. Nem todos os pivots têm essa cobertura. | Estender somente pivots deficientes; manter os actuais. Não criar investment_relations polimórfica concorrente. |
| Dependências | `complementary_project_links`, migration investmoz_clusters | Direcção anchor→complementary, tipos dependency/enabler/supplier/market, auto-relação recusada. Não foi identificado controlo de ciclos indirectos ou concorrentes. | Estender esta tabela e o serviço de escrita para dependency; não tratar supplier/market como dependência acíclica. Falta implementar/testar controlo de ciclos. |
| Geometrias | `geo_geometries`, `project_locations`, `investment_zone_geometries`; migrations geography/zone_foundation | PostGIS 4326, 2D, não vazio, válido, tipo consistente e limites geográficos já impostos. Zonas distinguem perímetro oficial/influência/site e fonte legal. | Reutilizar. Não criar spatial_features nem duplicar geometria em cada entidade. Pertença jurídica nunca inferida por ST_Intersects. |
| Instalações | `existing_business_objects`; `mne.assets`, `real_estates`, `real_estate_units`, `improvements`; respectivas migrations e serviços | Existem activos/instalações de negócios, mas não um inventário operacional de lotes/capacidade de zonas. Payload privado não é uma tabela de reservas. | Criar apenas a relação/entidade operacional de instalação territorial ausente, após distinguir instalação de activo patrimonial. Não copiar os activos MNE. Ainda não implementada. |
| Regras regulatórias | `investment_legal_sources`, regras de publicação em InvestmentZones, `mne.restrictions`, `portal_resources` | Diplomas, restrições patrimoniais e recursos existem; não foi encontrado cadastro de regras territoriais versionadas dos cinco tipos do anexo. | Criar regras territoriais referenciando o diploma existente, com FK explícitas; preservar restrições patrimoniais. Ainda não implementado. |
| Documentos | `public.documents`, pivots `*_documents`, portal_resources; `mne.documents` e qualificações skills_edu | Mestre público tem UUID, chave, MIME, checksum, classificação e versão; domínios mantêm ligações próprias. Novos campos de vigência/título/fonte exigem mapeamento. | Reutilizar documents e pivots; não criar outro documents nem copiar ficheiros. MNE conserva contrato enquanto não reconciliado. |
| Indicadores | Contagens calculadas em EducationController; `skills_edu.skill_gaps`; conteúdo de investment_dossiers | Existem métricas calculadas, mas não série genérica com definição, unidade, natureza, período, fonte e aprovação. Lacuna de competências não equivale a qualquer indicador. | Criar série de indicadores apenas para essa lacuna, com proprietário por FK explícita. Não persistir cópia das contagens dos dashboards. Ainda não implementado. |
| Manifestações | `interest_requests`, `interest_submission_receipts`, leads, contacts, consents, outbox_events; InterestSubmission e ConvertInterestToLead | Fluxo de interesse e CRM existente; consentimentos e recibos separados. Manifestação não é acto administrativo nem candidatura. | Reutilizar fluxo e IDs; estender contexto/SLA onde faltar, sem expressions_of_interest paralelo. |
| Versões | version_no dos mestres; investment_zone_revisions, geo_territory_revisions, existing_business_revisions, investment_submission_revisions | Snapshots especializados existem; version_no isolado não permite reconstrução. Não há histórico completo de todos os mestres. | Reutilizar históricos por domínio e estender cobertura; não criar entity_versions que duplique snapshots existentes. |
| Auditoria | `public.audit_events`, PortalAudit, trigger imz_audit_events_immutable; auditorias especializadas mne/skills_edu | Auditoria pública já recusa UPDATE/DELETE/TRUNCATE por statement, incluindo zero linhas. Conta proprietária pode alterar DDL. | Reutilizar; preservar tipos e segregação de dados. Não substituir UUID por bigserial nem criar outro audit_events público. |
## Decisões e limites
Responsável pela autorização das fases: utilizador, instrução de 30/09/2026. Decisões técnicas: reconciliação acima, baseada no inventário e no código. Esta identificação não inventa revisores humanos, mandatos públicos, aprovação jurídica ou testes não executados.
Não executar o bloco BEGIN…COMMIT do anexo: criaria mestres paralelos e `IF NOT EXISTS` ocultaria divergências, por exemplo em `documents` e `audit_events`. Não eliminar os schemas existentes ou aplicar DROP CASCADE. Nenhuma migration nova foi preparada nesta etapa: ainda faltam contratos de compatibilidade e desenho detalhado das extensões/criações identificadas. O gate técnico de fecho da reconciliação está, por isso, **incompleto**, apesar da autorização de todas as fases.
## Reconciliação, testes e rollback a executar com as migrations
1. Guardar inventário e backup restaurável de todos os schemas; restaurar isoladamente e reconciliar contagens. O inventário acima não substitui este passo.
2. Congelar por migration os objectos/colunas/restrições novos e a decisão desta matriz que os justifica. Não reescrever migrations já aplicadas.
3. Validar FK, unicidade e duplicados lógicos com relatório de excepções; não eliminar ou fundir por semelhança de nome.
4. Verificar geometrias por validade, vazio, SRID e igualdade geométrica quando houver transformação. Manter geometrias restritas fora de respostas públicas.
5. Exercitar dependências directas/indirectas, updates, restauro lógico e duas sessões concorrentes. A auto-relação actualmente recusada não prova ausência de ciclos.
6. Exercitar autorização, datas, versões, consentimento, idempotência e contratos de URL/API. Testes devem usar dados sintéticos numa base isolada, nunca limpar a base operacional.
7. Comparar contagens/checksums antes/depois e IDs dos mestres. Aceitar somente as diferenças explicitadas no plano da migration.
8. DOWN remove apenas objectos introduzidos, com RESTRICT, recusando perda de novos dados. Consolidar dados exige mapa reversível de IDs e restauro testado; não foi realizada consolidação nesta etapa.
## Estado real da entrega
Executados: inventário real e análise dos 16 conceitos, com decisões de reaproveitamento e lacunas identificadas. Não executados: novas migrations, consolidação, testes de concorrência, reserva de instalações, motor regulatório, séries de indicadores, alterações de UI ou deploy. O pacote completo pedido ainda não está concluído. A aprovação das fases não está pendente; o trabalho restante é técnico.