Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef0c2acd36 | ||
|
|
30aed7cd12 | ||
|
|
95d4cf44d7 | ||
|
|
73c046e104 | ||
|
|
15d82611b6 |
@@ -19,7 +19,8 @@ final class EnsureScopedPortalAdministration
|
||||
$path = $request->path();
|
||||
$interests = $request->is('management/interests', 'management/interests/*', 'api/v1/investment/admin/interests/*');
|
||||
// Fiscal maintenance reuses authorised administration; unrelated gates remain unchanged.
|
||||
$catalogue = $request->is('management/economic-references/lot-01', 'management/economic-references/lot-01/*',
|
||||
$catalogue = $request->is('management/territory-geometry-import', 'management/territory-geometry-import/*',
|
||||
'management/economic-references/lot-01', 'management/economic-references/lot-01/*',
|
||||
'management/education', 'management/education/*', 'management/investment/references', 'management/investment/references/*', 'api/v1/investment/admin/references', 'api/v1/investment/admin/references/*',
|
||||
'management/investment/zones', 'management/investment/zones/*', 'api/v2/investment/admin/zones', 'api/v2/investment/admin/zones/*',
|
||||
'management/balcao/atribuicoes', 'management/balcao/atribuicoes/*', 'management/balcao/servicos',
|
||||
|
||||
@@ -20,7 +20,7 @@ class TerritoryGeometryImport
|
||||
|
||||
private function guard(User $actor, bool $write = false): void
|
||||
{
|
||||
abort_unless($actor->can('manage-portal'), 403);
|
||||
abort_unless($actor->can('manage-portal') || app(ScopedPortalAdministration::class)->administrative($actor), 403);
|
||||
abort_unless(config('geometry-import.enabled'), 503, 'Importação cartográfica não disponibilizada.');
|
||||
if (! $write) {
|
||||
return;
|
||||
|
||||
@@ -319,4 +319,9 @@ Situação comum observada: `publication_status=draft`, `validation_status=valid
|
||||
### Referência da entrada única de gestão — QA, 22/10/2026
|
||||
|
||||
Nos requisitos existentes `MASTER-9f6dd1d9-0001`, `MASTER-9f6dd1d9-0003` e `MASTER-02f44baf-0002`, acrescentar como evidência delimitada a secção «Entrada única de gestão — preparação em QA, 22/10/2026» do manual de rastreabilidade e `tests/Feature/ContentManagementEntryTest.php`: quatro testes/99 asserções, ClamAV real e reversão transaccional em QA. Origem do objectivo e das regras operacionais: decisão expressa da Tempo; navegação, filtros e recuperação do upload são escolhas de implementação. Reutilizados biblioteca, armazenamento, perfis e histórico. Não altera os critérios de aceitação nem fecha os requisitos integrais: integração/contas reais e categorias não demonstradas conservam o seu estado. Geometrias permanecem separadas.
|
||||
Conferência de integração de 22/10/2026 (requisitos documentais MASTER-9f6dd1d9-0001/0003 e reutilização MASTER-02f44baf-0002): 8286d750 servido, acesso real de Nala restituído removendo dependência indevida da reserva técnica do host; lista nominal, MFA e âmbito preservados. Ver secção «Entrada única — integração e conferência real» do manual. Não encerrar o fluxo integral das oportunidades: ausência de Publicador activo e publicação promocional ainda por executor privado são saldos explícitos; não condicionam os acessos já aprovados. Não foram criadas contas, matrizes ou papéis adicionais.
|
||||
|
||||
|
||||
AUD-GEO-02/03: Recuperação da demarcação territorial — QA, 22/10/2026. 408/581 limites recuperados em QA; cadeia territorial guardada/reaberta no portal. Homologacao ainda sem reposicao; consultar a nova secao no manual existente. Mantidas 173 correspondencias pendentes e ausencia de geometrias proprias das localidades. Origem: pedido expresso da Tempo de recuperar funcionalidade; enquadramento e mensagens sao escolhas tecnicas. Nao encerra o criterio de reposicao no destino.
|
||||
|
||||
|
||||
AUD-GEO-02/03 - 22/10/2026: Demarcação territorial reposta na homologação — 22/10/2026. 408 limites (11/133/264) importados pela conta real/MFA, com tres recibos auditados; backup/restauro de 171 tabelas conferido, 333 ficheiros verificados. Consulta do mapa demonstrada na oportunidade real #30, sem a alterar. Acessos previstos e originais privados preservados. Reposicao no destino concluida; 26+147 correspondencias e ausencia de poligonos proprios de localidades mantidas como lacunas reais. Ver fecho e guia no manual existente; sem repetir provas QA aceites.
|
||||
|
||||
@@ -2255,16 +2255,79 @@ Verificação nova: quatro testes, 99 asserções, com transacções revertidas
|
||||
No navegador, login e MFA normais da conta sintética de equipa; entrada → legislação; entrada → nova oportunidade → guardar preparação incompleta → recarregar, com título recuperado e ficha QA 241. Não foi repetido o workflow integral de revisão/publicação de oportunidades já existente; a prova nova cobre a entrada e a gravação/recuperação. O ensaio documental completo foi HTTP transaccional, não uma prova visual de cada clique. A conta QA tem autorizações explicitamente sintéticas; não comprova as designações das contas reais. Não transportar fixtures, configurações QA ou concessões de acesso.
|
||||
|
||||
Provas locais: `outputs/content-management-20261022/entrada-final.png`, `build-final.log`, e `tests/Feature/ContentManagementEntryTest.php`. Estado: candidata em branch `codex/content-management-entry-QA`, ainda sem integração ou instalação na homologação. Não declarar encerrada a autonomia operacional de todas as contas/categorias apenas por esta entrada. Não houve migration, publicação remota, alteração de papéis activos ou activação fiscal.
|
||||
## Entrada única — integração e conferência real, 22/10/2026
|
||||
|
||||
A pedido expresso da Tempo, revista e integrada por avanço directo a candidata 97d51db6, seguida de aa2b3d05 (ligação da antiga página de acesso para a entrada autorizada) e 8286d750 (restituição do acesso editorial nominal). Push em main e instalação pelo processo Git automático existente; revisão efectivamente servida 8286d7504d9d368de81b4e8d095398764278bb95, build 22/10/2026 19:04:38 UTC. Nenhuma migration, publicação de conteúdo ou alteração de atribuições activas nesta entrega.
|
||||
|
||||
A leitura da sessão real de Osmane Nala após login/MFA confirmou a dupla 15/16, Promotor e Validador activos da conta 15, uma atribuição anterior revogada preservada e nenhuma atribuição Publicador na lista actual. Criação abre as seis categorias atribuídas e os promotores reais; a lista recupera os 30 resumos já publicados e a fila Validação e pareceres abre sem casos. Isto não afirma revisão de um caso inexistente nem publica para ensaio. Atribuições futuras de Aurora/Palmira/Mario conservam a decisão da Tempo e não são pré-condição dos acessos de Nala/onala. Não foi repetido login da outra titular; reutiliza-se a prova nominal anterior e a mesma regra editorial delimitada.
|
||||
## Recuperação da demarcação territorial — QA, 22/10/2026
|
||||
|
||||
Falha concreta encontrada no destino: `portal.homologation_only=false` fazia `DocumentManagementAccess::editorial` recusar a designação já aprovada. A reserva do host era escolha técnica, não condição organizacional da edição. 8286d750 remove apenas essa dependência; preserva lista nominal Nala/onala, dupla existente, MFA recente, exclusão de Auditor e âmbito documental. Um teste novo, 24 asserções, exclusivamente em QA isolada com rollback, demonstrou independência da reserva e recusa sem designação/MFA. Não foram repetidos os quatro testes/99 asserções anteriores. TypeScript foi conferido para o delta da antiga página de acesso; build/testes obrigatórios pertencem ao processo automático.
|
||||
Retomada expressa da Tempo como recuperação de funcionalidade anteriormente demonstrada, nos requisitos existentes AUD-GEO-02/03 e na experiência de cadastro. Não é criação de outra cartografia. O incremento de divulgação promocional permanece na sua branch independente.
|
||||
|
||||
Na mesma sessão real, após instalação, a entrada passou a mostrar Novo documento e Nova legislação e 29 publicados/2 não publicados. A biblioteca abriu com Receber documento em privado, Guardar em quarentena, Registar documento na biblioteca, pesquisa, Editar metadados, Publicar/Retirar e históricos. ClamAV real 1.4.3 disponível, 32 ficheiros limpos no âmbito. Nenhum conteúdo foi alterado ou publicado para repetir prova já aceite em QA.
|
||||
**Causa comprovada:** leitura SSH autenticada, em transacção READ ONLY, da release `8286d750`/base `teste_investidor`: zero linhas em `geo_geometries`, incluindo eliminadas. Cadastro preservado: 11 províncias, 148 distritos, 437 postos e 1324 localidades. Directório dos originais privados ausente; importador desabilitado. Serviço de consulta, controlador e correspondências iguais por SHA256 ao código QA. Hash do cadastro (IDs, nomes, pais, nível, origem/códigos): `9de4181c42055bc6ef11b56333b64dd463d0ebe16bb2d1ea3238fc79e69024f7`, igual no QA e destino. Não se confirmou recusa de acesso nem perda de associação: faltam os próprios dados.
|
||||
|
||||
**Dependências que permanecem explícitas:** (1) o fluxo normal de oportunidades tem interface, mas a lista actual não tem Publicador activo; falta a disponibilidade da conta/função já designada, não um comando do Codex. Nala não recebe outro papel por inferência. (2) A divulgação promocional de resumos em preparação, usada no lote Inhambane, foi operação por executor privado; ainda não existe acção genérica equivalente na interface. A candidata não encerra essa autonomia específica, nem as variantes de submissão que o editor já identifica como não implementadas. (3) manutenção de indisponibilidade persistente do scanner/worker continua técnica; as operações documentais normais não exigem comandos. Geometrias administrativas permanecem em saldo separado.
|
||||
**Histórico:** as provas de 09–10/10 em `docs/investmoz/v2/auditoria-portal/correspondencias-cartograficas.md` registam 361→393→408 correspondências em bases QA anteriores. Os originais e recibos privados permanecem disponíveis. Os transportes de 19–20/10 deste manual excluíram expressamente geometrias; a disponibilização Git também exclui dados e ficheiros privados (`deploy/homologacao/README.md`). Em 22/10 a leitura de Cabo Delgado já encontrara zero linhas; esta passagem conferiu a tabela inteira. Prova-se uma diferença de composição entre QA cartográfico e homologação. Não foi localizado recibo da instalação anterior destes limites na mesma base; não se atribui data a uma suposta eliminação nem se descarta a observação anterior no mesmo endereço.
|
||||
|
||||
Link único e guia curto encontram-se no início deste manual: https://teste-investidor.tempo.co.mz/gestao-conteudos. Recibo e imagem da conferência: outputs/content-management-delivery-20261022/. Registo documental mantido na branch QA, sem novo push main/desencadeamento de instalação apenas para o recibo.
|
||||
**Recuperação executada em QA:** o clone actual também tinha zero geometrias. Reutilizados seis originais INE/UNOCHA de 07/06/2019 (três GeoJSON e três metadados), hashes preservados e correspondências v2 existentes. O serviço `TerritoryGeometryImport` inseriu 11 províncias, 133 distritos e 264 postos, internos, com recibos e auditoria. Todos válidos/SRID4326. Nos exemplos, a consulta pública continua sem devolver limites internos. ClamAV real, assinaturas de 22/10: três originais limpos. ADM3, 26773796 bytes, excedeu o stream de 25 MiB e passou pelo scanner de ficheiro GIS com orçamento de 40 MiB nesse processo; tentativa inconclusiva preservada. Sem alterar limites globais ou configuração do daemon.
|
||||
|
||||
O mapa enquadra o limite seleccionado; se não existir, enquadra o nível superior disponível mais próximo e informa que é contexto. A lista distingue selecção de contexto superior. Fonte, edição e actualidade jurídica indeterminada preservadas. Limite administrativo não representa parcela nem autorização de investimento.
|
||||
|
||||
**Prova de interface:** oportunidade sintética QA268 criada no portal. Cabo Delgado → ANCUABE → posto ANCUABE → ANCUABE-SEDE; limites e aproximação progressiva nos três primeiros níveis. Recurso de contexto/principal guardado; após recarregar e abrir “Ver / editar / restaurar”, os quatro níveis foram recuperados. A localidade sem limite próprio apresenta aviso dos níveis superiores. A fixture QA255 anterior tinha versão fechada/payload sintético mínimo incompatível com gravação do contrato; não foi usada como prova de persistência nem corrigida silenciosamente. Uma navegação durante a recompilação QA conservava referência a asset removido; recarregar resolveu o carregamento, sem alteração no destino.
|
||||
|
||||
**Cobertura:** 408/581 elementos do exemplar aceite: 11/11 províncias, 133/159 distritos, 264/411 postos. Mantêm-se 26+147 correspondências pendentes, com nomes nas prévias e no documento de correspondências existente. O denominador do cadastro é diferente (148 distritos, 437 postos). Este exemplar não contém geometrias próprias de localidades ou parcelas; não atribuir limites superiores às 1324 localidades.
|
||||
|
||||
**Reposição preparada, não aplicada na homologação:** `outputs/investmoz-territory-recovery-20261022/demarcation/retained-geometry-restoration.zip`, SHA256 `53a5d8aa2be57603c928279cbd14561741dd2ec4b44b138fbc785380de6f951d`, contém seis originais e manifesto. Não contém dump de substituição, actores QA ou limites sintéticos. Procedimento existente:
|
||||
|
||||
1. Rever e integrar pelo Git o ajuste visual; conferir revisão efectivamente servida.
|
||||
2. Colocar originais no armazenamento privado partilhado, conservando hashes. Executar scanner actual ligado aos originais. Conferir backup/restauro e prova de activação da revisão/alvo, como exige o importador já existente; disponibilizar a configuração do importador.
|
||||
3. Gestor já autorizado, com MFA, abre `/management/territory-geometry-import`: prévia e aplicação de províncias, depois distritos, depois postos, com motivo/confirmar. Reconciliar prévia fresca no destino; não executar hashes antigos do QA. Recibos e reversão dos lotes novos existentes preservados.
|
||||
4. Conferir a cadeia territorial no formulário do destino e a recuperação após guardar, mantendo lacunas explícitas.
|
||||
|
||||
O staging dos originais, backup/restauro e configuração inicial ainda exigem operação técnica. Depois da reposição, seleccionar, guardar e recuperar a localização/mapa usam a interface. Esta passagem só leu o servidor remoto: sem alterar dados, permissões, camadas ou configuração. Não declarar a reposição no destino concluída.
|
||||
|
||||
Origem das restrições: decisões expressas da Tempo e requisitos anteriores do importador para preservação/backup e autenticação. Enquadramento e mensagens são escolhas de implementação, sem novo papel ou aprovador. Fonte oficial aceite reutilizada sem exigir nova validação da credibilidade.
|
||||
|
||||
Provas na pasta desta recuperação: inventário dos seis ficheiros, prévias/recibos por nível, `verification.json`, `homologation-readonly.json` e capturas. TypeScript, build QA e sintaxe PHP verificados; provas editoriais anteriores não repetidas.
|
||||
|
||||
|
||||
### Ajuste de acesso encontrado na disponibilização — 22/10/2026
|
||||
|
||||
A conta real de Superutilizador encontrou HTTP403 no importador: o middleware não incluía a cartografia no âmbito administrativo já aprovado e o serviço exigia exclusivamente o gate legado `manage-portal`. Corrigidos ambos para reutilizar `ScopedPortalAdministration`, com MFA de sessão e as exclusões existentes de Auditor. Não foram alteradas atribuições, papéis, contas ou o gate global. Origem do bloqueio: escolha antiga de implementação, não decisão organizacional da Tempo. Três testes transaccionais novos/15 asserções em QA: Superutilizador com MFA chega à validação de fonte/aplicação; sem MFA e conta comum são recusados; nenhum grant legado criado. Provas QA cartográficas aceites reutilizadas sem repetição.
|
||||
|
||||
|
||||
## Demarcação territorial reposta na homologação — 22/10/2026
|
||||
|
||||
Pedido expresso da Tempo executado. Candidata `15d82611` revista e integrada em `73c046e1`; ajuste de acesso `95d4cf44`, testado em QA, integrado em `30aed7cd`. A revisão `30aed7cd12ef65f797191467232087643c396751` foi instalada pelo processo Git existente e confirmada em `/build/revision.json?demarcation=30aed7cd`. O transporte de dados foi executado separadamente do Git.
|
||||
|
||||
### Dados, backup e importação
|
||||
|
||||
Seis originais INE/UNOCHA de 07/06/2019 transportados para o armazenamento privado partilhado: hashes iguais ao manifesto aceite, ficheiros 0640 e directório 0750, fora da árvore pública. Análise real dos seis pelo ClamAV 1.4.3, assinaturas de 22/10/2026, resultado limpo. Utilizado o protocolo local FILDES do daemon existente, sem instalar scanner, alargar permissões dos originais ou alterar limites globais: [documentação oficial ClamAV](https://docs.clamav.net/manual/Usage/ClamdProtocol.html).
|
||||
|
||||
Backup completo de `teste_investidor`, snapshot exportado consistente, restaurado em `127.0.0.1:55441/demarcation_hml_restore_20261022_complete_test`. As 171 tabelas coincidem integralmente por contagem e checksum canónico; arquivo dos 333 ficheiros verificado por conteúdo/hash. Recibo remoto em `/var/backups/investmoz-demarcation-20261022-complete/backup-receipt.json`, dump SHA256 `f80551de827e954a35b66ee4c8f2fcc559a4c88b48b3dc799c33b74907079c9b`. A comparação inicial revelou diferenças de ordenação Linux/Windows e a visão RLS reduzida da conta da aplicação; a comparação final usou ordenação canónica e a mesma visão completa do backup, preservando também os registos fiscais. Não se desactivou RLS.
|
||||
|
||||
A conta real `nala@tempo.co.mz` (ID15), com login e MFA normais renovados pelo titular, abriu as prévias frescas e aplicou os três níveis pela interface `/management/territory-geometry-import`:
|
||||
|
||||
| Nível | Criados | Pendentes | Lote auditado |
|
||||
| --- | ---: | ---: | --- |
|
||||
| Províncias | 11 | 0 | ec2c7c27-e4a3-466e-91a4-60aba939d3a0 |
|
||||
| Distritos | 133 | 26 | 142667c7-7309-4461-8d0a-fd00cb421577 |
|
||||
| Postos administrativos | 264 | 147 | b6d5a7e2-2f44-4bd6-9634-d97ee58ab718 |
|
||||
|
||||
Total: **408 limites internos**, válidos, não vazios, SRID4326; três recibos e três eventos `geometry_import_applied` ligados ao actor real. As geometrias foram repostas para consulta autorizada no cadastro; não foram publicadas camadas internas. Reversão existente disponível por lote, preservando originais, histórico e reutilizações e recusando alterações posteriores. Não se executou reversão no destino.
|
||||
|
||||
### Delta e acessos conferidos
|
||||
|
||||
Na oportunidade real #30, «Centro de Emergência Turística e Medicina de Mergulho de Tofo», a sessão real abriu Recursos → Ver recurso. O mapa desenha o limite da **província de Inhambane**, com fonte/edição e identificação de território seleccionado. Não é um polígono próprio de Tofo: distrito/posto/localidade continuam por indicar nessa ficha, como antes. A oportunidade, o recurso e a publicação não foram gravados nem alterados. Captura: `hml-demarcacao-tofo.png`. A prova QA aceite de guardar/reabrir quatro níveis foi reutilizada, sem repetição.
|
||||
|
||||
Consultas de controlo no destino recuperaram 1/2/3 limites na cadeia Cabo Delgado → distrito Ancuabe → posto Ancuabe. A localidade Ancuabe-Sede continua sem limite próprio e com três limites ancestrais de contexto. O cadastro territorial conserva exactamente o hash anterior `9de4181c42055bc6ef11b56333b64dd463d0ebe16bb2d1ea3238fc79e69024f7`.
|
||||
|
||||
Conferidos os acessos das contas existentes: IDs15 e16 mantêm a designação aprovada de Superutilizador; ID15 mantém Promotor e Validador. Não foram criadas contas ou atribuições. O endpoint do cadastro aceita os perfis já autorizados; a demonstração visual autenticada foi com ID15. A conferência das atribuições de ID16 foi de política, sem simular outro login. Quinze contas sem o perfil correspondente não satisfazem essa autorização. Pedido HTTP sem sessão ao endpoint privado devolveu 401; API pública devolveu zero limites internos; tentativa de obter o original pelo caminho público devolveu 403. Originais privados e regras de acesso preservados. Contas ainda não criadas não bloquearam a reposição.
|
||||
|
||||
O importador tinha um bloqueio legado de implementação: exigia `manage-portal` e não reconhecia a administração já aprovada. Corrigido por reutilização de `ScopedPortalAdministration`, com MFA de sessão e exclusões de Auditor existentes. Três testes novos/15 asserções passaram; não foi criado novo papel, aprovador ou gate global. Não houve repetição das provas QA cartográficas aceites.
|
||||
|
||||
Após a importação, **166/171 tabelas integralmente iguais** ao backup. Apenas `geo_geometries` (0→408), `geo_write_receipts` (0→3), `audit_events` (+6), `cache` e `sessions` diferem. Oportunidades, documentos, cadastro territorial, atribuições e par de Superutilizadores preservados integralmente.
|
||||
|
||||
### Guia curto e estado final
|
||||
|
||||
Entrar em [Gestão de conteúdos e oportunidades](https://teste-investidor.tempo.co.mz/gestao-conteudos) → Gerir oportunidades e histórico → abrir a ficha → Imagens, documentos e localizações → abrir ou preparar a localização. Seleccionar a hierarquia documentada; o mapa enquadra o limite disponível. Se faltar geometria própria, a mensagem distingue o contexto superior. Guardar o recurso e reabrir pela mesma lista recupera a preparação; não é necessário comando ou Codex para a consulta ou cadastro corrente.
|
||||
|
||||
**Reposição concluída neste recorte.** Mantidas as 26 correspondências de distritos e 147 de postos pendentes, com nomes nos documentos/prévias existentes. O exemplar não contém polígonos próprios das localidades ou parcelas. Essas lacunas não bloqueiam os 408 limites recuperados. A data de eventual desaparecimento anterior no mesmo endereço continua não determinada. A divulgação promocional mantém o seu incremento separado; este fecho não a declara integrada.
|
||||
|
||||
Provas: `homologation-restored-readonly.json`, `staging-receipt.json`, `preservation-receipt.json`, `http-access-verification.json`, captura e recibos locais na pasta desta recuperação; backup/restauro e prova de activação em caminhos privados. Uma interrupção do transporte local foi resolvida por escrita atómica da mensagem, antes de qualquer importação. Não ficaram passos técnicos pendentes para esta reposição; futuras importações usam as salvaguardas de backup/MFA já existentes.
|
||||
|
||||
@@ -19,6 +19,8 @@ export type InvestmentMapFeature = {
|
||||
};
|
||||
};
|
||||
export type InvestmentMapArea = {
|
||||
selected_territory?: boolean;
|
||||
location_type?: string;
|
||||
purpose?: string;
|
||||
geometry: GeoJsonObject;
|
||||
title: string;
|
||||
@@ -74,6 +76,13 @@ export default function InteractiveInvestmentMap({
|
||||
.addTo(instance);
|
||||
L.control.scale({ imperial: false }).addTo(instance);
|
||||
const bounds: [number, number][] = [];
|
||||
const hasSelectedBoundary = areas.some((area) => area.selected_territory);
|
||||
const administrativeLevel: Record<string, number> = {
|
||||
province: 1, district: 2, admin_post: 3, locality: 4,
|
||||
};
|
||||
const closestContextLevel = contextOnly
|
||||
? Math.max(0, ...areas.map((area) => administrativeLevel[area.location_type ?? ''] ?? 0))
|
||||
: 0;
|
||||
for (const area of areas) {
|
||||
const label = document.createElement('p');
|
||||
label.textContent = `${area.title} · ${area.purpose === 'administrative_boundary' ? 'Limite administrativo da edição indicada; não representa um local de investimento' : 'Polígono cadastrado, limites oficiais por verificar'} · Fonte: ${area.source_reference ?? 'Não indicada'} · Data: ${area.source_date ?? 'Não documentada'}`;
|
||||
@@ -92,7 +101,10 @@ export default function InteractiveInvestmentMap({
|
||||
.bindPopup(label)
|
||||
.addTo(instance);
|
||||
const extent = layer.getBounds();
|
||||
if (extent.isValid())
|
||||
const focusBoundary = hasSelectedBoundary
|
||||
? area.selected_territory
|
||||
: !closestContextLevel || administrativeLevel[area.location_type ?? ''] === closestContextLevel;
|
||||
if (extent.isValid() && focusBoundary)
|
||||
bounds.push(
|
||||
[extent.getSouth(), extent.getWest()],
|
||||
[extent.getNorth(), extent.getEast()],
|
||||
|
||||
@@ -71,6 +71,12 @@ export default function TerritoryGeometryPreview({
|
||||
? 'Limite administrativo disponível para este território.'
|
||||
: 'Sem limite administrativo acessível para este território neste perfil.'}
|
||||
</p>
|
||||
{!context.selected_geometry_available && context.areas.length > 0 && (
|
||||
<p role="note">
|
||||
O mapa mostra apenas limites dos níveis superiores como contexto.
|
||||
Estes limites não representam o território seleccionado.
|
||||
</p>
|
||||
)}
|
||||
<p>
|
||||
{context.context_geometry_count} limites disponíveis na
|
||||
hierarquia selecionada. São contexto administrativo, não
|
||||
@@ -93,7 +99,7 @@ export default function TerritoryGeometryPreview({
|
||||
<ul>
|
||||
{context.areas.map((area, index) => (
|
||||
<li key={index}>
|
||||
{area.title} · Fonte:{' '}
|
||||
{area.title} · {area.selected_territory ? 'Território seleccionado' : 'Contexto de nível superior'} · Fonte:{' '}
|
||||
{area.source_reference ?? 'Não indicada'} ·
|
||||
Edição: {area.source_date ?? 'Não indicada'}
|
||||
</li>
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Services\TerritoryGeometryImport;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Tests\TestCase;
|
||||
|
||||
class TerritoryGeometryApprovedAdministrationTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function test_existing_superuser_with_completed_mfa_reaches_importer_without_legacy_manager_grant(): void
|
||||
{
|
||||
$actor = $this->holder();
|
||||
$this->actingAs($actor)->withSession(['portal.mfa_actor' => $actor->id, 'portal.mfa_at' => time(), 'portal.access_revision' => (int) $actor->portal_access_revision]);
|
||||
$this->assertFalse($actor->can('manage-portal'));
|
||||
// A missing exemplar must produce the source error, not deny the approved actor.
|
||||
try {
|
||||
app(TerritoryGeometryImport::class)->preview($actor, 1);
|
||||
$this->fail('The deliberately absent source must be reported.');
|
||||
} catch (HttpException $exception) {
|
||||
$this->assertSame(422, $exception->getStatusCode());
|
||||
}
|
||||
$this->getJson('/management/territory-geometry-import')->assertStatus(422);
|
||||
$this->postJson('/management/territory-geometry-import', [])->assertStatus(422);
|
||||
$this->assertFalse($actor->can('manage-portal'));
|
||||
}
|
||||
|
||||
public function test_superuser_without_mfa_session_cannot_import(): void
|
||||
{
|
||||
$actor = $this->holder();
|
||||
$this->actingAs($actor)->getJson('/management/territory-geometry-import')->assertForbidden();
|
||||
$this->postJson('/management/territory-geometry-import', [])->assertForbidden();
|
||||
}
|
||||
|
||||
public function test_ordinary_account_does_not_gain_cartographic_administration(): void
|
||||
{
|
||||
$this->holder();
|
||||
$actor = User::factory()->create();
|
||||
$this->actingAs($actor)->getJson('/management/territory-geometry-import')->assertForbidden();
|
||||
$this->postJson('/management/territory-geometry-import', [])->assertForbidden();
|
||||
}
|
||||
|
||||
private function holder(): User
|
||||
{
|
||||
$identity = DB::selectOne('select current_database() as name,inet_server_port() as port');
|
||||
$this->assertSame('simulator_fiscal_delta_20261021_test', $identity->name);
|
||||
$this->assertSame(55441, (int) $identity->port);
|
||||
config(['portal.profile_access_enabled' => true, 'portal.managers' => [], 'geometry-import.enabled' => true,
|
||||
'geometry-import.directory' => storage_path('app/private/nonexistent-demarcation-source')]);
|
||||
$actor = User::factory()->create(['two_factor_secret' => Crypt::encryptString('JBSWY3DPEHPK3PXP'), 'two_factor_confirmed_at' => now()]);
|
||||
$other = User::factory()->create();
|
||||
DB::table('portal_superuser_pair')->updateOrInsert(['id' => 1], ['first_user_id' => $actor->id, 'second_user_id' => $other->id, 'approved_by' => $other->id]);
|
||||
return $actor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
// Rehearse recovery with the existing importer. Never accepts an active database.
|
||||
require dirname(__DIR__).'/vendor/autoload.php';
|
||||
$app = require dirname(__DIR__).'/bootstrap/app.php';
|
||||
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
|
||||
$target = Illuminate\Support\Facades\DB::selectOne('select current_database() as database,inet_server_port() as port');
|
||||
throw_unless($target->database === 'simulator_fiscal_delta_20261021_test' && (int) $target->port === 55441
|
||||
&& config('database.connections.pgsql.host') === '127.0.0.1', RuntimeException::class, 'Isolated QA only.');
|
||||
$app->detectEnvironment(fn () => 'testing');
|
||||
config(['geometry-import.enabled' => true, 'portal.managers' => ['qa-content-promoter@example.test'],
|
||||
'services.malware.scanner' => 'clamd', 'services.malware.socket' => null,
|
||||
'services.malware.host' => '127.0.0.1', 'services.malware.port' => 3310]);
|
||||
$actor = App\Models\User::where('email', 'qa-content-promoter@example.test')->firstOrFail();
|
||||
$directory = config('geometry-import.directory');
|
||||
$out = storage_path('app/private/territory-demarcation-recovery');
|
||||
Illuminate\Support\Facades\File::ensureDirectoryExists($out, 0700);
|
||||
$manifest = [];
|
||||
foreach (glob($directory.'/moz-adm*-ine-*') as $file) {
|
||||
$manifest[] = ['file' => basename($file), 'bytes' => filesize($file), 'sha256' => hash_file('sha256', $file)];
|
||||
}
|
||||
file_put_contents($out.'/source-inventory.json', json_encode($manifest, JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));
|
||||
$scanner = app(App\Services\Malware\MalwareScanner::class);
|
||||
$health = $scanner->health();
|
||||
throw_unless($health['available'], RuntimeException::class, 'Current scanner unavailable.');
|
||||
$scans = [];
|
||||
foreach ([1, 2, 3] as $level) {
|
||||
$file = $directory.'/moz-adm'.$level.'-ine-20190607.geojson';
|
||||
// Scope the stream budget to this retained GIS original in this process only.
|
||||
config(['services.malware.max_file_bytes' => filesize($file), 'services.malware.max_stream_bytes' => filesize($file)]);
|
||||
$scan = $scanner->scan($file, filesize($file), storage_path('app/private'));
|
||||
$scan['signatures_at'] = $scan['signatures_at']?->format(DATE_ATOM);
|
||||
if ($level === 3 && $scan['reason_code'] === 'scanner_decompression_limit') {
|
||||
$binary = getenv('GIS_CLAMSCAN_BINARY');
|
||||
$database = getenv('GIS_CLAMSCAN_DATABASE');
|
||||
throw_unless(is_file($binary ?: '') && is_dir($database ?: ''), RuntimeException::class, 'GIS scanner paths required.');
|
||||
$process = new Symfony\Component\Process\Process([$binary, '--database='.$database, '--max-filesize=40M', '--max-scansize=100M', '--no-summary', $file]);
|
||||
$process->setTimeout(120)->mustRun();
|
||||
throw_unless(str_ends_with(trim($process->getOutput()), ': OK'), RuntimeException::class, 'GIS original scan incomplete.');
|
||||
$version = new Symfony\Component\Process\Process([$binary, '--database='.$database, '--version']);
|
||||
$version->mustRun();
|
||||
$parts = explode('/', trim($version->getOutput()));
|
||||
$signatureTime = strtotime(end($parts));
|
||||
throw_unless($signatureTime && time() - $signatureTime < 48 * 3600, RuntimeException::class, 'Current GIS signatures required.');
|
||||
$scan = ['result' => 'clean', 'engine' => 'ClamAV', 'engine_version' => trim($version->getOutput()),
|
||||
'signatures_at' => gmdate(DATE_ATOM, $signatureTime), 'scope' => 'retained GIS original only',
|
||||
'original_sha256' => hash_file('sha256', $file), 'stream_attempt' => $scan, 'exit_code' => $process->getExitCode()];
|
||||
}
|
||||
$scans[] = ['file' => basename($file), 'sha256' => hash_file('sha256', $file), 'scan' => $scan];
|
||||
echo json_encode(['level' => $level, 'scan' => $scan]).PHP_EOL;
|
||||
throw_unless($scan['result'] === 'clean', RuntimeException::class, 'Original scan incomplete.');
|
||||
}
|
||||
file_put_contents($directory.'/geometry-source-scans.json', json_encode(['captured_at' => gmdate('c'), 'files' => $scans], JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));
|
||||
$service = app(App\Services\TerritoryGeometryImport::class);
|
||||
foreach ([1, 2, 3] as $level) {
|
||||
$plan = $service->preview($actor, $level);
|
||||
file_put_contents($out.'/preview-'.$level.'.json', json_encode($plan, JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));
|
||||
echo json_encode(['level' => $level, 'resolved' => $plan['resolved'], 'pending' => $plan['pending']]).PHP_EOL;
|
||||
if (! in_array('--apply', $argv, true)) break; // Lower levels require imported parent correspondences.
|
||||
$result = $service->apply($actor, ['level' => $level, 'hash' => $plan['hash'], 'idempotency_key' => (string) Illuminate\Support\Str::uuid(),
|
||||
'reason' => 'Recuperação QA da demarcação anteriormente demonstrada, com originais e correspondências aceites.', 'confirm' => true]);
|
||||
file_put_contents($out.'/result-'.$level.'.json', json_encode($result, JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));
|
||||
echo json_encode(['level' => $level, 'inserted' => $result['inserted'], 'reused' => $result['reused']]).PHP_EOL;
|
||||
}
|
||||
Reference in New Issue
Block a user