Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77d133ef0e | ||
|
|
930013f920 | ||
|
|
6d14769369 | ||
|
|
a022ed9848 | ||
|
|
2f58b7d438 | ||
|
|
150bef4b89 |
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Opportunity;
|
||||
use App\Services\ProactiveSupply;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
final class ProactiveSupplyController extends Controller
|
||||
{
|
||||
public function index(Request $request,ProactiveSupply $service): JsonResponse
|
||||
{
|
||||
$v=$request->validate(['record_id'=>'nullable|integer|min:1']);
|
||||
$record=isset($v['record_id'])?Opportunity::findOrFail($v['record_id']):null;
|
||||
return response()->json($service->search($request->user(),$request->all(),$record))->header('Cache-Control','private, no-store');
|
||||
}
|
||||
}
|
||||
@@ -128,7 +128,7 @@ class OpportunityManagement
|
||||
$this->check($record, $data['etag']);
|
||||
abort_if($record->trashed() || $record->status === 'archived', 409, 'Restaure antes de editar.');
|
||||
}
|
||||
if (array_key_exists('proactive', $data)) $data['proactive'] = app(ProactiveInvestment::class)->draft($data['proactive'], $record);
|
||||
if (array_key_exists('proactive', $data)) $data['proactive'] = app(ProactiveInvestment::class)->draft($data['proactive'], $record, $actor);
|
||||
$before = $record->getAttributes();
|
||||
$previousCanonical = $record->exists ? ($this->values($record)['canonical'] ?? []) : [];
|
||||
$changes = array_intersect_key($data, array_flip([...self::FIELDS, ...self::PRIVATE_DRAFT_FIELDS]));
|
||||
@@ -227,7 +227,7 @@ class OpportunityManagement
|
||||
$record = Opportunity::withTrashed()->whereKey($id)->lockForUpdate()->firstOrFail();
|
||||
abort_if($record->owner_user_id !== null, 403, 'A v1 exige pedido e decisão de arquivo; não concede exclusão ou restauro ao Promotor.');
|
||||
$this->check($record, $data['etag']);
|
||||
if (array_key_exists('proactive', $data)) $data['proactive'] = app(ProactiveInvestment::class)->draft($data['proactive'], $record);
|
||||
if (array_key_exists('proactive', $data)) $data['proactive'] = app(ProactiveInvestment::class)->draft($data['proactive'], $record, $actor);
|
||||
$before = $record->getAttributes();
|
||||
if ($data['action'] === 'restore') {
|
||||
abort_unless($record->trashed() || $record->status === 'archived', 409, 'Registo não está eliminado/arquivado.');
|
||||
|
||||
@@ -16,12 +16,14 @@ final class ProactiveInvestment
|
||||
public const NOTICE = 'A ausência de registo não comprova ausência de oferta. Proximidade não comprova contrato, participação, energia acessível, terra disponível ou benefício fiscal. Necessidade confirmada não comprova viabilidade comercial; hipótese de fornecimento não é encomenda.';
|
||||
public const GROUPS = ['exists' => 'O que existe', 'needs' => 'Necessidades identificadas', 'ideas' => 'Ideias de investimento'];
|
||||
|
||||
public function draft(array $input, ?Opportunity $record = null): array
|
||||
public function draft(array $input, ?Opportunity $record = null, ?\App\Models\User $actor = null): array
|
||||
{
|
||||
$d = Validator::make(['proactive' => $input], [
|
||||
'proactive' => 'required|array:version,kind,evidence,need,idea,relations', 'proactive.version' => 'required|integer|in:1',
|
||||
'proactive' => 'required|array:version,kind,evidence,need,idea,relations,supply_comparisons', 'proactive.version' => 'required|integer|in:1',
|
||||
'proactive.kind' => 'required|in:exists,needs,ideas', 'proactive.evidence' => 'present|array|max:60',
|
||||
'proactive.evidence.*' => 'array:id,category,purpose,statement,interpretation,source,locator,period,scope,coverage,verified_on,access,document_id',
|
||||
'proactive.evidence.*' => 'array:id,category,purpose,statement,interpretation,source,locator,period,scope,coverage,verified_on,access,document_id,observation_id',
|
||||
'proactive.evidence.*.observation_id'=>'sometimes|nullable|string|size:64',
|
||||
'proactive.supply_comparisons'=>'sometimes|array|max:40',
|
||||
'proactive.evidence.*.id' => 'required|uuid|distinct',
|
||||
'proactive.evidence.*.category' => 'required|in:activity,resource,infrastructure,supply,demand',
|
||||
'proactive.evidence.*.purpose' => 'required|in:local,methodology',
|
||||
@@ -47,6 +49,19 @@ final class ProactiveInvestment
|
||||
'proactive.relations.*.reference' => 'required|string|max:4000',
|
||||
'proactive.relations.*.evidence_ids' => 'present|array|max:60', 'proactive.relations.*.evidence_ids.*' => 'uuid|distinct',
|
||||
])->validate()['proactive'];
|
||||
$actor??=auth()->user();
|
||||
if (!empty($d['supply_comparisons'])) $d['supply_comparisons']=app(ProactiveSupply::class)->comparisons($actor,$d['supply_comparisons'],$record);
|
||||
foreach($d['evidence'] as &$proof){
|
||||
if(!empty($proof['observation_id'])){
|
||||
$canonical=app(ProactiveSupply::class)->evidence($actor,$proof['observation_id'],$record);
|
||||
if($proof['access']!=='internal') $this->fail('evidence','A observação da base interna não pode ser reclassificada como pública.');
|
||||
$proof=array_replace($proof,$canonical);
|
||||
}
|
||||
}
|
||||
unset($proof);
|
||||
foreach($d['supply_comparisons']??[] as $comparison){
|
||||
if(!in_array($comparison['observation_id'],array_column($d['evidence'],'observation_id'),true))$this->fail('supply_comparisons','A comparação deve conservar a evidência interna da observação seleccionada.');
|
||||
}
|
||||
$ids = array_column($d['evidence'], 'id');
|
||||
foreach ([$d['need'], ...$d['relations']] as $row) {
|
||||
if (array_diff($row['evidence_ids'], $ids)) $this->fail('evidence', 'Seleccione apenas evidências desta análise.');
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Opportunity;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/** Internal observations, never a public business directory or proof of demand. */
|
||||
final class ProactiveSupply
|
||||
{
|
||||
public const LIMITATIONS = 'Informação administrativa histórica e incompleta. Actividade actual, capacidade, procura e compromissos por confirmar. Ausência de registos significa informação insuficiente, nunca ausência de oferta.';
|
||||
|
||||
public function authorize(User $actor, ?Opportunity $record = null): void
|
||||
{
|
||||
$access=app(PortalProfileAccess::class);
|
||||
$internal=collect(OpportunityManagement::CATEGORIES)->contains(fn($type)=>$access->internalAssignment($actor,$type)!==null);
|
||||
abort_unless($access->legacyWriteAllowed($actor)||$internal,403);
|
||||
if ($record?->exists) $access->authorize($actor,'view',$record);
|
||||
}
|
||||
|
||||
private function read(User $actor, callable $callback): mixed
|
||||
{
|
||||
return DB::transaction(function()use($actor,$callback){
|
||||
DB::select("select set_config('app.actor_user_id',?,true),set_config('app.proactive_supply_read','internal',true)",[(string)$actor->id]);
|
||||
return $callback();
|
||||
});
|
||||
}
|
||||
|
||||
public function search(User $actor,array $input,?Opportunity $record=null): array
|
||||
{
|
||||
$this->authorize($actor,$record);
|
||||
$v=Validator::make($input,[
|
||||
'territory_id'=>['required','uuid',Rule::exists('geo_locations','id')->whereNull('deleted_at')],
|
||||
'activity'=>'nullable|string|max:150','sector'=>'nullable|string|max:250','page'=>'nullable|integer|min:1|max:100000',
|
||||
])->validate();
|
||||
if(!Schema::hasTable('proactive_supply_observations'))return ['installed'=>false,'rows'=>[],'total'=>null,'limitations'=>self::LIMITATIONS,'status'=>'Base interna ainda não carregada neste ambiente.'];
|
||||
return $this->read($actor,function()use($v){
|
||||
$ids=[$v['territory_id']];
|
||||
// Canonical descendants, not a name-only cross-province match.
|
||||
$front=$ids;
|
||||
for($depth=0;$depth<5&&$front;$depth++){
|
||||
$front=DB::table('geo_locations')->whereIn('parent_id',$front)->whereNull('deleted_at')->pluck('id')->all();
|
||||
$ids=[...$ids,...$front];
|
||||
}
|
||||
$base=DB::table('proactive_supply_observations as o')->join('proactive_supply_sources as s','s.sha256','=','o.source_hash')->whereIn('o.territory_id',$ids);
|
||||
$sectors=(clone $base)->where('o.sector','<>','')->distinct()->orderBy('o.sector')->pluck('o.sector')->all();
|
||||
if(!empty($v['sector']))$base->where('o.sector',$v['sector']);
|
||||
if(!empty($v['activity']))$base->where(fn($q)=>$q->where('o.cae','ilike','%'.$v['activity'].'%')->orWhereRaw("translate(lower(o.activity),'áàâãéêíóôõúç','aaaaeeiooouc') LIKE ?",['%'.strtolower(\Illuminate\Support\Str::ascii($v['activity'])).'%']));
|
||||
$page=$v['page']??1;
|
||||
// Exact repeated observations are retained in storage, counted separately.
|
||||
$total=(clone $base)->whereNull('o.duplicate_of')->count();
|
||||
$duplicates=(clone $base)->whereNotNull('o.duplicate_of')->count();
|
||||
$rows=(clone $base)->whereNull('o.duplicate_of')->orderBy('o.name')->orderBy('o.id')->offset(($page-1)*25)->limit(25)->get(['o.id','o.entity_key','o.name','o.province','o.district','o.territory_id','o.territory_resolution','o.cae','o.activity','o.sector','o.source_date','o.sheet','o.row_number','o.source_hash','o.identity_conflict','s.name as source_name','s.period as source_period','s.imported_at']);
|
||||
$coverage=DB::table('proactive_supply_observations')->selectRaw("count(*) FILTER (WHERE territory_id IS NULL) AS unmapped, count(*) FILTER (WHERE territory_resolution='province_only_district_unknown') AS province_only")->first();
|
||||
return ['installed'=>true,'rows'=>$rows,'total'=>$total,'duplicate_sightings'=>$duplicates,'page'=>$page,'per_page'=>25,'sectors'=>$sectors,'territory_coverage'=>$coverage,'limitations'=>self::LIMITATIONS,'status'=>$total?'Registos documentais encontrados; não equivalem a empresas únicas ou oferta actual confirmada.':'Informação insuficiente nesta base e nestes filtros.'];
|
||||
});
|
||||
}
|
||||
|
||||
/** Resolve server-side source lineage; a caller cannot fabricate an internal observation. */
|
||||
public function comparisons(User $actor,array $input,?Opportunity $record): array
|
||||
{
|
||||
$this->authorize($actor,$record);
|
||||
$v=Validator::make(['rows'=>$input],[
|
||||
'rows'=>'array|max:40','rows.*'=>'array:observation_id,relationship,rationale,limitations',
|
||||
'rows.*.observation_id'=>'required|string|size:64|distinct',
|
||||
'rows.*.relationship'=>'required|in:competition,complementarity,partner,uncertain',
|
||||
'rows.*.rationale'=>'required|string|max:4000','rows.*.limitations'=>'required|string|max:4000',
|
||||
])->validate()['rows'];
|
||||
if(!$v)return [];
|
||||
abort_unless(Schema::hasTable('proactive_supply_observations'),422,'Base interna indisponível.');
|
||||
return $this->read($actor,function()use($v){
|
||||
$fingerprints=[];
|
||||
foreach($v as $row){
|
||||
$o=DB::table('proactive_supply_observations')->where('id',$row['observation_id'])->first();
|
||||
if(!$o)throw \Illuminate\Validation\ValidationException::withMessages(['proactive.supply_comparisons'=>'Registo da oferta indisponível; preserve a referência anterior e confira o ambiente.']);
|
||||
if($o->duplicate_of!==null||isset($fingerprints[$o->fingerprint]))throw \Illuminate\Validation\ValidationException::withMessages(['proactive.supply_comparisons'=>'Esta observação administrativa é repetida. Compare a referência original uma única vez; a repetição e a sua fonte permanecem conservadas na base interna.']);
|
||||
$fingerprints[$o->fingerprint]=true;
|
||||
}
|
||||
return $v;
|
||||
});
|
||||
}
|
||||
|
||||
public function evidence(User $actor,string $id,?Opportunity $record): array
|
||||
{
|
||||
$this->authorize($actor,$record);
|
||||
return $this->read($actor,function()use($id){
|
||||
$o=DB::table('proactive_supply_observations as o')->join('proactive_supply_sources as s','s.sha256','=','o.source_hash')->where('o.id',$id)->first(['o.*','s.name as source_name','s.period as source_period']);
|
||||
if(!$o)throw \Illuminate\Validation\ValidationException::withMessages(['proactive.evidence'=>'Observação interna indisponível neste ambiente.']);
|
||||
return ['observation_id'=>$o->id,'category'=>'supply','purpose'=>'local','access'=>'internal','document_id'=>null,
|
||||
'statement'=>'Registo administrativo: '.$o->name.'; CAE '.$o->cae.'; '.($o->activity?:'actividade por classificar').'; '.$o->district.', '.$o->province.'. Não confirma actividade actual.',
|
||||
'source'=>$o->source_name.'; SHA256 '.$o->source_hash,'locator'=>$o->sheet.'!linha '.$o->row_number,
|
||||
'period'=>$o->source_date.'; '.$o->source_period,'scope'=>$o->district.', '.$o->province,
|
||||
'coverage'=>self::LIMITATIONS.($o->identity_conflict?' Identificador partilhado por denominações divergentes: identidade por conferir.':''),
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('proactive_supply_sources', function ($t) {
|
||||
$t->char('sha256',64)->primary();
|
||||
$t->text('name');
|
||||
$t->text('period');
|
||||
$t->text('limitations');
|
||||
$t->jsonb('receipt');
|
||||
$t->timestampTz('imported_at');
|
||||
});
|
||||
Schema::create('proactive_supply_observations', function ($t) {
|
||||
$t->char('id',64)->primary();
|
||||
$t->char('source_hash',64);
|
||||
$t->foreign('source_hash')->references('sha256')->on('proactive_supply_sources')->restrictOnDelete();
|
||||
$t->text('sheet');
|
||||
$t->integer('row_number');
|
||||
$t->char('entity_key',64)->index();
|
||||
foreach (['name','province','district','cae','activity','sector','source_date'] as $field) $t->text($field);
|
||||
$t->char('fingerprint',64)->index();
|
||||
$t->char('duplicate_of',64)->nullable();
|
||||
$t->boolean('identity_conflict')->default(false);
|
||||
$t->uuid('territory_id')->nullable()->index();
|
||||
$t->text('territory_resolution');
|
||||
$t->index(['province','district']);
|
||||
$t->unique(['source_hash','sheet','row_number']);
|
||||
});
|
||||
// Technical defence of the private-analysis scope, not a new business role.
|
||||
foreach (['proactive_supply_sources','proactive_supply_observations'] as $table) {
|
||||
DB::statement("ALTER TABLE {$table} ENABLE ROW LEVEL SECURITY");
|
||||
DB::statement("ALTER TABLE {$table} FORCE ROW LEVEL SECURITY");
|
||||
DB::statement("CREATE POLICY {$table}_private_read ON {$table} FOR SELECT USING (current_setting('app.proactive_supply_read',true)='internal' AND NULLIF(current_setting('app.actor_user_id',true),'')::bigint IS NOT NULL)");
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if (DB::table('proactive_supply_sources')->exists()) throw new RuntimeException('Preserve imported source history before rollback.');
|
||||
Schema::dropIfExists('proactive_supply_observations');
|
||||
Schema::dropIfExists('proactive_supply_sources');
|
||||
}
|
||||
};
|
||||
@@ -1608,3 +1608,97 @@ Escolha expressa das parcelas correntes e anteriores, vinculada à colecta selec
|
||||
**Provas do delta:**20testes económicos novos+65afectados aceites=85aprovados;API1teste/210asserções,11casos POST/PUT/GET na base guardada `proactive_investment_20261027_test`,loopback55441,transacção com rollback. Onze recuperações recalculadas integralmente,HTML/dossier concordantes e históricos anteriores conservados.55oportunidades,64documentos,1vínculo,19rascunhos,40utilizadores,3organizações e409eventos anteriores intactos. TypeScript,build QA ePint aprovados.31ficheiros do acervo preservados por bytes,sem novas importações,publicações,scan declarado,migrations,contas ougrants. Não foi feita nova prova manual da interface nem avaliação pela equipa.
|
||||
|
||||
**Pendências concretas:**inclusão/correcção automática do dividendo/acréscimo emL6 e derivação da quota proporcional;exclusão40(1)na base com todas as suas condições;liquidação64(3)/associações64(4);créditos/benefícios específicos;fracção internacional/CDT automática;obrigação/calendárioPPC/PEC;autónomos,períodos especiais,outras moedas eM20/M22. Correspondências e parâmetros em falta afectam o caso dependente,sem revalidação genérica de fontes oficiais. Saldos anteriores mantêm o recorte ordinário já demonstrado. **L8 permanece parcial.** Recibo:`docs/investmoz/simulations/irpc-l8-economic-qa-20261028.json`.
|
||||
|
||||
|
||||
## 28/10/2026 — entrega agrupada de saldos anteriores e crédito económico pelo Git
|
||||
|
||||
**Instalado e conferido em `569a1cd750f3d2a0b87f130e992e73e070bbd1ab`**, sobre `838ad941`, por fast-forward normal em `main`,sem force. Foram entregues em conjunto os saldos anteriores `d134a92d` e o crédito económico ordinário `aae67c1`, já demonstrados emQA. Os estadosQA anteriores permanecem históricos;estes recortes deixam de estar pendentes de instalação. **L8 permanece parcial;esta entrega não fecha integralmente a liquidação anual.**
|
||||
|
||||
**Delta e preservação:**12ficheiros de aplicação,byte a byte correspondentes à candidata aceite;1005fontes antes e1011depois conferidas comGit. Demais fontes comparadas,PECcorrente,internacionalcorrente,sectores,L3/ISPC/câmbio/L5–L7,originais e contexto adaptativo preservados. Sem alterações de schema/configuração/rotas/grants,migrations ou importações de conteúdo. RuntimeQA conservado fora da árvore servida. Songo/Gurué permanecem privados eSGFTEMPO separado.
|
||||
|
||||
**Capacidade/recuperação:**82,71GiB e23,52milhões de inodes livres antes;capacidade novamente conferida após backup,com o mínimo existente do instalador e espaço real do backup. Backup fresco `2026-10-28T12:38:25Z` (14:38:25locais),em `/var/backups/investmoz-irpc-l8-grouped-20261028/backup`;dumpSHA256`007cd1851d5bd51a54265ab99a342d6a312de06ef642fc42b38b7a5e771d4d9a`. Restauro174tabelas com contagens/checksums iguais emcontentorsemrede/porta55441;369ficheiros/configuração conferidos. Depois da entrega:172tabelas de negócio e369ficheiros persistentes/configuração intactos;schema,papéis,grants epermissões iguais,identidades de comparação equivalentes. Delta operacional delimitado a `queue:restart` e2sessões anónimas do agente de entrega,correlacionadas com pedidos públicos;nenhum dado ou sessão sobrescrito para obter igualdade. Release anterior mantida até todos os controlos e troca atómica;manifest/revisão servidos confirmados.
|
||||
|
||||
**Provas reutilizadas e controlos da entrega:**85testes afectados/20económicos novos,API210asserções/11recuperações HTML/dossier e históricos aceites reutilizados;sem repetir avaliação manual de funcionalidades independentes. Instalador obrigatório:PostgreSQL1teste/3106asserções;Unit59/317;frontend1146aprovados/0falhas;TypeScript/Wayfinder/build aprovados (30,82s). SimuladorHTTP200 e8marcadores compilados dos saldos anteriores/económico/internacional/PEC conferidos. Não foram criados casosQA no portal e não se declara nova aceitação manual da equipa.
|
||||
|
||||
### Saldo concreto de L8 após a entrega
|
||||
|
||||
| Grupo | Cobertura entregue ou trabalho restante |
|
||||
| --- | --- |
|
||||
| Saldos anteriores65(3)/66 | Entregues no recorte de origens anuais ordinárias documentadas,mesmo sujeito/2026/MZN,prazos próprios5/3exercícios,utilizações efectivas e afectação explícita. Não cria nem consome saldos oficiais;casos transitórios/especiais permanecem próprios. |
|
||||
| Económico64(1–2) | Entregue para lucros ordinários qualificados,IRPC correspondente documentado,acréscimo53 já incluído emL6 e quota proporcional documentada,antes do internacional/PEC,sem dupla utilização da mesma parcela. Parcela excluída40(1)não recebe também64. |
|
||||
| Inclusão/correcção económica e quota | Ainda por implementar a inclusão/correcção automática do dividendo/acréscimo emL6,a derivação automática da quota e o tratamento completo40(1)na base. A comprovação manual corrente não é declarada automação desses grupos. |
|
||||
| Económico64(3/4) e benefícios | Liquidação,associações ecréditos/benefícios específicos mantêm qualificação,limites,cumulação e uso próprios por implementar;sem os transformar emcrédito internacional. |
|
||||
| Internacional automático | Derivação da fracção nacional e correspondência específicaCDT continuam pendentes;qualificação/dedução internacional documentada entregue preservada. |
|
||||
| Obrigações e representação | Obrigação/calendárioPPC/PEC,juros/multas/guias,autónomos,períodos especiais,outras moedas eM20/M22/transporte/submissão continuam nos grupos concretos anteriores. |
|
||||
|
||||
A falta de prova do caso (titular/rendimento/imposto/inclusão/uso/regra do período ou quota) limita só o resultado dependente;não é reabertura da autenticidade das fontes oficiais aceites nem novo aprovador. Exemplo agrupado condicionado aos suportes sintéticos já aceites:1408−12económico−8internacionalcorrente−50internacionalanterior−200PECcorrente−100PECanterior−500PPC=**538MZN de saldo simulado a pagar**;não é declaração submetida ou saldo fiscal consumido.
|
||||
|
||||
Recibo consolidado:`docs/investmoz/simulations/irpc-l8-grouped-delivery-20261028.json`. Cópia remota:`/var/backups/investmoz-irpc-l8-grouped-20261028/installed-receipt.json`. Provas locais:`outputs/irpc-l8-grouped-delivery-20261028/`. Actualização documental segue ramo próprio de recibos,sem alterar novamente a revisão servida.
|
||||
|
||||
Cópia remota do recibo conferida:SHA256`b70fe154f95162f029b5ca712b41962757360d73b0ff07f52b8ce14848d990df`;a matriz existente recebeu saldoL8 nos camposEstado,Disponibilidade activa,Evidências,Critérios restantes eDependências,só em `AUD-SIM-INT02`.
|
||||
|
||||
|
||||
## 28/10/2026 — listas como base interna da oferta: uso não demonstrado
|
||||
|
||||
**Decisão expressa da Tempo e resultado da conferência: a utilização das listas de empresas,hotéis/restaurantes como base comparativa no portal é NÃO DEMONSTRADA.** A entrega do percurso proactivo,os seus campos de evidência e os pilotos privados anteriores mantêm-se aceites;isso não demonstra carregamento,classificação e ligação destas listas ao consumidor de análise. O âmbito é primeiro a análise interna,não a publicação automática de um directório.
|
||||
|
||||
**Fonte e âmbito conferidos:**13ficheiros inventariados nos dois directórios acessíveis de empresas/relatórios,antes da selecção;originais `EMPRESAS_LIST2014–2022`, `Empresas Licenciadas`,cadastro turístico30/10/2024 e `CAE eBAU` localizados e iguais aos hashes aceites. As148557/212632/460linhasXML não vazias dos recibos anteriores são observações dos ficheiros,não empresas únicas nem carga no portal. Empresas têm província/distrito/CAE;isso não prova o mapeamento no portal. O cadastro turístico observado é de alojamento,não um censo completo da restauração. Não foi identificada lista autónoma de restaurantes nestes dois directórios;isso não significa ausência de restaurantes ou de actividades de restauração nas listas empresariais. Contactos e outros campos privados não foram publicados.
|
||||
|
||||
**Destino entregue:**código `569a1cd7`,base `teste_investidor`,198tabelas não sistémicas(174public+24skills_edu),consultadas em transacções READ ONLY com identidade técnicapostgres explicitada. Zero correspondências dos hashes originais entre368ficheiros persistentes e nenhuma correspondência exacta dos nomes/hashes nas referências pesquisadas. Cadastros `existing_businesses` e `investment_company_declarations` têm0registos nesse destino;organizações1,actividades11,documentos60 e recursos59 não são convertidos em oferta empresarial das listas. Esta é uma fotografia técnica,não prova de visibilidade para uma conta operacional,perda de registos ou ausência de oferta no mercado. Registos antigos de `investemoz` e basesQA são ambientes distintos;nenhuma eliminação é declarada. Pesquisa por nomes/hashes em metadados não exclui ficheiros renomeados,arquivados,conteúdo cifrado ou outros acervos.
|
||||
|
||||
**Causa delimitada no consumidor instalado:**`ProactiveInvestment::options` devolve documentos/grupos e referências a projecto,cluster,corredor,parque,cadeia;os hashes dos ficheiros instalados coincidem comGit. O editor recebe `observed_supply` e fontes/localizadores/períodos/cobertura como texto manual;as relações proactivas não admitem entidade empresarial destas listas. Não foi encontrada consulta de oferta carregada por localização/sector/actividade nem ligação de linhas das listas a concorrentes,complementares ou parceiros. A possível existência de cadastros noutro módulo não demonstra consumo por este percurso.
|
||||
|
||||
| Percurso de conferência — Gurué,distrito canónico | Resultado |
|
||||
| --- | --- |
|
||||
| Escolher localização | Referência canónica localizada;prova anterior de selecção territorial preservada,não se declara nova prova de interface. |
|
||||
| Consultar oferta existente das listas | Não demonstrado:consulta/importação/classificação consumida pelo percurso não identificada. A demonstração pára neste ponto. |
|
||||
| Comparar com uma ideia | Não demonstrado sobre conjunto rastreável de registos dessas listas. Campos manuais não equivalem à integração. |
|
||||
| Identificar necessidade | Não demonstrado por este fluxo;ausência de registo não confirma necessidade nem ausência de concorrência. Manter hipótese,período,cobertura e incertezas. |
|
||||
| Preparar oportunidade | Mecanismo privado entregue preservado;nenhuma oportunidade nova foi criada para simular passos anteriores ainda não demonstrados. |
|
||||
|
||||
**Critério de retoma:**lotes/linhas privados rastreáveis e classificados,reutilizando os mecanismos/cadastros existentes;consulta autorizada da oferta por localização/sector/actividade;comparação da ideia com concorrência,complementares e possíveis parceiros,com fontes/datas/cobertura;necessidade fundamentada sem inferir ausência de oferta;e prévia/gravação/recuperação da oportunidade no percurso existente emQA isolada. Só essa conferência pode demonstrar o uso;nenhum novo aprovador organizacional ou directório público é criado.
|
||||
|
||||
Sem imports,normalização,alteração de código,permissões,publicação ou escrita em dados activos. Songo/Gurué privados,aprovações/históricos e entregaL8 preservados. Registo:`docs/investmoz/unification/existing-supply-lists-audit-20261028.json`. Cobertura actualizada nas linhas existentesRF-CLU-003,MASTER-471012ab-0010,M2-d0c06a75-0147/0151 eMASTER-1f0a4cde-0002,sem nova matriz/backlog.
|
||||
|
||||
A base interna de oferta não exige transformar cada empresa numa organização utilizadora,anúncio de venda/parceria,promotor ou cliente confirmado. O cadastro e a consulta analítica são distintos da operação comercial e da divulgação.
|
||||
|
||||
|
||||
## 28/10/2026 — oferta existente ligada à análise proactiva, demonstrada em QA
|
||||
|
||||
Incremento sobre o código entregue `569a1cd7`, ramo `codex/proactive-supply-qa-20261028`: **demonstrado em QA isolada, sem instalação nem importação no activo**. O relatório de conferência anterior permanece histórico: no destino activo, o uso das listas continua não demonstrado. Reutilizam-se a aceitação do percurso, o inventário de 13 ficheiros, os originais, as fontes e as provas anteriores; não se reabre a decisão da Tempo nem se encerra o estudo de mercado nacional.
|
||||
|
||||
Carregadas privadamente **361.464 observações**: 148.556 da lista 2014–2022, 212.631 de empresas licenciadas e 277 do cadastro turístico (duas folhas com estruturas distintas). Os quatro hashes dos originais e do CAE foram conferidos. Pré-visualização, análise Microsoft Defender sem ameaças e repetição idempotente 343.041→343.041 demonstradas. Os 147.165 registos com a mesma projecção administrativa são conservados como repetições; não representam empresas únicas nem duplicados de ficheiros por bytes. As 113.880 observações com denominações divergentes para um identificador partilhado ficam assinaladas, sem fusão automática. Contactos, documentos de identidade e NUIT não são devolvidos pela consulta.
|
||||
|
||||
Consulta interna por localização canónica (incluindo descendentes), sector CAE e actividade; resultados paginados conservam fonte, folha/linha, hash e data original. O sector provém da hierarquia SELF_ID do CAE fornecido, sem inventar correspondência com os sectores de navegação. A selecção permite comparar possível concorrência, complementaridade, parceiro ou relação por determinar, com fundamento e incertezas. O servidor recupera a origem, impede reclassificar a evidência interna como pública e conserva-a no rascunho e no dossier privado. Não transforma a lista em anúncios, promotores, clientes confirmados ou publicação de directório.
|
||||
|
||||
**Gurué:** o operador QA sintético 1021 consultou 410 observações distintas na primeira demonstração (435 após o complemento de registos sem denominação), comparou dois registos reais (serviços agrícolas CAE 01610 e comércio de plantas/sementes/fertilizantes CAE 47732) com a hipótese já preparada de apoio à colheita e qualidade da folha verde. A oferta possível exige verificar sobreposição e colaboração, sem presumir mercado vazio. Rascunho 402 passa de v1 a v2, mantendo integralmente as cinco evidências anteriores, a ideia e o histórico. O mesmo dossier `883c8ec3-59a3-4074-b91c-b7d4a5d88ff0` passa de contexto1 a2 por pré-visualização e confirmação idempotente, preservando título e campos deliberados. A necessidade permanece **hipótese**, sem procura, falta de oferta ou viabilidade confirmadas. Gravação, recarregamento, acesso privado e resultado vazio como **informação insuficiente** demonstrados no navegador. Isto é prova técnica, sem substituir observação/aceitação da equipa.
|
||||
|
||||
**Cobertura e pendências:** 205.608 observações sem correspondência territorial canónica única (incluindo 19 ambiguidades), 336 apenas com província e 82.864 sem designação de actividade CAE correspondente. Mantêm-se os rótulos originais e a incerteza; não há atribuição territorial aproximada nem preenchimento presumido. O cadastro de alojamento não é um censo exaustivo de hotéis/restaurantes. Situação operacional actual, capacidade, preços, procura e compromissos continuam por demonstrar. O incremento nacional é parcial; não encerra os requisitos amplos de mercado, clusters ou liquidação anual. **L8 permanece parcial e inalterado.**
|
||||
|
||||
Sete testes dirigidos/62 asserções, TypeScript e compilação QA passaram. As duas tabelas internas têm RLS forçada; teste com identidade sem privilégios confirma ocultação sem contexto de actor e leitura com contexto autorizado. Conferidos os hashes de todas as linhas anteriores nos sete conjuntos do inventário: só o rascunho402 e o mesmo dossier sofreram o delta intencional; Songo401, projectos, territórios, sectores, documentos e cadeias foram conservados. Provas fiscais aceites reutilizadas, sem repetir as suas suites.
|
||||
|
||||
Actualizados os cinco requisitos existentes `RF-CLU-003`, `MASTER-471012ab-0010`, `M2-d0c06a75-0147`, `M2-d0c06a75-0151` e `MASTER-1f0a4cde-0002`, preservando o restante estado e pendências. Nenhuma matriz ou backlog novo. A entrega activa futura mantém conferência de capacidade/compatibilidade com main, backup/restauro fresco, operação autenticada e delta instalado. Relatório: `docs/investmoz/unification/proactive-existing-supply-qa-20261028.json`.
|
||||
|
||||
Complemento final: as 18.423 linhas empresariais sem denominação social também foram conservadas, com essa lacuna explícita e sem reutilizar o nome pessoal do requerente. As 343.041 observações da primeira fotografia permaneceram idênticas; extensão e repetição idempotente atingem 361.464 sem substituir registos, alterar as comparações ou rever as provas aceites. Os exemplares e as fotografias anteriores foram preservados.
|
||||
|
||||
|
||||
## 28/10/2026 — base comparativa instalada pelo Git; conferência visual autenticada pendente
|
||||
|
||||
Instalada e servida a revisão **`6d147693`**, que inclui o candidato aceite `a022ed98`, por fast-forward normal em `main`, sem força. A revisão569a1cd7 e os backups anteriores foram preservados. O carregamento privado foi executado em transacção autenticada depois de backup novo, restauro isolado e ensaio idempotente; o instalador Git, as suas guardas e a CI habitual foram conservados. O contrato recebeu apenas o hash da migração nova, mantendo os90 anteriores.
|
||||
|
||||
**361.464 observações administrativas não são361.464 empresas únicas.** As147.165 repetições foram conservadas, mas excluídas das contagens/resultados; as214.299 projecções administrativas distintas também não equivalem a empresas únicas. Em Gurué, o serviço instalado recupera435 observações distintas e conserva237 repetições fora dessa contagem. A API também recusa gravar uma repetição como outra comparação: apenas esse delta recebeu teste dirigido1/2asserções. As7provas/62asserções, tipos, compilação e percurso QA aceites foram reutilizados.
|
||||
|
||||
A aplicação, com a sua conta real e um actor existente que satisfaz as autorizações anteriores, consultou Gurué e conservou fonte/linha/data/hash. O resultado vazio foi «Informação insuficiente nesta base e nestes filtros.» Sem actor/contexto, a RLS devolveu0; acesso HTTP anónimo recusado401. Isto é conferência técnica do consumidor instalado, **não uma nova aceitação da equipa no navegador**. A conferência visual autenticada permanece pendente por bloqueio do mecanismo Windows de confirmação segura do URL; não pedir nova aceitação das provas QA nem confundir essa pendência com perda da funcionalidade entregue.
|
||||
|
||||
Backup fresco de174tabelas públicas e369ficheiros/configuração; restauro sem rede/55441 e os174checksums passaram. As duas tabelas privadas também foram carregadas, repetidas e restauradas no ensaio. Medidos344.399.872bytes para as novas relações;capacidade conferida antes da instalação. No activo, as linhas anteriores foram preservadas e os369ficheiros/configuração ficaram idênticos; o delta esperado é de2tabelas privadas e1registo de migração. Cache queue:restart e2sessões anónimas de conferência foram reconciliados separadamente. Foi criado também backup da base privada já instalada, preservando GRANT/RLS. Não se criaram utilizadores, organizações, oportunidades ou dossiers QA no activo, nem se publicaram as listas.
|
||||
|
||||
CI habitual do instalador: PostgreSQL1/3106asserções, Unit59/317, frontend1146aprovados/0falhas, tipos e build31,30s. O script do instalador e as configurações/ficheiros anteriores mantiveram-se; apenas leitura nas duas tabelas novas foi concedida à conta de aplicação existente. Não houve alteração de papéis de funcionários.
|
||||
|
||||
**Pendências conservadas:** cobertura nacional parcial,205.608observações sem território canónico único,336só com província,82.864sem designação CAE,113.880com divergências de denominação/identificador e18.423sem denominação social, todas preservadas. A ausência de registos é informação insuficiente. A comparação fundamenta uma hipótese, sem comprovar procura ou viabilidade. O caso/dossier QA anterior mantém-se em QA e não foi transplantado para o activo. **L8 permanece parcial e inalterado.** A actualização continua nos cinco requisitos existentes, sem nova matriz ou backlog. Recibo:`docs/investmoz/unification/proactive-supply-git-delivery-20261028.json`.
|
||||
|
||||
|
||||
## 28/10/2026 — correcção do bloqueio da conferência visual
|
||||
|
||||
`6d147693` está instalado, incluindo a base interna. A única pendência da conferência desta entrega é a validação visual autenticada: **Gurué → consultar oferta → comparar ideia → guardar → recuperar no mesmo dossier**, conservando fonte, data e incertezas. As435 observações de Gurué não são435 empresas únicas. Listas internas, cobertura nacional parcial e hipótese de investimento mantêm-se.
|
||||
|
||||
O erro actual, novamente observado, é **“The browser access host is unavailable”**: indisponibilidade do host do conector, distinta de deixar uma aba aberta. A orientação anterior para apenas manter a aba aberta era insuficiente. Restabelecer o conector por mecanismo suportado ou realizar a conferência pela equipa. Não reinstalar nem repetir as provas técnicas aceites; não declarar conferência visual concluída sem o seu resultado. O recibo remoto original e o historial do diagnóstico anterior ficam preservados; esta correcção é documental.
|
||||
|
||||
@@ -0,0 +1,793 @@
|
||||
{
|
||||
"date": "2026-10-28",
|
||||
"status": "installed_and_verified",
|
||||
"served_commit": "569a1cd750f3d2a0b87f130e992e73e070bbd1ab",
|
||||
"previous_commit": "838ad941a24ae76ec4bf91ccc3f92012668fe8cf",
|
||||
"accepted_QA": {
|
||||
"economic": "aae67c169dc0d14f95cf2ee6fa28e5ff05341dcf",
|
||||
"carry": "d134a92d169f9159a692ed158e3a051889f7ab3a"
|
||||
},
|
||||
"scope": {
|
||||
"economic": {
|
||||
"current_year": "2026",
|
||||
"current_period": "calendar year, resident general",
|
||||
"currency": "documented MZN; exact fractions without fiscal rounding",
|
||||
"profit_kind": "ordinary distributions art64(1–2)",
|
||||
"eligibility": "same-country seat/effective management; payer subject to IRPC and nonexempt; nonapplication of40(1) documented, including2009 version",
|
||||
"nominal_credit": "60 percent of documented IRPC corresponding to included distributed profits; no assumed rate on net dividend",
|
||||
"gross_up": "expected nominal60 percent reconciled with53(1)a amount already included in current L6; does not overwrite or recalculate source base",
|
||||
"proportional_ceiling": "case-specific documented input tied to current assessment; not automatically derived",
|
||||
"prior_use": "actual documented economic credit use, distinct from simulation history and prior source-tax attributions",
|
||||
"order": [
|
||||
"economic credit up to collecta",
|
||||
"international credit current/prior according to existing explicit selection",
|
||||
"PEC current/prior according to existing explicit selection",
|
||||
"own withholdings and PPC"
|
||||
],
|
||||
"refund": "no economic-credit refund or carry created; PPC can create simulated recoverable balance"
|
||||
},
|
||||
"carry": {
|
||||
"year": "2026",
|
||||
"subject": "same subject, resident general",
|
||||
"period": "annual ordinary origins; calendar-year current close",
|
||||
"currency": "documented MZN; exact foreign-credit fractions supported",
|
||||
"international": "eligible origin credit minus origin deduction and documented actual later uses; five subsequent exercises, art65(3)",
|
||||
"pec": "actual PEC eligible origin minus origin deduction and actual later uses; three subsequent exercises, art66(1–2)",
|
||||
"allocation": "explicit documented selection between current and prior exercises, confirmed against current assessment; no presumed FIFO or automatic reduction",
|
||||
"expiry": "ordinary window ended excludes current deduction but keeps source and arithmetic historical remainder",
|
||||
"origin_rules": "origin assessment, inclusion/limits/insufficient collection and applicable period documented; exceptional/transitional rules outside scope"
|
||||
},
|
||||
"grouped_example": "1408-12economic-8currentInternational-50priorInternational-200currentPEC-100priorPEC-500PPC=538MZN payable simulated",
|
||||
"same_parcel_nonduplication": {
|
||||
"stable_identity": "issuer/document/parcel; distribution identity also independent of source-tax document,date and row id",
|
||||
"economic_vs_international": "same physical tax parcel cannot fund both credit natures",
|
||||
"same_paper_different_tax": "supported distinct parcels can coexist; old proof without a parcel overlaps the whole paper until reconciled",
|
||||
"capacity": "same proof parcel amounts and prior attributions must agree and total allocation remains within available amount across snapshot contexts",
|
||||
"L5": "existing withholding source/allocation checks preserved; parcel relabelling cannot turn repeated L5 use into new tax",
|
||||
"partial_information": "saved and recovered as indeterminate, never reset or silently assumed absent",
|
||||
"global_official_account_reconciliation": false
|
||||
}
|
||||
},
|
||||
"compatibility": {
|
||||
"main_normal_fast_forward": true,
|
||||
"force_used": false,
|
||||
"runtime_files_identical_to_accepted_QA": true,
|
||||
"runtime_delta": [
|
||||
{
|
||||
"file": "resources/js/components/fiscal-proof-parcel.tsx",
|
||||
"before_sha256": null,
|
||||
"after_sha256": "9712108707e20c60ed625b296aa8cec92a6ce1b7f6af5c51355d52b6b08a4f7a"
|
||||
},
|
||||
{
|
||||
"file": "resources/js/components/simulation-irpc-annual.tsx",
|
||||
"before_sha256": "307725306df321fc89caf3d1a9efcf7463bb93d9d515d44347f8e14d4fd3136b",
|
||||
"after_sha256": "75176ae721b88f2686987be5b499bf8909fc1d958a497bfdffde8ae22fca9586"
|
||||
},
|
||||
{
|
||||
"file": "resources/js/components/simulation-irpc-carry.tsx",
|
||||
"before_sha256": null,
|
||||
"after_sha256": "020e2c26643b8b0dcf31d673db55217a7285b2187bdb8abf020bdfc6cc78e868"
|
||||
},
|
||||
{
|
||||
"file": "resources/js/components/simulation-irpc-economic.tsx",
|
||||
"before_sha256": null,
|
||||
"after_sha256": "57a019d4359f0ee119d9d52202104ca846518b1bfba2bfb8fddb769c168ecd44"
|
||||
},
|
||||
{
|
||||
"file": "resources/js/components/simulation-irpc-international.tsx",
|
||||
"before_sha256": "6a9f796cc1e678823ed1dc14fbc28ca0e270752b2650518de239424dc75d167c",
|
||||
"after_sha256": "64b6077bfb673b918476f923a045d39cfde73c07aca2ae066772a24bef9a396d"
|
||||
},
|
||||
{
|
||||
"file": "resources/js/components/simulation-irpc-pec.tsx",
|
||||
"before_sha256": "c3f0ea9ff9389cf856ecda3894c7a1486f44193eb893c341ef10c67f888a2fc6",
|
||||
"after_sha256": "d0bba7015521d9d143c819e4203e03cf2f6d076093f5cd29deb96c3a11fc6aa1"
|
||||
},
|
||||
{
|
||||
"file": "resources/js/lib/fiscal-proof-parcel.ts",
|
||||
"before_sha256": null,
|
||||
"after_sha256": "9eb500a7be2e05f39203c5cebf86cd3c64bbace7a615b9b62161f34dbe4a63da"
|
||||
},
|
||||
{
|
||||
"file": "resources/js/lib/irpc-annual-settlement.ts",
|
||||
"before_sha256": "47d4167d0f80c649e6d89a586e0d206d2d2a12b8553a7e98be362ea56203a540",
|
||||
"after_sha256": "ac2896dca3002b27bf5f6bfd2e670295dcfaa3dc24f77b8e52469ed551cb1877"
|
||||
},
|
||||
{
|
||||
"file": "resources/js/lib/irpc-credit-carry.ts",
|
||||
"before_sha256": null,
|
||||
"after_sha256": "5e88833842304f8e8e580916e2be657c33ad1fc53b7d544fa453b51ea27004e1"
|
||||
},
|
||||
{
|
||||
"file": "resources/js/lib/irpc-economic-credit.ts",
|
||||
"before_sha256": null,
|
||||
"after_sha256": "fa19b289cba504d3636d76b3232694327e63674c907305425c2043d847046af1"
|
||||
},
|
||||
{
|
||||
"file": "resources/js/lib/irpc-international-credit.ts",
|
||||
"before_sha256": "40b46fbaaff6071ce6a35fd82de586158bf1f58fae8f46bcf6e4ddbbf84287f1",
|
||||
"after_sha256": "4fc68f1fbe0cb82ca01a3c30dcb505adb60eced1de92213260b9a5ea1711fe7b"
|
||||
},
|
||||
{
|
||||
"file": "resources/js/lib/irpc-pec.ts",
|
||||
"before_sha256": "eb40b0601b6a1a69a6e81ad210589cc8744d74c6ef65e12132de13a5fcd6a90e",
|
||||
"after_sha256": "9715ba106fd44b9ef16a55f2e85c2169ad09413a75167990e78ee644556fa381"
|
||||
}
|
||||
],
|
||||
"source_files_before": 1005,
|
||||
"source_files_after": 1011,
|
||||
"all_other_compared_sources_preserved": true,
|
||||
"schema_configuration_routes_permissions_preserved": true,
|
||||
"previous_accepted_receipts_and_frozen_history_preserved": true
|
||||
},
|
||||
"accepted_QA_reused": {
|
||||
"economic_new_frontend_tests": 20,
|
||||
"previous_affected_tests": 65,
|
||||
"frontend_pass": 85,
|
||||
"frontend_fail": 0,
|
||||
"API_tests": 1,
|
||||
"API_assertions": 210,
|
||||
"API_cases": 11,
|
||||
"database": "proactive_investment_20261027_test",
|
||||
"loopback_port": 55441,
|
||||
"API_transactions_rolled_back": true,
|
||||
"recovered_cases_recalculated_with_HTML_and_dossier": 11,
|
||||
"frozen_histories_preserved": true,
|
||||
"typescript": "passed",
|
||||
"build": "isolated QA, subprocess exit0, final component hashes recorded",
|
||||
"PHP_formatting": "pint passed",
|
||||
"new_browser_manual_proof": false,
|
||||
"team_usability_evaluation": "unchanged; awaiting team feedback"
|
||||
},
|
||||
"backup": {
|
||||
"stage": "/var/backups/investmoz-irpc-l8-grouped-20261028/backup",
|
||||
"created_at": "2026-10-28T12:38:25Z",
|
||||
"target": {
|
||||
"database": "teste_investidor",
|
||||
"port": 5432
|
||||
},
|
||||
"tables": 174,
|
||||
"files_verified": 369,
|
||||
"dump_sha256": "007cd1851d5bd51a54265ab99a342d6a312de06ef642fc42b38b7a5e771d4d9a",
|
||||
"files_sha256": "cd81cc9421e1d324648c706d8e56cbb6ad8766eb13e9a1a9cfda87814388b51f",
|
||||
"restore_verified": false
|
||||
},
|
||||
"restore": {
|
||||
"restore_verified": true,
|
||||
"tables_verified": 174,
|
||||
"container_network": "none",
|
||||
"database": "irpc_grouped_restore_20261028",
|
||||
"port": 55441,
|
||||
"source_dump_sha256": "007cd1851d5bd51a54265ab99a342d6a312de06ef642fc42b38b7a5e771d4d9a",
|
||||
"active_database_writes": 0
|
||||
},
|
||||
"capacity_before": {
|
||||
"checked_at_UTC": "2026-10-28T12:38:00.155432+00:00",
|
||||
"installed": {
|
||||
"status": "installed",
|
||||
"commit": "838ad941a24ae76ec4bf91ccc3f92012668fe8cf",
|
||||
"release": "/var/www/investmoz-homologacao/releases/838ad941a24ae76ec4bf91ccc3f92012668fe8cf-ca7817df",
|
||||
"previous_release": "/var/www/investmoz-homologacao/releases/e182d1d79dc945f35d2079c6b8ca2ac93048cec3-e2a185b3",
|
||||
"database_migrations": 0,
|
||||
"content_imports": 0
|
||||
},
|
||||
"current_link": "/var/www/investmoz-homologacao/releases/838ad941a24ae76ec4bf91ccc3f92012668fe8cf-ca7817df",
|
||||
"release_revision": {
|
||||
"commit": "838ad941a24ae76ec4bf91ccc3f92012668fe8cf",
|
||||
"branch": "main",
|
||||
"built_at": "2026-10-27T13:20:18.055286+00:00",
|
||||
"manifest_sha256": "b873c14f3e0e43435fa8f67fa9a65ff8978123cd8792c5f7c2e0cf6f8f84a3b2"
|
||||
},
|
||||
"service": "MainPID=0\nExecMainStatus=0\nActiveState=inactive\nSubState=dead\n",
|
||||
"volumes": [
|
||||
{
|
||||
"path": "/var/www/investmoz-homologacao/releases",
|
||||
"mount": "/dev/md2 / ext4",
|
||||
"device": 2306,
|
||||
"available_bytes": 88808570880,
|
||||
"available_inodes": 23523855,
|
||||
"total_inodes": 29089792
|
||||
},
|
||||
{
|
||||
"path": "/var/lib/investmoz-deploy",
|
||||
"mount": "/dev/md2 / ext4",
|
||||
"device": 2306,
|
||||
"available_bytes": 88808570880,
|
||||
"available_inodes": 23523855,
|
||||
"total_inodes": 29089792
|
||||
},
|
||||
{
|
||||
"path": "/var/backups",
|
||||
"mount": "/dev/md2 / ext4",
|
||||
"device": 2306,
|
||||
"available_bytes": 88808570880,
|
||||
"available_inodes": 23523855,
|
||||
"total_inodes": 29089792
|
||||
},
|
||||
{
|
||||
"path": "/tmp",
|
||||
"mount": "/dev/md2 / ext4",
|
||||
"device": 2306,
|
||||
"available_bytes": 88808570880,
|
||||
"available_inodes": 23523855,
|
||||
"total_inodes": 29089792
|
||||
},
|
||||
{
|
||||
"path": "/var/lib/docker",
|
||||
"mount": "/dev/md2 / ext4",
|
||||
"device": 2306,
|
||||
"available_bytes": 88808570880,
|
||||
"available_inodes": 23523855,
|
||||
"total_inodes": 29089792
|
||||
}
|
||||
],
|
||||
"free_bytes": 88808570880,
|
||||
"existing_pipeline_minimum_bytes": 5368709120,
|
||||
"observed_previous_backup_bytes": 175921269,
|
||||
"same_partition_release_and_backup": true,
|
||||
"space_guard_satisfied": true,
|
||||
"current_release_entry_reference": 31327,
|
||||
"inodes_available_vs_reference": true,
|
||||
"inode_note": "Observed current release footprint for scale;no new arbitrary inode threshold. Capacity rechecked after fresh backup.",
|
||||
"pipeline_sha256": "adec1c17654f3a8658b83b134f1e75f95391b235738552721906c3236e388604",
|
||||
"active_writes": 0
|
||||
},
|
||||
"capacity_after_backup": {
|
||||
"at": "2026-10-28T12:40:09.841070+00:00",
|
||||
"baseline": "838ad941a24ae76ec4bf91ccc3f92012668fe8cf",
|
||||
"capacity_after_backup_satisfied": true,
|
||||
"volumes": [
|
||||
{
|
||||
"path": "/var/www/investmoz-homologacao/releases",
|
||||
"free_bytes": 88629272576,
|
||||
"free_inodes": 23523836
|
||||
},
|
||||
{
|
||||
"path": "/var/lib/investmoz-deploy",
|
||||
"free_bytes": 88629272576,
|
||||
"free_inodes": 23523836
|
||||
},
|
||||
{
|
||||
"path": "/var/backups",
|
||||
"free_bytes": 88629272576,
|
||||
"free_inodes": 23523836
|
||||
},
|
||||
{
|
||||
"path": "/tmp",
|
||||
"free_bytes": 88629272576,
|
||||
"free_inodes": 23523836
|
||||
},
|
||||
{
|
||||
"path": "/var/lib/docker",
|
||||
"free_bytes": 88629272576,
|
||||
"free_inodes": 23523836
|
||||
}
|
||||
],
|
||||
"backup_age_seconds": 104.841034,
|
||||
"backup_dump_sha256": "007cd1851d5bd51a54265ab99a342d6a312de06ef642fc42b38b7a5e771d4d9a",
|
||||
"restore_verified": true,
|
||||
"tables_verified": 174,
|
||||
"files_verified": 369,
|
||||
"backup_paths": {
|
||||
"dump": "/var/backups/investmoz-irpc-l8-grouped-20261028/backup/database.dump",
|
||||
"files": "/var/backups/investmoz-irpc-l8-grouped-20261028/backup/files-before.tar.gz"
|
||||
},
|
||||
"active_writes": 0
|
||||
},
|
||||
"schema_permissions": {
|
||||
"schema_sha256": "5045b3e51d2185377aa3d9ac7fa07a44fad3ec620cdfa83ffe31c8884ee1601d",
|
||||
"roles_grants_sha256": "5f0c554373e879fa9db81c3e746886a40a3c47875e6f41c61cfc0b3aee2dfd2b",
|
||||
"filesystem_permissions": {
|
||||
".env": {
|
||||
"mode": 416,
|
||||
"uid": 1001,
|
||||
"gid": 33
|
||||
},
|
||||
"storage": {
|
||||
"mode": 1533,
|
||||
"uid": 1001,
|
||||
"gid": 33
|
||||
},
|
||||
"storage/app": {
|
||||
"mode": 1533,
|
||||
"uid": 1001,
|
||||
"gid": 33
|
||||
},
|
||||
"bootstrap/cache": {
|
||||
"mode": 1528,
|
||||
"uid": 1001,
|
||||
"gid": 33
|
||||
}
|
||||
},
|
||||
"comparison_identity": "postgres;catalogue read only;no passwords collected",
|
||||
"unchanged": true
|
||||
},
|
||||
"preservation": {
|
||||
"checked_at": "2026-10-28T12:47:37.401210+00:00",
|
||||
"installed": "569a1cd750f3d2a0b87f130e992e73e070bbd1ab",
|
||||
"business_tables_unchanged": 172,
|
||||
"total_tables": 174,
|
||||
"operational_tables_to_reconcile": {
|
||||
"cache": {
|
||||
"before": {
|
||||
"rows": 110,
|
||||
"checksum": "59099e8b28be3b1cd3b30a0be1b22519"
|
||||
},
|
||||
"after": {
|
||||
"rows": 110,
|
||||
"checksum": "832a86e7c4ccd6c9b5d725a45a63f2cb"
|
||||
}
|
||||
},
|
||||
"sessions": {
|
||||
"before": {
|
||||
"rows": 2,
|
||||
"checksum": "09d77c0790973a0ed80f9dbb177f186c"
|
||||
},
|
||||
"after": {
|
||||
"rows": 4,
|
||||
"checksum": "570c460243c35d5c20b2e700077c15b3"
|
||||
}
|
||||
}
|
||||
},
|
||||
"persistent_files_unchanged": 369,
|
||||
"original_configuration_preserved": true,
|
||||
"synthetic_QA_storage_not_served": true,
|
||||
"QA_storage_saved_outside_active": "/var/lib/investmoz-deploy/qa-runtime/569a1cd750f3d2a0b87f130e992e73e070bbd1ab-53a57d6f",
|
||||
"fixture_or_content_imports": 0,
|
||||
"migrations": 0,
|
||||
"comparison_identity": "postgres;UTC;consistent before snapshot and after read-only transaction",
|
||||
"active_database_writes": 0
|
||||
},
|
||||
"operational_delta": {
|
||||
"status": "operational_delta_reconciled",
|
||||
"comparison_identity": "postgres before/after;isolated restored baseline;no RLS visibility inference",
|
||||
"cache": "queue:restart;requestthrottle;identical home-sector cached value with refreshed expiry(HomeController remember10min);no deletedcache",
|
||||
"sessions": "anonymous sessions from delivery user-agent or exactUA/time correlated with nginx public request;deleted sessions individually expired beyond configured lifetime,normal database session garbage collection(config/session lottery2/100)",
|
||||
"delta": {
|
||||
"cache": {
|
||||
"before_rows": 110,
|
||||
"after_rows": 110,
|
||||
"changes": [
|
||||
{
|
||||
"key_sha256": "7f7dd83f47f5bf404d55e6e9f1367948721579970b195680645e356031a070ff",
|
||||
"kind": "updated",
|
||||
"fields": [
|
||||
"value",
|
||||
"expiration"
|
||||
],
|
||||
"key_pattern": "investemoz-cache-illuminate:queue:restart",
|
||||
"is_queue_restart_key": true,
|
||||
"expiration_unchanged": false,
|
||||
"throttle_timer_pair": false,
|
||||
"same_value_home_cache_expiry_refresh": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"sessions": {
|
||||
"before_rows": 2,
|
||||
"after_rows": 4,
|
||||
"changes": [
|
||||
{
|
||||
"key_sha256": "61ee61858f50dc6bb6b3c42a15ea2a158e29a8de51071570a48e245072f35647",
|
||||
"kind": "added",
|
||||
"fields": [
|
||||
"id",
|
||||
"ip_address",
|
||||
"user_agent",
|
||||
"payload",
|
||||
"last_activity"
|
||||
],
|
||||
"anonymous_user": true,
|
||||
"delivery_user_agent": true,
|
||||
"expired_before_comparison": false,
|
||||
"session_lifetime_minutes": 120,
|
||||
"anonymous_public_request_logged": true
|
||||
},
|
||||
{
|
||||
"key_sha256": "93d2f8d1f352499a4b04fe6ffe15ecd52738871794282140a78bfef2ad6ba80b",
|
||||
"kind": "added",
|
||||
"fields": [
|
||||
"id",
|
||||
"ip_address",
|
||||
"user_agent",
|
||||
"payload",
|
||||
"last_activity"
|
||||
],
|
||||
"anonymous_user": true,
|
||||
"delivery_user_agent": true,
|
||||
"expired_before_comparison": false,
|
||||
"session_lifetime_minutes": 120,
|
||||
"anonymous_public_request_logged": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"business_tables_to_confirm_separately": 172,
|
||||
"active_data_writes_by_comparison": 0,
|
||||
"restore_network": "none"
|
||||
},
|
||||
"installer": {
|
||||
"service": "MainPID=0\nExecMainStatus=0\nActiveState=inactive\nSubState=dead\n",
|
||||
"installed": {
|
||||
"status": "installed",
|
||||
"commit": "569a1cd750f3d2a0b87f130e992e73e070bbd1ab",
|
||||
"release": "/var/www/investmoz-homologacao/releases/569a1cd750f3d2a0b87f130e992e73e070bbd1ab-53a57d6f",
|
||||
"previous_release": "/var/www/investmoz-homologacao/releases/838ad941a24ae76ec4bf91ccc3f92012668fe8cf-ca7817df",
|
||||
"database_migrations": 0,
|
||||
"content_imports": 0
|
||||
},
|
||||
"latest_report": {
|
||||
"status": "installed",
|
||||
"commit": "569a1cd750f3d2a0b87f130e992e73e070bbd1ab",
|
||||
"release": "/var/www/investmoz-homologacao/releases/569a1cd750f3d2a0b87f130e992e73e070bbd1ab-53a57d6f",
|
||||
"previous_release": "/var/www/investmoz-homologacao/releases/838ad941a24ae76ec4bf91ccc3f92012668fe8cf-ca7817df",
|
||||
"database_migrations": 0,
|
||||
"content_imports": 0,
|
||||
"requested_commits": [
|
||||
"569a1cd750f3d2a0b87f130e992e73e070bbd1ab"
|
||||
],
|
||||
"finished_at": "2026-10-28T12:44:28.780095+00:00"
|
||||
},
|
||||
"log_file": "1793191252185211711.log",
|
||||
"mandatory_summaries": [
|
||||
"OK (1 test, 3106 assertions)",
|
||||
"✓ built in 30.82s",
|
||||
"OK (59 tests, 317 assertions)",
|
||||
"# tests 1146",
|
||||
"# pass 1146",
|
||||
"# fail 0"
|
||||
]
|
||||
},
|
||||
"public_delta": {
|
||||
"phase": "after",
|
||||
"revision": {
|
||||
"commit": "569a1cd750f3d2a0b87f130e992e73e070bbd1ab",
|
||||
"branch": "main",
|
||||
"built_at": "2026-10-28T12:44:25.642859+00:00",
|
||||
"manifest_sha256": "9cb557f62c0dbe49ec87d7ccb532f6c7dc29fc980658ffc51ff024755c26f186"
|
||||
},
|
||||
"manifest_verified": true,
|
||||
"simulator_HTTP": 200,
|
||||
"simulator_component": "portal/services",
|
||||
"active_QA_records_created": false,
|
||||
"compiled_markers": {
|
||||
"IRPC-credit-carry/1": [
|
||||
"assets/project-dossier-BUI-Tqnq.js"
|
||||
],
|
||||
"IRPC-economic-credit/1": [
|
||||
"assets/project-dossier-BUI-Tqnq.js"
|
||||
],
|
||||
"Preparar saldos anteriores": [
|
||||
"assets/services-DOMV1YET.js"
|
||||
],
|
||||
"Preparar crédito económico": [
|
||||
"assets/services-DOMV1YET.js"
|
||||
],
|
||||
"Crédito económico — antes do internacional": [
|
||||
"assets/project-dossier-BUI-Tqnq.js"
|
||||
],
|
||||
"Confirmar afectação à colecta seleccionada": [
|
||||
"assets/services-DOMV1YET.js"
|
||||
],
|
||||
"IRPC-international-credit/1": [
|
||||
"assets/project-dossier-BUI-Tqnq.js"
|
||||
],
|
||||
"IRPC-PEC/1": [
|
||||
"assets/project-dossier-BUI-Tqnq.js"
|
||||
]
|
||||
}
|
||||
},
|
||||
"remote_backup": "/var/backups/investmoz-irpc-l8-grouped-20261028/backup",
|
||||
"remote_receipt": "/var/backups/investmoz-irpc-l8-grouped-20261028/installed-receipt.json",
|
||||
"remaining": {
|
||||
"economic": [
|
||||
"automatic inclusion/correction of distributed profits and53 addition in L6",
|
||||
"automatic proportional ceiling derivation and complete40(1) exclusion treatment at base stage",
|
||||
"liquidation64(3),associations64(4),special-period and other-currency cases"
|
||||
],
|
||||
"other_L8": [
|
||||
"specific benefit credits and their qualification,cumulation and limits",
|
||||
"automatic international national-fraction and case-specific treaty matching",
|
||||
"PPC/PEC obligation/calendar,interest/penalties and payment guides with applicable edition",
|
||||
"autonomous taxation,special periods,other currencies and M20/M22 transport/submission",
|
||||
"exceptional/transitional interyear cases beyond accepted ordinary carry scope"
|
||||
]
|
||||
},
|
||||
"L8_complete": false,
|
||||
"limits": [
|
||||
"No global annual IRPC closure claimed",
|
||||
"No official tax-account balance consumed,submitted or refunded",
|
||||
"Economic credit limited to ordinary documented current-year case;source inclusion/gross-up and proportional quota not automatically derived",
|
||||
"Carry retains its documented ordinary-year scope;no exceptional/transitional rule inferred",
|
||||
"No new manual team usability acceptance claimed",
|
||||
"Songo/Gurue remain private;SGF TEMPO untouched;no active QA fixture/content imports"
|
||||
],
|
||||
"local_evidence_directory": "outputs/irpc-l8-grouped-delivery-20261028",
|
||||
"local_artifacts": [
|
||||
{
|
||||
"file": "authenticate.ps1",
|
||||
"sha256": "80109eafe4fe5c065debcb3ebf626638904f09c04450565b0be940a6372ddc1d",
|
||||
"bytes": 214
|
||||
},
|
||||
{
|
||||
"file": "backup.py",
|
||||
"sha256": "f1e02236d97caaed537249dfc8c7a002c18bd8cc067af5bd11f94cc4dc1ad027",
|
||||
"bytes": 5448
|
||||
},
|
||||
{
|
||||
"file": "capacity-after.py",
|
||||
"sha256": "9f1e2df54963469c670098b77392020f2719bb3f795cd04e5ecdad6f1fad1b84",
|
||||
"bytes": 1621
|
||||
},
|
||||
{
|
||||
"file": "compatibility-and-preservation-plan.json",
|
||||
"sha256": "00e4f0b33e5ea46007c74a2d8a7976fec363baf5cace55063b764fa3fceb822a",
|
||||
"bytes": 1160
|
||||
},
|
||||
{
|
||||
"file": "git-main-delivery.json",
|
||||
"sha256": "28c6503ad9229afdefd7f3466e7ab235417c1f2f7a7372ce269951db0657ef6f",
|
||||
"bytes": 242
|
||||
},
|
||||
{
|
||||
"file": "group-backup-20261028-response.json",
|
||||
"sha256": "9860fd3a9f4c1abb5e9298965a604cfa5efc0198ee8069aaf7ed9844b2bf00d6",
|
||||
"bytes": 606
|
||||
},
|
||||
{
|
||||
"file": "group-backup-20261028.json",
|
||||
"sha256": "f1e0096263b3b8a83401c3a83035e67e0c8f50e647030812811fe1b1700c6dea",
|
||||
"bytes": 437
|
||||
},
|
||||
{
|
||||
"file": "group-capacity-after-20261028-response.json",
|
||||
"sha256": "ce5b65cc2e31b9f938dac177d0420baaca6ad2df95e417c43e6fd79b474bc2d5",
|
||||
"bytes": 1394
|
||||
},
|
||||
{
|
||||
"file": "group-capacity-after-20261028.json",
|
||||
"sha256": "0d63c97cecd57683cd4046f3d327d489a966b67508e5f8c54c5829da85e8e869",
|
||||
"bytes": 1200
|
||||
},
|
||||
{
|
||||
"file": "group-operational-20261028-response.json",
|
||||
"sha256": "d7ca760c85a2deb24084167701aafb389bcd0a807c95bce5a6a9cddd4f6480ff",
|
||||
"bytes": 2483
|
||||
},
|
||||
{
|
||||
"file": "group-operational-20261028.json",
|
||||
"sha256": "dd0476edc7212ca0cc25646441c7f73aa5e406201563070e9adb3930751d21b6",
|
||||
"bytes": 2434
|
||||
},
|
||||
{
|
||||
"file": "group-preflight-20261028-response.json",
|
||||
"sha256": "b98a6d1970dc4f681e99c8500e863b24dc30bafe3d536d256183924911c2da6c",
|
||||
"bytes": 3029
|
||||
},
|
||||
{
|
||||
"file": "group-preflight-20261028.json",
|
||||
"sha256": "5f70b0af720beb1199c09cb21118a6d13f47e07ab55f4eeef3ae969ad1fcac14",
|
||||
"bytes": 2595
|
||||
},
|
||||
{
|
||||
"file": "group-preserve-20261028-response.json",
|
||||
"sha256": "9f96f52a494c5d29702cc9abd1ace94d314a8cda54281ec6ac1b810efc6b8c50",
|
||||
"bytes": 1324
|
||||
},
|
||||
{
|
||||
"file": "group-preserve-20261028.json",
|
||||
"sha256": "f655c23059156461f49200a419fafffdf0cecab62df99b5995bcab59eaf7e7de",
|
||||
"bytes": 1137
|
||||
},
|
||||
{
|
||||
"file": "group-progress-a1-20261028-response.json",
|
||||
"sha256": "b968fa1a79f043c1b2f9440e7e1828eea466c7b77e45c8f66dec47cc59202305",
|
||||
"bytes": 2715
|
||||
},
|
||||
{
|
||||
"file": "group-progress-a1-20261028.json",
|
||||
"sha256": "8f3cd7de2b9c10ea72a04783f2740301a96f641f07189515c386908aad7bf139",
|
||||
"bytes": 2294
|
||||
},
|
||||
{
|
||||
"file": "group-progress-a2-20261028-response.json",
|
||||
"sha256": "05432b187845dd4ea7ba3a966fea54023877c7bc0cb1c5721539f9591b8c174d",
|
||||
"bytes": 1567
|
||||
},
|
||||
{
|
||||
"file": "group-progress-a2-20261028.json",
|
||||
"sha256": "19ee1ec59ed4d91487a765c079cb042d84e70d8c6f575385425132832f4a8f88",
|
||||
"bytes": 1247
|
||||
},
|
||||
{
|
||||
"file": "group-restore-20261028-response.json",
|
||||
"sha256": "4ce2051902d1c00d04bef1f34fd88671c0be6b5b65228d4c13271bb541453a0f",
|
||||
"bytes": 427
|
||||
},
|
||||
{
|
||||
"file": "group-restore-20261028.json",
|
||||
"sha256": "aa3a90c80a037d4977988d7d9935a52f226b25ea414e54012446e528b69769c2",
|
||||
"bytes": 282
|
||||
},
|
||||
{
|
||||
"file": "group-schema-after-20261028-response.json",
|
||||
"sha256": "740062552f5a4747fc0151ab32956774f8cb8cef6be6fef116a9c8b0505db67c",
|
||||
"bytes": 772
|
||||
},
|
||||
{
|
||||
"file": "group-schema-after-20261028.json",
|
||||
"sha256": "265a0f0bb3b8e8232c8a8f2f72802664091ff8f9127e7a3868694f1373fc7b51",
|
||||
"bytes": 668
|
||||
},
|
||||
{
|
||||
"file": "group-schema-before-20261028-response.json",
|
||||
"sha256": "ba122cba99109784b22699391efc6359c53d17e44dbe4bb14184389589cb0d80",
|
||||
"bytes": 745
|
||||
},
|
||||
{
|
||||
"file": "group-schema-before-20261028.json",
|
||||
"sha256": "8c171cfaae6feef78fbd43f8046ffb62c1daad9a258d95e6df43d20ba4b335c4",
|
||||
"bytes": 646
|
||||
},
|
||||
{
|
||||
"file": "group-source-after-20261028-response.json",
|
||||
"sha256": "be446ca8152ea5d940a8650d38fc8d141ee94cebafab8406d7c33b9a97cb6b4f",
|
||||
"bytes": 284
|
||||
},
|
||||
{
|
||||
"file": "group-source-after-20261028.json",
|
||||
"sha256": "f9a457698c36c0e433ff5c10956cef3f4548534ae0a9d8718a03af2f3101360b",
|
||||
"bytes": 165
|
||||
},
|
||||
{
|
||||
"file": "group-source-before-20261028-response.json",
|
||||
"sha256": "e2f155845283962a0729a295856d0ba0b631530faa40074c317a57b90836e1b5",
|
||||
"bytes": 285
|
||||
},
|
||||
{
|
||||
"file": "group-source-before-20261028.json",
|
||||
"sha256": "3383b6c77419378eac7fb078dad3e57989a8def0965c29477f0a827e1972a67c",
|
||||
"bytes": 166
|
||||
},
|
||||
{
|
||||
"file": "group-status-a1-20261028-response.json",
|
||||
"sha256": "860ba2f40d709ff491113c54cd253926adb0b21e22179b02e978a388ef6e2bc9",
|
||||
"bytes": 1809
|
||||
},
|
||||
{
|
||||
"file": "group-status-a1-20261028.json",
|
||||
"sha256": "38c718738dad722e06069993894ad9eba2f61b946d6e2042cfbc33e6d1c6aa10",
|
||||
"bytes": 1442
|
||||
},
|
||||
{
|
||||
"file": "group-status-a2-20261028-response.json",
|
||||
"sha256": "2f6815214d9b0240cc298f78b7838fd2b7061333e4cfda1ecab2779a688be130",
|
||||
"bytes": 2117
|
||||
},
|
||||
{
|
||||
"file": "group-status-a2-20261028.json",
|
||||
"sha256": "328e6fc9e8ce7f488e83c9c7154472f76cacf08381ad48ea38af8c6139317b7f",
|
||||
"bytes": 1706
|
||||
},
|
||||
{
|
||||
"file": "group-status-a3-20261028-response.json",
|
||||
"sha256": "a9af2b8c3c53341a0dbc030ceaa984818af1710b9be1f13437026093edc9b9ea",
|
||||
"bytes": 2117
|
||||
},
|
||||
{
|
||||
"file": "group-status-a3-20261028.json",
|
||||
"sha256": "e79288f392bd00c31059031c653ba703a4546d853945f0859f426bbccb8f381d",
|
||||
"bytes": 1706
|
||||
},
|
||||
{
|
||||
"file": "group-status-final-20261028-response.json",
|
||||
"sha256": "1e54588459017ee079eae4759d07cf4629287f053df50679fcf4026052696a9c",
|
||||
"bytes": 2220
|
||||
},
|
||||
{
|
||||
"file": "group-status-final-20261028.json",
|
||||
"sha256": "dca34a8237f9d019d3f36976668dd8b4a86e492e3d29c3eac8294c0d5294edee",
|
||||
"bytes": 1795
|
||||
},
|
||||
{
|
||||
"file": "pipeline-progress.py",
|
||||
"sha256": "848be5b9c462802395e2944fb30487407d86668ff27640125885994acdd4c241",
|
||||
"bytes": 1006
|
||||
},
|
||||
{
|
||||
"file": "preflight.py",
|
||||
"sha256": "92113ce782cc6cce88e3146dcc34363ad21d888f83b26b4e5a0d26fbf60e640f",
|
||||
"bytes": 2338
|
||||
},
|
||||
{
|
||||
"file": "preparation.json",
|
||||
"sha256": "47007bfe846aa9e22dc723b320e4a580396e413a75bb846c44c9cfda60ad84eb",
|
||||
"bytes": 251405
|
||||
},
|
||||
{
|
||||
"file": "preserve-data.py",
|
||||
"sha256": "e56d27b726c5e710b9bd5721d729cc019d57011b2ea1e2699fee6685f1f1bfbd",
|
||||
"bytes": 2304
|
||||
},
|
||||
{
|
||||
"file": "public-after.json",
|
||||
"sha256": "db39a1bd518eddcc4b23ee1fa28ae7a06d51e289d549790cfb64b04100976b6b",
|
||||
"bytes": 1149
|
||||
},
|
||||
{
|
||||
"file": "public-before.json",
|
||||
"sha256": "05a748e8a0f08ba438026f4e53c2d8260586c93061748ad3428188afe86e9b2a",
|
||||
"bytes": 414
|
||||
},
|
||||
{
|
||||
"file": "reconcile-operational-delta.py",
|
||||
"sha256": "1ad5c3663364e59d54bfaff2a0686b43fe7d658f5445076f670fb9aa69c1321c",
|
||||
"bytes": 5336
|
||||
},
|
||||
{
|
||||
"file": "remote_delivery_inspection.py",
|
||||
"sha256": "7c95ba88a9d5a62a15ec2bf581fa62f5151e548014d106f081487889e56b2703",
|
||||
"bytes": 1947
|
||||
},
|
||||
{
|
||||
"file": "restore.py",
|
||||
"sha256": "dcb4952f56d2524f36bc4b24164f8041f99de8e98d461bd40bafbb5102c28349",
|
||||
"bytes": 2792
|
||||
},
|
||||
{
|
||||
"file": "rpc.py",
|
||||
"sha256": "9c9ebe126d345d20ab46a07469f41bac3e76dbfbcdb64ac32606796c9c0f9520",
|
||||
"bytes": 1650
|
||||
},
|
||||
{
|
||||
"file": "schema-permissions-after.py",
|
||||
"sha256": "44fd24feb568ca5b9488c923fbd3967756c0fbb2e6fee4016f6c0272c536cac1",
|
||||
"bytes": 1862
|
||||
},
|
||||
{
|
||||
"file": "schema-permissions-before.py",
|
||||
"sha256": "d17fbac06b88b189171da56ea2d4e501eae331a39bd78c14cbd3fc65d19e59c4",
|
||||
"bytes": 1705
|
||||
},
|
||||
{
|
||||
"file": "source-after-input.json",
|
||||
"sha256": "3716126155fcff9b74fb34c408b57bc7b9482e62ed1b3f29dfd994d1c894f11a",
|
||||
"bytes": 240716
|
||||
},
|
||||
{
|
||||
"file": "source-before-input.json",
|
||||
"sha256": "8ca115700b7df7db7b306d7a0ff1063a54716977465674a31d5d2cd588d8f2e2",
|
||||
"bytes": 240717
|
||||
},
|
||||
{
|
||||
"file": "source-check.py",
|
||||
"sha256": "1cec620043b013e6b6f79b9f67e4e5588117d91ee7dc0c93592fc6b8d6d0557f",
|
||||
"bytes": 676
|
||||
},
|
||||
{
|
||||
"file": "ssh-session.py",
|
||||
"sha256": "e81df6aae12e03b0faac33e03108ef6dcdc5c0d3598538c4db5bb4bb65e9f3f4",
|
||||
"bytes": 3084
|
||||
},
|
||||
{
|
||||
"file": "status.py",
|
||||
"sha256": "41d3a13211c4d4725178477aa48e10f27fe313f41a2b33e555f7ae04a8a14bdd",
|
||||
"bytes": 1204
|
||||
},
|
||||
{
|
||||
"file": "store-receipt.py",
|
||||
"sha256": "9c671b6019c7fd80df91c9af795b16a8a80c119a79877ad6cf57c8fea436ef9c",
|
||||
"bytes": 864
|
||||
},
|
||||
{
|
||||
"file": "update-existing-docs.py",
|
||||
"sha256": "e088143d2e3e24390b36c5a08dd3312537b2b3f5778633c8086fcbdde00b2b60",
|
||||
"bytes": 1478
|
||||
},
|
||||
{
|
||||
"file": "verify-public.py",
|
||||
"sha256": "f6b5ad42ead0f6a7f2ddcb6f16a648293ad5eba1001b591dc32f4ddfa4f68f00",
|
||||
"bytes": 2107
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
{
|
||||
"date": "2026-10-28",
|
||||
"status": "not_demonstrated",
|
||||
"audited_use": "companies,hotels and restaurants lists as private supply-comparison basis inside proactive journey",
|
||||
"Tempo_decision": "know existing supply by location/sector/activity;compare ideas for competition,complementarity and possible partners;identify evidenced needs and prepare opportunities;no absence-of-offer inference from absent records;no automatic public directory",
|
||||
"installed_runtime": "569a1cd750f3d2a0b87f130e992e73e070bbd1ab",
|
||||
"scope": {
|
||||
"destination": "teste-investidor.tempo.co.mz",
|
||||
"database": "teste_investidor",
|
||||
"catalogue_tables": 198,
|
||||
"schemas": {
|
||||
"public": 174,
|
||||
"skills_edu": 24
|
||||
},
|
||||
"identity": "postgres owner,read-only transactions;technical metadata inventory,not an authenticated team UI proof",
|
||||
"read_only": true,
|
||||
"RLS_note": "No permissions changed or RLS bypassed for a team operation. Owner inventory is explicitly separated from application/user visibility. Differences from earlier investemoz/QA environments are not declared deletion/loss.",
|
||||
"source_matching_limit": "plaintext metadata/reference matches only; absence does not establish missing encrypted or external records or absence of real market supply"
|
||||
},
|
||||
"source_inventory": {
|
||||
"accessible_files_in_two_source_roots": 13,
|
||||
"all_inventoried_before_selection": true,
|
||||
"known_originals": [
|
||||
{
|
||||
"alias": "companies.xlsx",
|
||||
"file_name": "EMPRESAS_LIST 2014-2022.xlsx",
|
||||
"sha256": "1ba4c4114b16b407e7746712d00d4f681246bbbc1b42351e96a31f7129acad30",
|
||||
"bytes": 26586267,
|
||||
"unchanged": true,
|
||||
"sheet_names": [
|
||||
"EMPRESAS 2014-2022"
|
||||
],
|
||||
"prior_nonempty_XML_rows": 148557,
|
||||
"row_count_is_not_unique_business_count": true,
|
||||
"period": "2014–2022 stated in file name"
|
||||
},
|
||||
{
|
||||
"alias": "licensed.xlsx",
|
||||
"file_name": "Empresas Licenciadas.xlsx",
|
||||
"sha256": "1b5087b464e39e88c42def39655c2391aaaf684b4c832d6a77ff274b98889f00",
|
||||
"bytes": 34940776,
|
||||
"unchanged": true,
|
||||
"sheet_names": [
|
||||
"EMPRESAS"
|
||||
],
|
||||
"prior_nonempty_XML_rows": 212632,
|
||||
"row_count_is_not_unique_business_count": true,
|
||||
"period": "varies by record;reference date not inferred from file modification time"
|
||||
},
|
||||
{
|
||||
"alias": "tourism.xlsx",
|
||||
"file_name": "CADASTRO DE EMPREENDIMENTOS TURISTICOS 3, 4 E 5 ESTRELAS 30.10.24 18,00h.xlsx",
|
||||
"sha256": "f02e086ed4973ef17234aa6cc54b98fbb7a6952ffb16ec4dc4f7d0c72af6400c",
|
||||
"bytes": 73955,
|
||||
"unchanged": true,
|
||||
"sheet_names": [
|
||||
"1. Alojamento",
|
||||
"Folha1"
|
||||
],
|
||||
"prior_nonempty_XML_rows": 460,
|
||||
"row_count_is_not_unique_business_count": true,
|
||||
"period": "30/10/2024 stated in file name;current operating status unconfirmed"
|
||||
},
|
||||
{
|
||||
"alias": "cae.xlsx",
|
||||
"file_name": "CAE eBAU.xlsx",
|
||||
"sha256": "83b4f1200988e99834c2ae3915346fbf237d128308554b67b5a080d4956bf506",
|
||||
"bytes": 75018,
|
||||
"unchanged": true,
|
||||
"sheet_names": [
|
||||
"CAE"
|
||||
],
|
||||
"prior_nonempty_XML_rows": null,
|
||||
"row_count_is_not_unique_business_count": true,
|
||||
"period": "code labels supplied;version/application require source correspondence"
|
||||
}
|
||||
],
|
||||
"duplicates_detected_by_bytes": [],
|
||||
"private_fields_exported": false,
|
||||
"tourism_scope": "workbook labelled accommodation;not treated as an exhaustive restaurant or hotel market census",
|
||||
"restaurant_scope": "separate restaurant dataset not identified in these two source roots;restaurant activities may exist within company registers;not a finding of absence in the market"
|
||||
},
|
||||
"findings": {
|
||||
"originals_available_and_unchanged": true,
|
||||
"loading_into_portal": "not demonstrated in audited destination;no exact known source hash matches among368persistent files;no exact known source references in audited metadata",
|
||||
"normalisation_and_classification": "source company workbooks have province/district/CAE and a supplied CAE code table;mapping of their entities to portal location/sector/activity not demonstrated",
|
||||
"journey_linkage": "not demonstrated;installed ProactiveInvestment.options only returns document links/groups/project-cluster-corridor-park-chain references;no list-backed offer query or company/hotel/restaurant relation consumer in that method",
|
||||
"manual_evidence_path": "already delivered;acceptance preserved. Manual source/locator/period/coverage/observed_supply fields do not establish imported source records or catalogue-backed comparison.",
|
||||
"active_counts_context_only": {
|
||||
"existing_businesses": 0,
|
||||
"organizations": 1,
|
||||
"documents": 60,
|
||||
"economic_activities": 11,
|
||||
"portal_resources": 59
|
||||
},
|
||||
"no_market_absence_inference": true,
|
||||
"no_loss_claim": true
|
||||
},
|
||||
"demonstration_attempt": {
|
||||
"kind": "read-only trace and canonical-location check,not a browser or team end-to-end proof",
|
||||
"location": {
|
||||
"name": "Gurué",
|
||||
"id": "87a8ba3d-b152-560c-a566-7db4531ad536",
|
||||
"type": "district"
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"step": 1,
|
||||
"action": "choose location",
|
||||
"observed": "canonical Gurué district exists;previous location-selection path acceptance reused;no new manual UI proof claimed",
|
||||
"status": "location_reference_available"
|
||||
},
|
||||
{
|
||||
"step": 2,
|
||||
"action": "consult existing supply from these lists",
|
||||
"observed": "no wired list-backed offer consumer or demonstrated imported/classified enterprise records for this location",
|
||||
"status": "not_demonstrated",
|
||||
"blocks_remaining_demo": true
|
||||
},
|
||||
{
|
||||
"step": 3,
|
||||
"action": "compare with an idea for competitors/complements/partners",
|
||||
"observed": "manual analysis fields exist;no reproducible offer-result set/source-row linkage demonstrated",
|
||||
"status": "not_demonstrated"
|
||||
},
|
||||
{
|
||||
"step": 4,
|
||||
"action": "identify a need with dates and uncertainties",
|
||||
"observed": "cannot infer unmet need from missing catalogue records;hypothesis and unknown coverage remain explicit",
|
||||
"status": "not_demonstrated_for_this_list_backed_flow"
|
||||
},
|
||||
{
|
||||
"step": 5,
|
||||
"action": "prepare opportunity retaining sources/period/coverage/uncertainty",
|
||||
"observed": "existing private preparation mechanism preserved;no new opportunity created to simulate completion of missing steps2–4",
|
||||
"status": "not_demonstrated_for_this_list_backed_flow"
|
||||
}
|
||||
],
|
||||
"end_to_end_criterion_passed": false
|
||||
},
|
||||
"preservation": {
|
||||
"proactive_delivery_preserved": true,
|
||||
"previous_manual_real_source_QA_pilots_preserved": true,
|
||||
"Songo_Gurue": "existing private QA hypotheses unchanged;their manual spreadsheet reading does not establish list integration",
|
||||
"L8_delivery_569a1cd7": "unchanged,remains partial",
|
||||
"application_code_changes": 0,
|
||||
"new_tests": 0,
|
||||
"new_imports_or_normalisation": 0,
|
||||
"new_opportunities": 0,
|
||||
"new_publication": 0,
|
||||
"active_writes": 0,
|
||||
"new_roles_or_grants": 0,
|
||||
"no_new_matrix_or_backlog": true
|
||||
},
|
||||
"next_concrete_work": "Establish traceable private source batches/rows and mappings using existing data/inventory mechanisms;expose authorised location-sector-activity supply query in existing proactive editor;link selected supply records and source coverage to comparison/need/opportunity,then demonstrate save/recover end-to-end in isolated QA. Do not publish a directory or treat unknown coverage as zero supply. Analytical supply records must not require a user organisation,a business-for-sale listing,a real promoter or a confirmed commercial commitment just to be consulted internally.",
|
||||
"matrix_ids": [
|
||||
"RF-CLU-003",
|
||||
"MASTER-471012ab-0010",
|
||||
"M2-d0c06a75-0147",
|
||||
"M2-d0c06a75-0151",
|
||||
"MASTER-1f0a4cde-0002"
|
||||
],
|
||||
"evidence": {
|
||||
"active_source_code_verified": [
|
||||
{
|
||||
"file": "app/Services/ProactiveInvestment.php",
|
||||
"sha256": "957dc455f0153d615577fe182b51969e6d253b33e24ac78547fcccd06215a51c",
|
||||
"installed_matches_Git": true
|
||||
},
|
||||
{
|
||||
"file": "app/Services/OpportunityManagement.php",
|
||||
"sha256": "ebe5535d94ad3c7d607bd70f2ff5dce75d810ab26924ac55ac08f036fd5eee33",
|
||||
"installed_matches_Git": true
|
||||
},
|
||||
{
|
||||
"file": "resources/js/components/proactive-investment.tsx",
|
||||
"sha256": "9c6acadef8d29732efcfcc4bcdfb28ce3be4f19127a626c1e85c6df98fd91ecf",
|
||||
"installed_matches_Git": true
|
||||
},
|
||||
{
|
||||
"file": "app/Services/InvestorContext.php",
|
||||
"sha256": "54be88361214d896640c808d8fe29f93c552a69870d6bb3ecce4de458a51ed48",
|
||||
"installed_matches_Git": true
|
||||
},
|
||||
{
|
||||
"file": "routes/web.php",
|
||||
"sha256": "95930c76977d446b74a422acc4b60869ff6bae5d4567c21cc644484c5f09cbda",
|
||||
"installed_matches_Git": true
|
||||
}
|
||||
],
|
||||
"local_artifacts": {
|
||||
"accessible-source-inventory.json": "2815b778b0a4d1d601fd85c46c9b5c3a19bd7adcdb46cf86f0f28f45717b2afa",
|
||||
"accepted-source-check.json": "06b814ee4e322fb78f081788b061936a324b3edcf6d56a80a82eeabdf2906ec6",
|
||||
"active-audit.json": "06e3cfba8c180e93a86a9dc5db86403220e7c5406229ef166b4e604cc2d8e71c",
|
||||
"consumer-trace.json": "df1fa6fa8dc3c18485b6e83a4f6a688f2d5a2124e9a8f5c75a944ec859e420f3"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
{
|
||||
"date": "2026-10-28",
|
||||
"status": "internal_supply_integration_demonstrated_in_isolated_QA_not_installed",
|
||||
"baseline": "150bef4b897de8d0711c1e2bbeedbcaf5ee241d2b",
|
||||
"baseline_runtime": "569a1cd750f3d2a0b87f130e992e73e070bbd1ab",
|
||||
"branch": "codex/proactive-supply-qa-20261028",
|
||||
"active_runtime_unchanged": "569a1cd750f3d2a0b87f130e992e73e070bbd1ab",
|
||||
"accepted_inventory_reused": "existing-supply-lists-audit-20261028.json;13 accessible originals/variants inventoried before source selection;prior proactive acceptance and fiscal delivery preserved",
|
||||
"sources": [
|
||||
{
|
||||
"alias": "companies.xlsx",
|
||||
"name": "EMPRESAS_LIST 2014-2022.xlsx",
|
||||
"sha256": "1ba4c4114b16b407e7746712d00d4f681246bbbc1b42351e96a31f7129acad30",
|
||||
"bytes": 26586267,
|
||||
"period": "2014–2022; data por registo quando disponível"
|
||||
},
|
||||
{
|
||||
"alias": "licensed.xlsx",
|
||||
"name": "Empresas Licenciadas.xlsx",
|
||||
"sha256": "1b5087b464e39e88c42def39655c2391aaaf684b4c832d6a77ff274b98889f00",
|
||||
"bytes": 34940776,
|
||||
"period": "Data por registo; cobertura temporal global não determinada"
|
||||
},
|
||||
{
|
||||
"alias": "tourism.xlsx",
|
||||
"name": "CADASTRO DE EMPREENDIMENTOS TURISTICOS 3, 4 E 5 ESTRELAS 30.10.24 18,00h.xlsx",
|
||||
"sha256": "f02e086ed4973ef17234aa6cc54b98fbb7a6952ffb16ec4dc4f7d0c72af6400c",
|
||||
"bytes": 73955,
|
||||
"period": "30/10/2024 (nome do exemplar); situação actual não confirmada"
|
||||
},
|
||||
{
|
||||
"alias": "cae.xlsx",
|
||||
"name": "CAE eBAU.xlsx",
|
||||
"sha256": "83b4f1200988e99834c2ae3915346fbf237d128308554b67b5a080d4956bf506",
|
||||
"bytes": 75018,
|
||||
"period": "Versão não indicada"
|
||||
}
|
||||
],
|
||||
"source_rows": {
|
||||
"companies.xlsx": 148556,
|
||||
"licensed.xlsx": 212631,
|
||||
"tourism.xlsx": 277
|
||||
},
|
||||
"catalog": {
|
||||
"records": 361464,
|
||||
"repeated_projection": 147165,
|
||||
"identity_conflicts": 113880,
|
||||
"activity_unclassified": 82864,
|
||||
"territory_unmapped": 205608,
|
||||
"province_only": 336,
|
||||
"unnamed_observations": 18423
|
||||
},
|
||||
"duplicate_meaning": "same projected administrative fields including entity identifier, licence/process,date,place,activity,name;all source sightings retained,not a unique-company count or byte-identical-file claim",
|
||||
"classification": {
|
||||
"activity_and_sector": "supplied CAE labels and SELF_ID hierarchy;no invented correspondence to twelve portal navigation sectors",
|
||||
"territory": "unique normalized province+district,otherwise exact unique canonical subordinate name within province;Maputo explicitly distinct from Maputo Cidade;no fuzzy matching;raw source labels retained",
|
||||
"identity": "valid-shaped NUIT only hashed as a possible entity key;different names flagged,not silently merged;unidentified rows remain distinct",
|
||||
"dates": "original date strings and Excel serials retained verbatim;file modification time not a reference date",
|
||||
"tourism": "277 establishment observations across two differently structured sheets;explicit province section preserved;missing district remains unknown;not an exhaustive hotels/restaurants census"
|
||||
},
|
||||
"private_import": {
|
||||
"preview": true,
|
||||
"original_hashes_checked": true,
|
||||
"malware": "Microsoft Defender custom scan on same-hash private copies,exit0,no threats detected,2026-10-28",
|
||||
"idempotency": {
|
||||
"before": 343041,
|
||||
"after": 343041,
|
||||
"sha256": "6a9c820c73a8c535ed3cdfc03db0311edf0300f1f97ade75f0fce3d3c84a02e1",
|
||||
"publication": false,
|
||||
"database": "proactive_investment_20261027_test",
|
||||
"port": 55441
|
||||
},
|
||||
"tables": [
|
||||
"proactive_supply_sources",
|
||||
"proactive_supply_observations"
|
||||
],
|
||||
"RLS": "forced on both;non-owner QA policy proof:0 rows without actor/read context;authorized context recovers row",
|
||||
"contacts_identity_documents_nuit_not_returned_by_query": true,
|
||||
"no_automatic_directory_publication": true,
|
||||
"no_promoters_or_user_organizations_created": true,
|
||||
"extension_first": {
|
||||
"before": 343041,
|
||||
"after": 361464,
|
||||
"sha256": "dec9db62a52a8bac5f1e2675aecbeb4239f2b1f4fa521db01693c32bdfeef01e",
|
||||
"extension": true,
|
||||
"previous_snapshot": "6a9c820c73a8c535ed3cdfc03db0311edf0300f1f97ade75f0fce3d3c84a02e1",
|
||||
"publication": false,
|
||||
"database": "proactive_investment_20261027_test",
|
||||
"port": 55441
|
||||
},
|
||||
"extension_replay": {
|
||||
"before": 361464,
|
||||
"after": 361464,
|
||||
"sha256": "dec9db62a52a8bac5f1e2675aecbeb4239f2b1f4fa521db01693c32bdfeef01e",
|
||||
"extension": true,
|
||||
"previous_snapshot": "6a9c820c73a8c535ed3cdfc03db0311edf0300f1f97ade75f0fce3d3c84a02e1",
|
||||
"publication": false,
|
||||
"database": "proactive_investment_20261027_test",
|
||||
"port": 55441
|
||||
}
|
||||
},
|
||||
"proactive_delta": "private authenticated territory query,CAE sector/activity filters,pagination,source/row/date/hash;select existing observations,compare competition/complementarity/possible partner/uncertain with rationale and limitations;server resolves source lineage,keeps list-derived evidence internal;save/reload and private dossier preserve references",
|
||||
"gurue": {
|
||||
"date": "2026-10-28",
|
||||
"QA_only": true,
|
||||
"operator": 1021,
|
||||
"record_id": 402,
|
||||
"dossier_id": "883c8ec3-59a3-4074-b91c-b7d4a5d88ff0",
|
||||
"prior_version": 1,
|
||||
"offer_query_total": 410,
|
||||
"selected_source_records": 2,
|
||||
"comparison_relationships": [
|
||||
"competition",
|
||||
"complementarity"
|
||||
],
|
||||
"existing_five_evidences_preserved": true,
|
||||
"total_evidence": 7,
|
||||
"idea_preserved": true,
|
||||
"need": "hypothesis",
|
||||
"empty_results": "insufficient_information",
|
||||
"save_reload_recovered": true,
|
||||
"existing_dossier_preview_confirm_replay": true,
|
||||
"anonymous_internal_query_denied": true,
|
||||
"no_publication": true,
|
||||
"no_external_contacts": true,
|
||||
"errors": [],
|
||||
"source_version": 2,
|
||||
"context_revision": 2,
|
||||
"deliberate_dossier_title_and_fields_preserved": true,
|
||||
"prior_source_v1_history_retained": true,
|
||||
"songo_401_unchanged": true,
|
||||
"refreshed_offer_query_total": 435,
|
||||
"initial_browser_proof_retained": true,
|
||||
"dossier_comparisons_visible": {
|
||||
"existing_dossier_comparison_1_visible": true,
|
||||
"existing_dossier_comparison_2_visible": true,
|
||||
"read_only_after_commit": true,
|
||||
"errors": []
|
||||
}
|
||||
},
|
||||
"verification": {
|
||||
"delta_tests": 7,
|
||||
"assertions": 62,
|
||||
"types": "passed",
|
||||
"QA_build": "passed",
|
||||
"accepted_prior_proofs": "reused;no fiscal suites repeated",
|
||||
"browser": "authenticated synthetic operator1021,real supplied source rows,save/reload,existing dossier preview-confirm-replay;not team acceptance",
|
||||
"preservation": {
|
||||
"opportunities": {
|
||||
"baseline_rows": 55,
|
||||
"new_rows": 0,
|
||||
"expected_changes": [
|
||||
"402"
|
||||
]
|
||||
},
|
||||
"desk_cases": {
|
||||
"baseline_rows": 20,
|
||||
"new_rows": 0,
|
||||
"expected_changes": [
|
||||
"883c8ec3-59a3-4074-b91c-b7d4a5d88ff0"
|
||||
]
|
||||
},
|
||||
"projects": {
|
||||
"baseline_rows": 19,
|
||||
"new_rows": 0,
|
||||
"expected_changes": []
|
||||
},
|
||||
"geo_locations": {
|
||||
"baseline_rows": 1921,
|
||||
"new_rows": 0,
|
||||
"expected_changes": []
|
||||
},
|
||||
"sectors": {
|
||||
"baseline_rows": 12,
|
||||
"new_rows": 0,
|
||||
"expected_changes": []
|
||||
},
|
||||
"documents": {
|
||||
"baseline_rows": 64,
|
||||
"new_rows": 0,
|
||||
"expected_changes": []
|
||||
},
|
||||
"value_chains": {
|
||||
"baseline_rows": 0,
|
||||
"new_rows": 0,
|
||||
"expected_changes": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"remaining": [
|
||||
"No installation or import in active portal;there the list-backed use remains not demonstrated until delivery.",
|
||||
"205608 observations lack a unique canonical territorial match,including19 ambiguous names;336 have province only. Raw source territory remains available in private originals.",
|
||||
"82864 observations lack an exact supplied CAE activity label;do not impute sector/activity.",
|
||||
"113880 observations have differing names for a shared valid-shaped identifier;identity needs reconciliation,not automatic merger.",
|
||||
"Historical administrative records do not establish current operating status,capacity,prices,demand,contracts or market vacancy.",
|
||||
"National mapping/classification completeness and actual team use remain partial;Gurué proof does not close all market-study/cluster requirements.",
|
||||
"Active delivery requires capability/compatibility review,fresh backup+restore,authenticated operation and installed delta check through the existing Git procedure."
|
||||
],
|
||||
"restriction_origins": {
|
||||
"internal_analysis_no_auto_publication_no_absence_inference": "explicit Tempo task",
|
||||
"preservation_QA_active_separation_backup_restore": "permanent AGENTS instructions",
|
||||
"canonical_unique_matching_hashed_keys_query_limits_RLS": "implementation choices,not new organizational approval requirements"
|
||||
},
|
||||
"proof_files": [
|
||||
"outputs/proactive-supply-20261028/gurue-ui-proof.json",
|
||||
"outputs/proactive-supply-20261028/preservation-proof.json",
|
||||
"outputs/proactive-supply-20261028/tests-receipt.json",
|
||||
"outputs/proactive-supply-20261028/dossier-visual-proof.json",
|
||||
"outputs/proactive-supply-20261028/supply-extension-preservation.json",
|
||||
"outputs/proactive-supply-20261028/refreshed-gurue-query.json"
|
||||
],
|
||||
"L8": "unchanged,partial;this increment has no fiscal calculation change",
|
||||
"supplement": {
|
||||
"previous_snapshot_records": 343041,
|
||||
"added_unnamed_source_rows": 18423,
|
||||
"placeholder": "Denominação não indicada;never the personal applicant name",
|
||||
"all_previous_observations_unchanged": {
|
||||
"old_observations_unchanged": 343041,
|
||||
"new_observations": 18423
|
||||
},
|
||||
"preview_both_hashes_bound": {
|
||||
"previous_observations_unchanged": 343041,
|
||||
"new_observations": 18423,
|
||||
"no_replacement": true,
|
||||
"previous_sha256": "6a9c820c73a8c535ed3cdfc03db0311edf0300f1f97ade75f0fce3d3c84a02e1",
|
||||
"sha256": "dec9db62a52a8bac5f1e2675aecbeb4239f2b1f4fa521db01693c32bdfeef01e"
|
||||
},
|
||||
"original_source_rows_accounted_for": "148556 companies+212631 licensed nonempty data rows,excluding headers;277 tourism establishment sightings from two structures"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
# Entrega controlada da base comparativa interna
|
||||
|
||||
A entrega inclui o candidato QA `a022ed98`. As sete provas dirigidas/62 asserções, tipos, compilação e percurso de Gurué continuam aceites. O delta de entrega acrescenta o carregador técnico e impede que a API grave uma repetição preservada como outra comparação; este último delta tem uma prova dirigida própria, sem repetir a suite anterior.
|
||||
|
||||
361.464 é o número de observações administrativas, incluindo 147.165 repetições preservadas. As 214.299 projecções distintas também **não** são empresas únicas. A consulta exclui `duplicate_of` das contagens e dos resultados; a gravação recusa repetições como comparações adicionais. A cobertura nacional é parcial. Ausência de resultados significa informação insuficiente. A comparação fundamenta uma hipótese e não confirma procura, capacidade actual, contrato ou viabilidade.
|
||||
|
||||
## Sequência da entrega
|
||||
|
||||
1. Conferir `main`, a revisão servida, ausência de deriva do código e capacidade. A base da entrega é `569a1cd7`; não usar force-push nem substituir alterações posteriores.
|
||||
2. Criar backup novo de base e ficheiros/configuração. Preservar os backups anteriores. Restaurar numa instância isolada, sem rede e na porta55441, e comparar as174 tabelas públicas do inventário.
|
||||
3. Ensaiar o SQL da migração exacta, o carregamento e a sua repetição. Conferir que as174 tabelas anteriores não mudam. Verificar a conta real da aplicação: sem contexto de actor não lê a base; com o contexto autorizado recupera435 observações distintas em Gurué e conserva237 repetições fora dessa contagem. Não criar utilizadores, organizações ou dossiers fictícios no activo.
|
||||
4. Conferir também backup/restauro das duas novas tabelas, incluindo RLS forçada. Registar espaço efectivamente ocupado pelo ensaio e voltar a conferir capacidade antes da instalação.
|
||||
5. Na sessão SSH autenticada do processo existente, aplicar a migração delimitada e registar uma linha de migração. Conceder apenas leitura nas duas tabelas à conta de aplicação já existente. Carregar o pacote privado em transacção, com hashes e contagens fixos, recusa de conflitos e sem sobrescrever observações. Não publicar listas nem criar anúncios, clientes confirmados ou promotores.
|
||||
6. Acrescentar apenas o hash dessa migração ao contrato de schema do instalador. Conservar os90 hashes anteriores e o próprio programa do instalador. Este passo é necessário porque o processo Git não executa migrações ou importações de conteúdo automaticamente.
|
||||
7. Fazer a entrega normal por Git/`main`, mantendo a construção isolada e as guardas habituais do instalador. As provas funcionais aceites são reutilizadas; a CI habitual do instalador permanece.
|
||||
8. Conferir revisão pública, hashes do código, schema e permissões esperados, ficheiros/configuração e linhas anteriores, separando a nova linha de migração e o delta operacional de cache/sessões. Conferir negação de acesso anónimo e consulta autorizada de Gurué. A conferência visual autenticada no portal é distinta do inventário técnico por SSH.
|
||||
|
||||
## Carregador
|
||||
|
||||
`tools/load-proactive-supply.py` usa pré-visualização por omissão. Exige o pacote privado com `import-manifest.json`, `preview.json`, `scan.json`, `schema.sql` e `observations.jsonl.gz`. O JSONL aceite tem SHA256 `dec9db62a52a8bac5f1e2675aecbeb4239f2b1f4fa521db01693c32bdfeef01e`. Os quatro originais e as datas são os aceites em QA; não se pede nova validação genérica da sua origem.
|
||||
|
||||
O modo `rehearsal` exige container sem rede/portas publicadas e a identidade efectiva `proactive_supply_delivery_20261028_test:55441`. O modo `apply` exige a sessão técnica SSH, a identidade `teste_investidor:5432`, revisão esperada e backup/restauro verificado. A preparação e os recibos ficam privados em `/var/backups/investmoz-proactive-supply-delivery-20261028`, fora da área servida. O carregador não cria schema no activo, não publica dados e não apaga ou substitui registos. A tabela temporária é indexada antes de conferir a linhagem de duplicações.
|
||||
|
||||
## Preservação e recuo
|
||||
|
||||
Se uma conferência divergir, preservar os exemplares e delimitar a diferença antes de agir. Recuar código não autoriza apagar a base privada ou o seu histórico. A migração recusa `down` enquanto existirem fontes carregadas. Um restauro integral nunca deve sobrescrever intervenções posteriores sem reconciliação; os originais e o pacote privado permitem recuperar o catálogo separadamente. As duas novas tabelas e o único registo de migração são o delta de schema/dados previsto; não alterar papéis de funcionários ou permissões anteriores.
|
||||
|
||||
As restrições de análise interna, preservação, não duplicação, não publicação e separação QA/activo vêm das decisões expressas da Tempo e do `AGENTS.md`. Identidades exactas, hashes, limites técnicos, indexação temporária e contratos são escolhas de implementação, não novas exigências organizacionais. O incremento não fecha os requisitos nacionais amplos e não altera L8, que permanece parcial.
|
||||
@@ -0,0 +1,412 @@
|
||||
{
|
||||
"date": "2026-10-28",
|
||||
"status": "installed_technical_delta_verified_authenticated_visual_conference_pending",
|
||||
"served_commit": "6d1476936920dc5715d4493b1f8b22a3d51935f4",
|
||||
"accepted_QA_candidate": "a022ed9848b41ba342b4209414c2bbf2ebd0a7a3",
|
||||
"previous_runtime": "569a1cd750f3d2a0b87f130e992e73e070bbd1ab",
|
||||
"normal_Git_fast_forward": true,
|
||||
"force_used": false,
|
||||
"original_QA_and_history_preserved": true,
|
||||
"scope": "private comparative supply base integrated into existing proactive journey;not automatic public directory",
|
||||
"counts": {
|
||||
"administrative_observations": 361464,
|
||||
"repetitions_preserved": 147165,
|
||||
"distinct_administrative_projections": 214299,
|
||||
"unique_companies": null,
|
||||
"Gurué_distinct_observations": 435,
|
||||
"Gurué_repetitions_excluded": 237,
|
||||
"comparison_API_rejects_preserved_duplicate_as_another_comparison": true
|
||||
},
|
||||
"limits": {
|
||||
"national_coverage": "partial",
|
||||
"missing_records": "insufficient information;never proof of absent supply",
|
||||
"investment_conclusion": "hypothesis only;no automatic proof of demand,capacity,contracts or viability",
|
||||
"unmapped_territory": 205608,
|
||||
"province_only": 336,
|
||||
"activity_unclassified": 82864,
|
||||
"shared_identifier_name_conflicts": 113880,
|
||||
"unnamed_observations_preserved": 18423
|
||||
},
|
||||
"backup": {
|
||||
"stage": "/var/backups/investmoz-proactive-supply-delivery-20261028/backup-install",
|
||||
"created_at": "2026-10-28T17:10:23Z",
|
||||
"target": {
|
||||
"database": "teste_investidor",
|
||||
"port": 5432
|
||||
},
|
||||
"tables": 174,
|
||||
"files_verified": 369,
|
||||
"dump_sha256": "1a0bc3767062633ba35afe1308c7f29ef441a01dba35551534ed1eef9e8e8509",
|
||||
"files_sha256": "e622cdd04a252740ad1d0c626a9de32aaf1e71f3c986591cdbd94bafd314a1d7",
|
||||
"restore_verified": false
|
||||
},
|
||||
"backup_restore_rehearsal": {
|
||||
"restore_verified": true,
|
||||
"tables_verified": 174,
|
||||
"container_network": "none",
|
||||
"database": "proactive_supply_delivery_20261028_test",
|
||||
"port": 55441,
|
||||
"source_dump_sha256": "1a0bc3767062633ba35afe1308c7f29ef441a01dba35551534ed1eef9e8e8509",
|
||||
"active_database_writes": 0,
|
||||
"private_import_first": {
|
||||
"candidate": "a022ed9848b41ba342b4209414c2bbf2ebd0a7a3",
|
||||
"records": 361464,
|
||||
"source_counts": {
|
||||
"1ba4c4114b16b407e7746712d00d4f681246bbbc1b42351e96a31f7129acad30": 148556,
|
||||
"1b5087b464e39e88c42def39655c2391aaaf684b4c832d6a77ff274b98889f00": 212631,
|
||||
"f02e086ed4973ef17234aa6cc54b98fbb7a6952ffb16ec4dc4f7d0c72af6400c": 277,
|
||||
"83b4f1200988e99834c2ae3915346fbf237d128308554b67b5a080d4956bf506": 0
|
||||
},
|
||||
"raw_sha256": "dec9db62a52a8bac5f1e2675aecbeb4239f2b1f4fa521db01693c32bdfeef01e",
|
||||
"sql_bytes": 201634556,
|
||||
"mode": "rehearsal",
|
||||
"unique_company_count": null,
|
||||
"duplicate_suppression": "WHERE duplicate_of IS NULL;retained administrative sightings do not count as separate offers",
|
||||
"national_coverage": "partial",
|
||||
"investment_conclusion": "hypothesis only;does not prove demand or viability",
|
||||
"database": "proactive_supply_delivery_20261028_test|55441",
|
||||
"before": 0,
|
||||
"after": {
|
||||
"records": 361464,
|
||||
"distinct_observations": 214299,
|
||||
"repeated_projections": 147165,
|
||||
"not_unique_companies": true,
|
||||
"published": false
|
||||
},
|
||||
"idempotent_no_overwrite": true,
|
||||
"active_existing_business_data_touched": false
|
||||
},
|
||||
"private_import_replay": {
|
||||
"candidate": "a022ed9848b41ba342b4209414c2bbf2ebd0a7a3",
|
||||
"records": 361464,
|
||||
"source_counts": {
|
||||
"1ba4c4114b16b407e7746712d00d4f681246bbbc1b42351e96a31f7129acad30": 148556,
|
||||
"1b5087b464e39e88c42def39655c2391aaaf684b4c832d6a77ff274b98889f00": 212631,
|
||||
"f02e086ed4973ef17234aa6cc54b98fbb7a6952ffb16ec4dc4f7d0c72af6400c": 277,
|
||||
"83b4f1200988e99834c2ae3915346fbf237d128308554b67b5a080d4956bf506": 0
|
||||
},
|
||||
"raw_sha256": "dec9db62a52a8bac5f1e2675aecbeb4239f2b1f4fa521db01693c32bdfeef01e",
|
||||
"sql_bytes": 201634556,
|
||||
"mode": "rehearsal",
|
||||
"unique_company_count": null,
|
||||
"duplicate_suppression": "WHERE duplicate_of IS NULL;retained administrative sightings do not count as separate offers",
|
||||
"national_coverage": "partial",
|
||||
"investment_conclusion": "hypothesis only;does not prove demand or viability",
|
||||
"database": "proactive_supply_delivery_20261028_test|55441",
|
||||
"before": 361464,
|
||||
"after": {
|
||||
"records": 361464,
|
||||
"distinct_observations": 214299,
|
||||
"repeated_projections": 147165,
|
||||
"not_unique_companies": true,
|
||||
"published": false
|
||||
},
|
||||
"idempotent_no_overwrite": true,
|
||||
"active_existing_business_data_touched": false
|
||||
},
|
||||
"baseline_public_tables_unchanged_after_import": 174,
|
||||
"actual_application_role": "teste_investidor_app",
|
||||
"RLS_without_actor_rows": 0,
|
||||
"Gurue": {
|
||||
"observations": 435,
|
||||
"retained_repetitions": 237,
|
||||
"not_unique_companies": true
|
||||
},
|
||||
"storage": {
|
||||
"private_relations_bytes": 344399872,
|
||||
"database_bytes": 409070739
|
||||
},
|
||||
"catalogue_backup_restore": {
|
||||
"verified": true,
|
||||
"sha256": "7ebb8791d5871bb4752a3e6e964a398c0f8fc094a10aab0e33aaee202dfc62d3",
|
||||
"rows": 361464,
|
||||
"forced_RLS_tables": 2,
|
||||
"row_checksum": "9f9773492aa45f498201ff791726004a"
|
||||
},
|
||||
"import_and_replay_seconds": 67.3371156482026,
|
||||
"no_application_users_or_dossiers_created": true,
|
||||
"no_publication": true,
|
||||
"active_data_writes": 0
|
||||
},
|
||||
"private_base_load": {
|
||||
"at": "2026-10-28T17:14:46.618843+00:00",
|
||||
"baseline": "569a1cd750f3d2a0b87f130e992e73e070bbd1ab",
|
||||
"private_schema_created": 2,
|
||||
"new_migration_registry_rows": 1,
|
||||
"new_grants": "SELECT only on the two new private tables to the existing application role;no employee assignment or public grant",
|
||||
"import": {
|
||||
"candidate": "a022ed9848b41ba342b4209414c2bbf2ebd0a7a3",
|
||||
"records": 361464,
|
||||
"source_counts": {
|
||||
"1ba4c4114b16b407e7746712d00d4f681246bbbc1b42351e96a31f7129acad30": 148556,
|
||||
"1b5087b464e39e88c42def39655c2391aaaf684b4c832d6a77ff274b98889f00": 212631,
|
||||
"f02e086ed4973ef17234aa6cc54b98fbb7a6952ffb16ec4dc4f7d0c72af6400c": 277,
|
||||
"83b4f1200988e99834c2ae3915346fbf237d128308554b67b5a080d4956bf506": 0
|
||||
},
|
||||
"raw_sha256": "dec9db62a52a8bac5f1e2675aecbeb4239f2b1f4fa521db01693c32bdfeef01e",
|
||||
"sql_bytes": 201634556,
|
||||
"mode": "apply",
|
||||
"unique_company_count": null,
|
||||
"duplicate_suppression": "WHERE duplicate_of IS NULL;retained administrative sightings do not count as separate offers",
|
||||
"national_coverage": "partial",
|
||||
"investment_conclusion": "hypothesis only;does not prove demand or viability",
|
||||
"database": "teste_investidor|5432",
|
||||
"before": 0,
|
||||
"after": {
|
||||
"records": 361464,
|
||||
"distinct_observations": 214299,
|
||||
"repeated_projections": 147165,
|
||||
"not_unique_companies": true,
|
||||
"published": false
|
||||
},
|
||||
"idempotent_no_overwrite": true,
|
||||
"active_existing_business_data_touched": false
|
||||
},
|
||||
"schema_contract_delta": "one reviewed migration appended;all90 previous entries unchanged",
|
||||
"pipeline_script_unchanged": true,
|
||||
"publication": false,
|
||||
"code_installation_pending": true,
|
||||
"backup_dump_sha256": "1a0bc3767062633ba35afe1308c7f29ef441a01dba35551534ed1eef9e8e8509"
|
||||
},
|
||||
"capacity": {
|
||||
"at": "2026-10-28T17:13:59.594789+00:00",
|
||||
"volumes": [
|
||||
{
|
||||
"path": "/var/www/investmoz-homologacao/releases",
|
||||
"free_bytes": 86519189504,
|
||||
"free_inodes": 23490190
|
||||
},
|
||||
{
|
||||
"path": "/var/lib/investmoz-deploy",
|
||||
"free_bytes": 86519189504,
|
||||
"free_inodes": 23490190
|
||||
},
|
||||
{
|
||||
"path": "/var/backups",
|
||||
"free_bytes": 86519189504,
|
||||
"free_inodes": 23490190
|
||||
},
|
||||
{
|
||||
"path": "/tmp",
|
||||
"free_bytes": 86519189504,
|
||||
"free_inodes": 23490190
|
||||
},
|
||||
{
|
||||
"path": "/var/lib/docker",
|
||||
"free_bytes": 86519189504,
|
||||
"free_inodes": 23490190
|
||||
}
|
||||
],
|
||||
"required_budget_bytes": 6259143420,
|
||||
"budget_basis": "existing5GiB build guard+twice measured private relations+measured SQL staging;already-created backups/stages included in actual free space",
|
||||
"backup_sha256": "1a0bc3767062633ba35afe1308c7f29ef441a01dba35551534ed1eef9e8e8509",
|
||||
"backup_restore_verified": true,
|
||||
"active_writes": 0
|
||||
},
|
||||
"installed_delta": {
|
||||
"at": "2026-10-28T17:22:03.023596+00:00",
|
||||
"served_commit": "6d1476936920dc5715d4493b1f8b22a3d51935f4",
|
||||
"backup_dump_sha256": "1a0bc3767062633ba35afe1308c7f29ef441a01dba35551534ed1eef9e8e8509",
|
||||
"prior_public_tables_checked": 174,
|
||||
"prior_domain_rows_preserved": true,
|
||||
"new_migration_registry_rows": 1,
|
||||
"operational_delta": {
|
||||
"cache": {
|
||||
"before": {
|
||||
"rows": 111,
|
||||
"checksum": "a9dc6868205af2190cee2d2787ee8b66"
|
||||
},
|
||||
"after": {
|
||||
"rows": 111,
|
||||
"checksum": "08c098b3fde0052afa05aae647d18c5f"
|
||||
}
|
||||
},
|
||||
"sessions": {
|
||||
"before": {
|
||||
"rows": 7,
|
||||
"checksum": "ef549836d7a2e0549b1a32f1627c83f4"
|
||||
},
|
||||
"after": {
|
||||
"rows": 8,
|
||||
"checksum": "4a8b547e0552be4da65bcb97d3851a49"
|
||||
}
|
||||
}
|
||||
},
|
||||
"persistent_files_and_configuration_identical": 369,
|
||||
"private_catalogue": {
|
||||
"observations": 361464,
|
||||
"distinct_administrative_observations": 214299,
|
||||
"preserved_repetitions": 147165,
|
||||
"unmapped_territory": 205608
|
||||
},
|
||||
"Gurue": {
|
||||
"observations": 435,
|
||||
"repetitions": 237
|
||||
},
|
||||
"RLS_without_actor_rows": 0,
|
||||
"anonymous_query_status": 401,
|
||||
"public_revision_confirmed": true,
|
||||
"actual_team_UI_journey": "pending authenticated browser conference;accepted QA is not repeated",
|
||||
"unique_company_count": null,
|
||||
"publication": false,
|
||||
"national_coverage": "partial",
|
||||
"investment_conclusion": "hypothesis,not proved demand or viability",
|
||||
"L8": "unchanged,partial"
|
||||
},
|
||||
"application_consumer": {
|
||||
"installed_application_service_read": true,
|
||||
"existing_actor_passes_existing_profile_authorization": true,
|
||||
"application_database_identity": {
|
||||
"database": "teste_investidor",
|
||||
"role": "teste_investidor_app",
|
||||
"port": 5432
|
||||
},
|
||||
"observations": 435,
|
||||
"preserved_repetitions_excluded": 237,
|
||||
"empty_results": "Informação insuficiente nesta base e nestes filtros.",
|
||||
"source_metadata_present": true,
|
||||
"no_new_actor_grant_or_record": true,
|
||||
"read_only": true,
|
||||
"proof_kind": "technical CLI consumer,not authenticated team browser acceptance"
|
||||
},
|
||||
"operational_delta": {
|
||||
"status": "operational_delta_reconciled",
|
||||
"comparison_identity": "postgres before/after;isolated restored baseline;no RLS visibility inference",
|
||||
"cache": "queue:restart;requestthrottle;identical home-sector cached value with refreshed expiry(HomeController remember10min);no deletedcache",
|
||||
"sessions": "anonymous sessions from delivery user-agent or exactUA/time correlated with nginx public request;deleted sessions individually expired beyond configured lifetime,normal database session garbage collection(config/session lottery2/100)",
|
||||
"delta": {
|
||||
"cache": {
|
||||
"before_rows": 111,
|
||||
"after_rows": 111,
|
||||
"changes": [
|
||||
{
|
||||
"key_sha256": "7f7dd83f47f5bf404d55e6e9f1367948721579970b195680645e356031a070ff",
|
||||
"kind": "updated",
|
||||
"fields": [
|
||||
"value",
|
||||
"expiration"
|
||||
],
|
||||
"key_pattern": "investemoz-cache-illuminate:queue:restart",
|
||||
"is_queue_restart_key": true,
|
||||
"expiration_unchanged": false,
|
||||
"throttle_timer_pair": false,
|
||||
"same_value_home_cache_expiry_refresh": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"sessions": {
|
||||
"before_rows": 7,
|
||||
"after_rows": 9,
|
||||
"changes": [
|
||||
{
|
||||
"key_sha256": "5cb21cc6c5f36834d39f2aa929d2272b65358654155858bae027abe4ccdf5c4e",
|
||||
"kind": "added",
|
||||
"fields": [
|
||||
"id",
|
||||
"ip_address",
|
||||
"user_agent",
|
||||
"payload",
|
||||
"last_activity"
|
||||
],
|
||||
"anonymous_user": true,
|
||||
"delivery_user_agent": true,
|
||||
"expired_before_comparison": false,
|
||||
"session_lifetime_minutes": 120,
|
||||
"anonymous_public_request_logged": true
|
||||
},
|
||||
{
|
||||
"key_sha256": "6fa8f1e07be4cea4d35b9ee67f1e480f35baac9228f33c5161f24e2aac5e33f2",
|
||||
"kind": "added",
|
||||
"fields": [
|
||||
"id",
|
||||
"ip_address",
|
||||
"user_agent",
|
||||
"payload",
|
||||
"last_activity"
|
||||
],
|
||||
"anonymous_user": true,
|
||||
"delivery_user_agent": true,
|
||||
"expired_before_comparison": false,
|
||||
"session_lifetime_minutes": 120,
|
||||
"anonymous_public_request_logged": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"business_tables_to_confirm_separately": 172,
|
||||
"active_data_writes_by_comparison": 0,
|
||||
"restore_network": "none"
|
||||
},
|
||||
"installer_and_catalogue_backup": {
|
||||
"at": "2026-10-28T17:24:41.602671+00:00",
|
||||
"installed_commit": "6d1476936920dc5715d4493b1f8b22a3d51935f4",
|
||||
"previous_release_preserved": "/var/www/investmoz-homologacao/releases/569a1cd750f3d2a0b87f130e992e73e070bbd1ab-53a57d6f",
|
||||
"installer_CI": [
|
||||
"\u001b[30;42mOK (1 test, 3106 assertions)\u001b[0m",
|
||||
"✓ built in 31.30s",
|
||||
"\u001b[30;42mOK (59 tests, 317 assertions)\u001b[0m",
|
||||
"# tests 1146",
|
||||
"# pass 1146",
|
||||
"# fail 0"
|
||||
],
|
||||
"accepted_QA_reused": {
|
||||
"tests": 7,
|
||||
"assertions": 62,
|
||||
"Gurue_browser_proof": "accepted earlier;not repeated as a prerequisite"
|
||||
},
|
||||
"delivery_delta_test": {
|
||||
"tests": 1,
|
||||
"assertions": 2,
|
||||
"scope": "repeated source sightings cannot inflate saved comparisons"
|
||||
},
|
||||
"installed_private_catalogue_backup": {
|
||||
"path": "/var/backups/investmoz-proactive-supply-delivery-20261028/catalogue-installed.dump",
|
||||
"bytes": 63461920,
|
||||
"sha256": "18f71d0ad72aae9645a6101c6388c4d7f481e56ef2d41dc64241245314e85d55",
|
||||
"read_grants_and_RLS_in_dump": true
|
||||
},
|
||||
"existing_pipeline_script_sha256": "adec1c17654f3a8658b83b134f1e75f95391b235738552721906c3236e388604",
|
||||
"code_branch": "main",
|
||||
"forced_push": false,
|
||||
"automatic_publication": false
|
||||
},
|
||||
"schema_and_access": "two new private tables,forced RLS,one migration registry row;SELECT only to existing application database role;no employee-role assignments changed;ninety earlier migration hashes preserved and one appended to the existing installer contract;installer program unchanged",
|
||||
"accepted_proofs_reused": {
|
||||
"QA_tests": 7,
|
||||
"QA_assertions": 62,
|
||||
"Gurue_saved_reload_dossier_chain": "accepted earlier in isolated QA;not falsely presented as new team acceptance"
|
||||
},
|
||||
"delivery_delta_test": {
|
||||
"tests": 1,
|
||||
"assertions": 2,
|
||||
"reason": "explicit non-inflation requirement;reject a preserved repeated observation as another saved comparison"
|
||||
},
|
||||
"visual_conference": {
|
||||
"status": "pending",
|
||||
"known_blocker": "The browser access host is unavailable",
|
||||
"technical_consumer_is_not_team_UI_acceptance": true,
|
||||
"manual_remaining": "Gurué → consultar oferta → comparar ideia → guardar → recuperar no mesmo dossier,com fontes,datas e incertezas;435 observações,não empresas únicas;listas internas,cobertura parcial e hipótese preservadas.",
|
||||
"blocker_history": [
|
||||
"Windows Computer Use could not determine the current browser URL with enough confidence to enforce policy"
|
||||
],
|
||||
"mechanism": "Browser access connector host unavailable;opening a tab alone does not restore the connector. No documented host-restart operation is exposed in the available browser control API.",
|
||||
"next_action": "Restabelecer o conector por mecanismo suportado ou receber conferência autenticada da equipa;nenhuma reinstalação ou repetição das provas técnicas aceites."
|
||||
},
|
||||
"preservation": "prior domain rows unchanged;369 persistent files/configuration identical;only intended migration row and new private base added;queue restart and2anonymous delivery sessions reconciled separately;no QA user,organisation,opportunity or dossier copied to active",
|
||||
"publication": false,
|
||||
"L8": "unchanged,partial;no fiscal code or calculation delta",
|
||||
"remote_receipts_root": "/var/backups/investmoz-proactive-supply-delivery-20261028",
|
||||
"local_proofs": "C:\\laragon\\www\\portal-investidor\\outputs\\proactive-supply-delivery-20261028",
|
||||
"restriction_origins": {
|
||||
"private_analysis_no_auto_publication_no_false_market_vacancy_no_duplicate_inflation": "explicit Tempo decisions",
|
||||
"backup_restore_QA_isolation_preservation": "AGENTS.md",
|
||||
"exact_hashes_temporary_index_database_identity_contract": "technical implementation choices;not new organisational approvals"
|
||||
},
|
||||
"documentation_correction": {
|
||||
"date": "2026-10-28",
|
||||
"origin": "explicit Tempo correction",
|
||||
"installed_revision_unchanged": "6d147693",
|
||||
"previous_remote_receipt_preserved_sha256": "2e306482a2dc4aea79acd77239309a3307950eddbbd9ea77db9c7f28c47d2110",
|
||||
"scope": "documentation only;no active write,no reinstall,no repeated technical tests"
|
||||
}
|
||||
}
|
||||
@@ -7166,3 +7166,97 @@ CIRPC64(1–2)/53(1)a e40(1) na redacçãoLei20/2009 são correspondidos no pró
|
||||
**Pendências concretas:**inclusão/correcção automática do dividendo/acréscimo emL6 e derivação da quota proporcional;exclusão40(1)na base com todas as suas condições;liquidação64(3)/associações64(4);créditos/benefícios específicos;fracção internacional/CDT automática;obrigação/calendárioPPC/PEC;autónomos,períodos especiais,outras moedas eM20/M22. Correspondências e parâmetros em falta afectam o caso dependente,sem revalidação genérica de fontes oficiais. Saldos anteriores mantêm o recorte ordinário já demonstrado. **L8 permanece parcial.** Recibo:`docs/investmoz/simulations/irpc-l8-economic-qa-20261028.json`.
|
||||
|
||||
Preservação delimitada:558ficheiros protegidos com conteúdo Git idêntico à baseQA;uma diferençaLF/CRLF em `docs/investmoz/simulations/irpc-l8-carry-qa-20261028.json` entre checkouts,conferida como normalização de ambiente,sem perda de conteúdo. Os31originais do acervo são iguais por bytes.
|
||||
|
||||
|
||||
## 28/10/2026 — entrega agrupada de saldos anteriores e crédito económico pelo Git
|
||||
|
||||
**Instalado e conferido em `569a1cd750f3d2a0b87f130e992e73e070bbd1ab`**, sobre `838ad941`, por fast-forward normal em `main`,sem force. Foram entregues em conjunto os saldos anteriores `d134a92d` e o crédito económico ordinário `aae67c1`, já demonstrados emQA. Os estadosQA anteriores permanecem históricos;estes recortes deixam de estar pendentes de instalação. **L8 permanece parcial;esta entrega não fecha integralmente a liquidação anual.**
|
||||
|
||||
**Delta e preservação:**12ficheiros de aplicação,byte a byte correspondentes à candidata aceite;1005fontes antes e1011depois conferidas comGit. Demais fontes comparadas,PECcorrente,internacionalcorrente,sectores,L3/ISPC/câmbio/L5–L7,originais e contexto adaptativo preservados. Sem alterações de schema/configuração/rotas/grants,migrations ou importações de conteúdo. RuntimeQA conservado fora da árvore servida. Songo/Gurué permanecem privados eSGFTEMPO separado.
|
||||
|
||||
**Capacidade/recuperação:**82,71GiB e23,52milhões de inodes livres antes;capacidade novamente conferida após backup,com o mínimo existente do instalador e espaço real do backup. Backup fresco `2026-10-28T12:38:25Z` (14:38:25locais),em `/var/backups/investmoz-irpc-l8-grouped-20261028/backup`;dumpSHA256`007cd1851d5bd51a54265ab99a342d6a312de06ef642fc42b38b7a5e771d4d9a`. Restauro174tabelas com contagens/checksums iguais emcontentorsemrede/porta55441;369ficheiros/configuração conferidos. Depois da entrega:172tabelas de negócio e369ficheiros persistentes/configuração intactos;schema,papéis,grants epermissões iguais,identidades de comparação equivalentes. Delta operacional delimitado a `queue:restart` e2sessões anónimas do agente de entrega,correlacionadas com pedidos públicos;nenhum dado ou sessão sobrescrito para obter igualdade. Release anterior mantida até todos os controlos e troca atómica;manifest/revisão servidos confirmados.
|
||||
|
||||
**Provas reutilizadas e controlos da entrega:**85testes afectados/20económicos novos,API210asserções/11recuperações HTML/dossier e históricos aceites reutilizados;sem repetir avaliação manual de funcionalidades independentes. Instalador obrigatório:PostgreSQL1teste/3106asserções;Unit59/317;frontend1146aprovados/0falhas;TypeScript/Wayfinder/build aprovados (30,82s). SimuladorHTTP200 e8marcadores compilados dos saldos anteriores/económico/internacional/PEC conferidos. Não foram criados casosQA no portal e não se declara nova aceitação manual da equipa.
|
||||
|
||||
### Saldo concreto de L8 após a entrega
|
||||
|
||||
| Grupo | Cobertura entregue ou trabalho restante |
|
||||
| --- | --- |
|
||||
| Saldos anteriores65(3)/66 | Entregues no recorte de origens anuais ordinárias documentadas,mesmo sujeito/2026/MZN,prazos próprios5/3exercícios,utilizações efectivas e afectação explícita. Não cria nem consome saldos oficiais;casos transitórios/especiais permanecem próprios. |
|
||||
| Económico64(1–2) | Entregue para lucros ordinários qualificados,IRPC correspondente documentado,acréscimo53 já incluído emL6 e quota proporcional documentada,antes do internacional/PEC,sem dupla utilização da mesma parcela. Parcela excluída40(1)não recebe também64. |
|
||||
| Inclusão/correcção económica e quota | Ainda por implementar a inclusão/correcção automática do dividendo/acréscimo emL6,a derivação automática da quota e o tratamento completo40(1)na base. A comprovação manual corrente não é declarada automação desses grupos. |
|
||||
| Económico64(3/4) e benefícios | Liquidação,associações ecréditos/benefícios específicos mantêm qualificação,limites,cumulação e uso próprios por implementar;sem os transformar emcrédito internacional. |
|
||||
| Internacional automático | Derivação da fracção nacional e correspondência específicaCDT continuam pendentes;qualificação/dedução internacional documentada entregue preservada. |
|
||||
| Obrigações e representação | Obrigação/calendárioPPC/PEC,juros/multas/guias,autónomos,períodos especiais,outras moedas eM20/M22/transporte/submissão continuam nos grupos concretos anteriores. |
|
||||
|
||||
A falta de prova do caso (titular/rendimento/imposto/inclusão/uso/regra do período ou quota) limita só o resultado dependente;não é reabertura da autenticidade das fontes oficiais aceites nem novo aprovador. Exemplo agrupado condicionado aos suportes sintéticos já aceites:1408−12económico−8internacionalcorrente−50internacionalanterior−200PECcorrente−100PECanterior−500PPC=**538MZN de saldo simulado a pagar**;não é declaração submetida ou saldo fiscal consumido.
|
||||
|
||||
Recibo consolidado:`docs/investmoz/simulations/irpc-l8-grouped-delivery-20261028.json`. Cópia remota:`/var/backups/investmoz-irpc-l8-grouped-20261028/installed-receipt.json`. Provas locais:`outputs/irpc-l8-grouped-delivery-20261028/`. Actualização documental segue ramo próprio de recibos,sem alterar novamente a revisão servida.
|
||||
|
||||
Cópia remota do recibo conferida:SHA256`b70fe154f95162f029b5ca712b41962757360d73b0ff07f52b8ce14848d990df`;a matriz existente recebeu saldoL8 nos camposEstado,Disponibilidade activa,Evidências,Critérios restantes eDependências,só em `AUD-SIM-INT02`.
|
||||
|
||||
|
||||
## 28/10/2026 — listas como base interna da oferta: uso não demonstrado
|
||||
|
||||
**Decisão expressa da Tempo e resultado da conferência: a utilização das listas de empresas,hotéis/restaurantes como base comparativa no portal é NÃO DEMONSTRADA.** A entrega do percurso proactivo,os seus campos de evidência e os pilotos privados anteriores mantêm-se aceites;isso não demonstra carregamento,classificação e ligação destas listas ao consumidor de análise. O âmbito é primeiro a análise interna,não a publicação automática de um directório.
|
||||
|
||||
**Fonte e âmbito conferidos:**13ficheiros inventariados nos dois directórios acessíveis de empresas/relatórios,antes da selecção;originais `EMPRESAS_LIST2014–2022`, `Empresas Licenciadas`,cadastro turístico30/10/2024 e `CAE eBAU` localizados e iguais aos hashes aceites. As148557/212632/460linhasXML não vazias dos recibos anteriores são observações dos ficheiros,não empresas únicas nem carga no portal. Empresas têm província/distrito/CAE;isso não prova o mapeamento no portal. O cadastro turístico observado é de alojamento,não um censo completo da restauração. Não foi identificada lista autónoma de restaurantes nestes dois directórios;isso não significa ausência de restaurantes ou de actividades de restauração nas listas empresariais. Contactos e outros campos privados não foram publicados.
|
||||
|
||||
**Destino entregue:**código `569a1cd7`,base `teste_investidor`,198tabelas não sistémicas(174public+24skills_edu),consultadas em transacções READ ONLY com identidade técnicapostgres explicitada. Zero correspondências dos hashes originais entre368ficheiros persistentes e nenhuma correspondência exacta dos nomes/hashes nas referências pesquisadas. Cadastros `existing_businesses` e `investment_company_declarations` têm0registos nesse destino;organizações1,actividades11,documentos60 e recursos59 não são convertidos em oferta empresarial das listas. Esta é uma fotografia técnica,não prova de visibilidade para uma conta operacional,perda de registos ou ausência de oferta no mercado. Registos antigos de `investemoz` e basesQA são ambientes distintos;nenhuma eliminação é declarada. Pesquisa por nomes/hashes em metadados não exclui ficheiros renomeados,arquivados,conteúdo cifrado ou outros acervos.
|
||||
|
||||
**Causa delimitada no consumidor instalado:**`ProactiveInvestment::options` devolve documentos/grupos e referências a projecto,cluster,corredor,parque,cadeia;os hashes dos ficheiros instalados coincidem comGit. O editor recebe `observed_supply` e fontes/localizadores/períodos/cobertura como texto manual;as relações proactivas não admitem entidade empresarial destas listas. Não foi encontrada consulta de oferta carregada por localização/sector/actividade nem ligação de linhas das listas a concorrentes,complementares ou parceiros. A possível existência de cadastros noutro módulo não demonstra consumo por este percurso.
|
||||
|
||||
| Percurso de conferência — Gurué,distrito canónico | Resultado |
|
||||
| --- | --- |
|
||||
| Escolher localização | Referência canónica localizada;prova anterior de selecção territorial preservada,não se declara nova prova de interface. |
|
||||
| Consultar oferta existente das listas | Não demonstrado:consulta/importação/classificação consumida pelo percurso não identificada. A demonstração pára neste ponto. |
|
||||
| Comparar com uma ideia | Não demonstrado sobre conjunto rastreável de registos dessas listas. Campos manuais não equivalem à integração. |
|
||||
| Identificar necessidade | Não demonstrado por este fluxo;ausência de registo não confirma necessidade nem ausência de concorrência. Manter hipótese,período,cobertura e incertezas. |
|
||||
| Preparar oportunidade | Mecanismo privado entregue preservado;nenhuma oportunidade nova foi criada para simular passos anteriores ainda não demonstrados. |
|
||||
|
||||
**Critério de retoma:**lotes/linhas privados rastreáveis e classificados,reutilizando os mecanismos/cadastros existentes;consulta autorizada da oferta por localização/sector/actividade;comparação da ideia com concorrência,complementares e possíveis parceiros,com fontes/datas/cobertura;necessidade fundamentada sem inferir ausência de oferta;e prévia/gravação/recuperação da oportunidade no percurso existente emQA isolada. Só essa conferência pode demonstrar o uso;nenhum novo aprovador organizacional ou directório público é criado.
|
||||
|
||||
Sem imports,normalização,alteração de código,permissões,publicação ou escrita em dados activos. Songo/Gurué privados,aprovações/históricos e entregaL8 preservados. Registo:`docs/investmoz/unification/existing-supply-lists-audit-20261028.json`. Cobertura actualizada nas linhas existentesRF-CLU-003,MASTER-471012ab-0010,M2-d0c06a75-0147/0151 eMASTER-1f0a4cde-0002,sem nova matriz/backlog.
|
||||
|
||||
A base interna de oferta não exige transformar cada empresa numa organização utilizadora,anúncio de venda/parceria,promotor ou cliente confirmado. O cadastro e a consulta analítica são distintos da operação comercial e da divulgação.
|
||||
|
||||
|
||||
## 28/10/2026 — oferta existente ligada à análise proactiva, demonstrada em QA
|
||||
|
||||
Incremento sobre o código entregue `569a1cd7`, ramo `codex/proactive-supply-qa-20261028`: **demonstrado em QA isolada, sem instalação nem importação no activo**. O relatório de conferência anterior permanece histórico: no destino activo, o uso das listas continua não demonstrado. Reutilizam-se a aceitação do percurso, o inventário de 13 ficheiros, os originais, as fontes e as provas anteriores; não se reabre a decisão da Tempo nem se encerra o estudo de mercado nacional.
|
||||
|
||||
Carregadas privadamente **361.464 observações**: 148.556 da lista 2014–2022, 212.631 de empresas licenciadas e 277 do cadastro turístico (duas folhas com estruturas distintas). Os quatro hashes dos originais e do CAE foram conferidos. Pré-visualização, análise Microsoft Defender sem ameaças e repetição idempotente 343.041→343.041 demonstradas. Os 147.165 registos com a mesma projecção administrativa são conservados como repetições; não representam empresas únicas nem duplicados de ficheiros por bytes. As 113.880 observações com denominações divergentes para um identificador partilhado ficam assinaladas, sem fusão automática. Contactos, documentos de identidade e NUIT não são devolvidos pela consulta.
|
||||
|
||||
Consulta interna por localização canónica (incluindo descendentes), sector CAE e actividade; resultados paginados conservam fonte, folha/linha, hash e data original. O sector provém da hierarquia SELF_ID do CAE fornecido, sem inventar correspondência com os sectores de navegação. A selecção permite comparar possível concorrência, complementaridade, parceiro ou relação por determinar, com fundamento e incertezas. O servidor recupera a origem, impede reclassificar a evidência interna como pública e conserva-a no rascunho e no dossier privado. Não transforma a lista em anúncios, promotores, clientes confirmados ou publicação de directório.
|
||||
|
||||
**Gurué:** o operador QA sintético 1021 consultou 410 observações distintas na primeira demonstração (435 após o complemento de registos sem denominação), comparou dois registos reais (serviços agrícolas CAE 01610 e comércio de plantas/sementes/fertilizantes CAE 47732) com a hipótese já preparada de apoio à colheita e qualidade da folha verde. A oferta possível exige verificar sobreposição e colaboração, sem presumir mercado vazio. Rascunho 402 passa de v1 a v2, mantendo integralmente as cinco evidências anteriores, a ideia e o histórico. O mesmo dossier `883c8ec3-59a3-4074-b91c-b7d4a5d88ff0` passa de contexto1 a2 por pré-visualização e confirmação idempotente, preservando título e campos deliberados. A necessidade permanece **hipótese**, sem procura, falta de oferta ou viabilidade confirmadas. Gravação, recarregamento, acesso privado e resultado vazio como **informação insuficiente** demonstrados no navegador. Isto é prova técnica, sem substituir observação/aceitação da equipa.
|
||||
|
||||
**Cobertura e pendências:** 205.608 observações sem correspondência territorial canónica única (incluindo 19 ambiguidades), 336 apenas com província e 82.864 sem designação de actividade CAE correspondente. Mantêm-se os rótulos originais e a incerteza; não há atribuição territorial aproximada nem preenchimento presumido. O cadastro de alojamento não é um censo exaustivo de hotéis/restaurantes. Situação operacional actual, capacidade, preços, procura e compromissos continuam por demonstrar. O incremento nacional é parcial; não encerra os requisitos amplos de mercado, clusters ou liquidação anual. **L8 permanece parcial e inalterado.**
|
||||
|
||||
Sete testes dirigidos/62 asserções, TypeScript e compilação QA passaram. As duas tabelas internas têm RLS forçada; teste com identidade sem privilégios confirma ocultação sem contexto de actor e leitura com contexto autorizado. Conferidos os hashes de todas as linhas anteriores nos sete conjuntos do inventário: só o rascunho402 e o mesmo dossier sofreram o delta intencional; Songo401, projectos, territórios, sectores, documentos e cadeias foram conservados. Provas fiscais aceites reutilizadas, sem repetir as suas suites.
|
||||
|
||||
Actualizados os cinco requisitos existentes `RF-CLU-003`, `MASTER-471012ab-0010`, `M2-d0c06a75-0147`, `M2-d0c06a75-0151` e `MASTER-1f0a4cde-0002`, preservando o restante estado e pendências. Nenhuma matriz ou backlog novo. A entrega activa futura mantém conferência de capacidade/compatibilidade com main, backup/restauro fresco, operação autenticada e delta instalado. Relatório: `docs/investmoz/unification/proactive-existing-supply-qa-20261028.json`.
|
||||
|
||||
Complemento final: as 18.423 linhas empresariais sem denominação social também foram conservadas, com essa lacuna explícita e sem reutilizar o nome pessoal do requerente. As 343.041 observações da primeira fotografia permaneceram idênticas; extensão e repetição idempotente atingem 361.464 sem substituir registos, alterar as comparações ou rever as provas aceites. Os exemplares e as fotografias anteriores foram preservados.
|
||||
|
||||
|
||||
## 28/10/2026 — base comparativa instalada pelo Git; conferência visual autenticada pendente
|
||||
|
||||
Instalada e servida a revisão **`6d147693`**, que inclui o candidato aceite `a022ed98`, por fast-forward normal em `main`, sem força. A revisão569a1cd7 e os backups anteriores foram preservados. O carregamento privado foi executado em transacção autenticada depois de backup novo, restauro isolado e ensaio idempotente; o instalador Git, as suas guardas e a CI habitual foram conservados. O contrato recebeu apenas o hash da migração nova, mantendo os90 anteriores.
|
||||
|
||||
**361.464 observações administrativas não são361.464 empresas únicas.** As147.165 repetições foram conservadas, mas excluídas das contagens/resultados; as214.299 projecções administrativas distintas também não equivalem a empresas únicas. Em Gurué, o serviço instalado recupera435 observações distintas e conserva237 repetições fora dessa contagem. A API também recusa gravar uma repetição como outra comparação: apenas esse delta recebeu teste dirigido1/2asserções. As7provas/62asserções, tipos, compilação e percurso QA aceites foram reutilizados.
|
||||
|
||||
A aplicação, com a sua conta real e um actor existente que satisfaz as autorizações anteriores, consultou Gurué e conservou fonte/linha/data/hash. O resultado vazio foi «Informação insuficiente nesta base e nestes filtros.» Sem actor/contexto, a RLS devolveu0; acesso HTTP anónimo recusado401. Isto é conferência técnica do consumidor instalado, **não uma nova aceitação da equipa no navegador**. A conferência visual autenticada permanece pendente por bloqueio do mecanismo Windows de confirmação segura do URL; não pedir nova aceitação das provas QA nem confundir essa pendência com perda da funcionalidade entregue.
|
||||
|
||||
Backup fresco de174tabelas públicas e369ficheiros/configuração; restauro sem rede/55441 e os174checksums passaram. As duas tabelas privadas também foram carregadas, repetidas e restauradas no ensaio. Medidos344.399.872bytes para as novas relações;capacidade conferida antes da instalação. No activo, as linhas anteriores foram preservadas e os369ficheiros/configuração ficaram idênticos; o delta esperado é de2tabelas privadas e1registo de migração. Cache queue:restart e2sessões anónimas de conferência foram reconciliados separadamente. Foi criado também backup da base privada já instalada, preservando GRANT/RLS. Não se criaram utilizadores, organizações, oportunidades ou dossiers QA no activo, nem se publicaram as listas.
|
||||
|
||||
CI habitual do instalador: PostgreSQL1/3106asserções, Unit59/317, frontend1146aprovados/0falhas, tipos e build31,30s. O script do instalador e as configurações/ficheiros anteriores mantiveram-se; apenas leitura nas duas tabelas novas foi concedida à conta de aplicação existente. Não houve alteração de papéis de funcionários.
|
||||
|
||||
**Pendências conservadas:** cobertura nacional parcial,205.608observações sem território canónico único,336só com província,82.864sem designação CAE,113.880com divergências de denominação/identificador e18.423sem denominação social, todas preservadas. A ausência de registos é informação insuficiente. A comparação fundamenta uma hipótese, sem comprovar procura ou viabilidade. O caso/dossier QA anterior mantém-se em QA e não foi transplantado para o activo. **L8 permanece parcial e inalterado.** A actualização continua nos cinco requisitos existentes, sem nova matriz ou backlog. Recibo:`docs/investmoz/unification/proactive-supply-git-delivery-20261028.json`.
|
||||
|
||||
|
||||
## 28/10/2026 — correcção do bloqueio da conferência visual
|
||||
|
||||
`6d147693` está instalado, incluindo a base interna. A única pendência da conferência desta entrega é a validação visual autenticada: **Gurué → consultar oferta → comparar ideia → guardar → recuperar no mesmo dossier**, conservando fonte, data e incertezas. As435 observações de Gurué não são435 empresas únicas. Listas internas, cobertura nacional parcial e hipótese de investimento mantêm-se.
|
||||
|
||||
O erro actual, novamente observado, é **“The browser access host is unavailable”**: indisponibilidade do host do conector, distinta de deixar uma aba aberta. A orientação anterior para apenas manter a aba aberta era insuficiente. Restabelecer o conector por mecanismo suportado ou realizar a conferência pela equipa. Não reinstalar nem repetir as provas técnicas aceites; não declarar conferência visual concluída sem o seu resultado. O recibo remoto original e o historial do diagnóstico anterior ficam preservados; esta correcção é documental.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,30 +1,34 @@
|
||||
import { createOperationKey } from '@/lib/operation-key';
|
||||
import { Field, control, button } from '@/pages/management/shared';
|
||||
import {ProactiveSupplyBrowser} from './proactive-supply';
|
||||
|
||||
export type ProactiveEvidence = {id:string;category:string;purpose:'local'|'methodology';statement:string;interpretation:string;source:string;locator:string;period:string;scope:string;coverage:string;verified_on:string;access:'internal'|'public';document_id:string};
|
||||
export type ProactiveEvidence = {id:string;observation_id?:string;category:string;purpose:'local'|'methodology';statement:string;interpretation:string;source:string;locator:string;period:string;scope:string;coverage:string;verified_on:string;access:'internal'|'public';document_id:string};
|
||||
export type ProactiveAnalysis = {
|
||||
version:1;kind:'exists'|'needs'|'ideas';evidence:ProactiveEvidence[];
|
||||
need:{state:'hypothesis'|'confirmed';problem:string;observed_supply:string;observed_demand:string;type:string;clients:string;evidence_ids:string[];limitations:string;missing:string;confirmation:string};
|
||||
idea:{solution:string;activity:string;chain_id:string;stage:string;coverage:string;mode:string;resources:string;assumptions:string;conditions:string;pending:string;next_action:string};
|
||||
relations:{id:string;kind:string;target_id:string;relation:string;reference:string;evidence_ids:string[]}[];
|
||||
supply_comparisons?:{observation_id:string;relationship:'competition'|'complementarity'|'partner'|'uncertain';rationale:string;limitations:string}[];
|
||||
};
|
||||
export type ProactiveOptions = {documents?:{id:string;title:string}[];groups:Record<string,string>;references:Record<string,{id:string;name:string}[]>};
|
||||
export type ProactivePublic = {version?:number;group:string;state:string;need:string;proposal:string;basis:string;limitations:string;next_action:string;evidence_ids:string[];chain_id?:string|null;stage?:string|null;notice?:string;resources?:string;assumptions?:string;conditions?:string;relation_ids?:string[];relations?:{kind:string;target_id:string;label:string;relationship:string;basis:string}[];evidence?:Partial<ProactiveEvidence>[]};
|
||||
export type ProactivePublic = {version?:number;group:string;state:string;need:string;proposal:string;basis:string;limitations:string;next_action:string;evidence_ids:string[];chain_id?:string|null;stage?:string|null;notice?:string;resources?:string;assumptions?:string;conditions?:string;relation_ids?:string[];relations?:{kind:string;target_id:string;label:string;relationship:string;basis:string}[];evidence?:Partial<ProactiveEvidence>[];internal_comparisons?:{relationship:string;rationale:string;limitations:string;statement?:string;source?:string;locator?:string;period?:string}[]};
|
||||
export const proactiveNotice = 'Ausência de registo não comprova ausência de oferta. Proximidade não comprova contrato, participação, energia acessível, terra disponível ou benefício fiscal. Necessidade confirmada não comprova viabilidade comercial; hipótese de fornecimento não é encomenda.';
|
||||
export const newProactive = ():ProactiveAnalysis => ({version:1,kind:'ideas',evidence:[],need:{state:'hypothesis',problem:'',observed_supply:'',observed_demand:'',type:'',clients:'',evidence_ids:[],limitations:'',missing:'',confirmation:''},idea:{solution:'',activity:'',chain_id:'',stage:'',coverage:'',mode:'',resources:'',assumptions:'',conditions:'',pending:'',next_action:''},relations:[]});
|
||||
export const newPublicProactive = ():ProactivePublic => ({group:'ideas',state:'hypothesis',need:'',proposal:'',basis:'',limitations:'',next_action:'',evidence_ids:[],chain_id:'',stage:'',resources:'',assumptions:'',conditions:'',relation_ids:[]});
|
||||
|
||||
export function ProactiveInvestmentEditor({value,onChange,options,history=[]}:{value?:ProactiveAnalysis;onChange:(value:ProactiveAnalysis)=>void;options?:ProactiveOptions;history?:{version:number;at:string;state:string;problem:string;confirmation:string}[]}){
|
||||
export function ProactiveInvestmentEditor({value,onChange,options,history=[],territoryId,recordId}:{value?:ProactiveAnalysis;onChange:(value:ProactiveAnalysis)=>void;options?:ProactiveOptions;history?:{version:number;at:string;state:string;problem:string;confirmation:string}[];territoryId?:string;recordId?:number}){
|
||||
if(!value)return <section className="rounded border p-4"><h2 className="font-semibold">Análise territorial e preparação de ideias</h2><p>Comece pelo território e sector existentes, mesmo sem oportunidade publicada, promotor, montante ou coordenadas. O rascunho e a publicação são independentes.</p><button type="button" className={button} onClick={()=>onChange(newProactive())}>Preparar análise e ideia</button></section>;
|
||||
const patch=(p:Partial<ProactiveAnalysis>)=>onChange({...value,...p});
|
||||
const removeEvidence=(id:string)=>{const proof=value.evidence.find(e=>e.id===id);patch({evidence:value.evidence.filter(e=>e.id!==id),need:{...value.need,evidence_ids:value.need.evidence_ids.filter(x=>x!==id)},relations:value.relations.map(r=>({...r,evidence_ids:r.evidence_ids.filter(x=>x!==id)})),...(proof?.observation_id?{supply_comparisons:(value.supply_comparisons??[]).filter(c=>c.observation_id!==proof.observation_id)}:{})});};
|
||||
const text=(label:string,v:string,change:(v:string)=>void)=><Field label={label}><textarea className={control} aria-label={label} value={v??''} onChange={e=>change(e.target.value)}/></Field>;
|
||||
const select=(label:string,v:string,choices:Record<string,string>,change:(v:string)=>void)=><Field label={label}><select className={control} aria-label={label} value={v??''} onChange={e=>change(e.target.value)}><option value="">Por determinar</option>{Object.entries(choices).map(([key,label])=><option key={key} value={key}>{label}</option>)}</select></Field>;
|
||||
const evidenceChoice=(selected:string[],change:(v:string[])=>void)=><fieldset><legend>Evidências que fundamentam esta entrada</legend>{value.evidence.map((e,i)=><label key={e.id} className="block"><input type="checkbox" checked={selected.includes(e.id)} onChange={event=>change(event.target.checked?[...selected,e.id]:selected.filter(id=>id!==e.id))}/> Evidência {i+1}: {e.statement||'Por descrever'} ({e.access==='public'?'Pública autorizada':'Interna'})</label>)}</fieldset>;
|
||||
return <section className="space-y-4 rounded border p-4" aria-label="Análise territorial e ideia">
|
||||
<h2 className="text-xl font-semibold">Território e sector → evidências → necessidade → ideia</h2><details><summary>Como fundamentar esta análise</summary><p>Inventarie actividades, recursos, infra-estruturas e serviços de apoio. Confronte oferta observada com procura documentada e identifique os clientes ou beneficiários. Registe o período, cobertura e limitações de cada observação. Use referências internacionais para orientar as perguntas; a confirmação do caso depende de evidência local.</p></details><p>Reutiliza o sector e a localização principal escolhidos nesta ficha. Guarde pelo botão de rascunho existente. Nenhum montante, promotor ou ponto exacto é exigido.</p><p>{proactiveNotice}</p>
|
||||
{select('Tipo de conteúdo em preparação',value.kind,{exists:'O que existe',needs:'Necessidade identificada',ideas:'Ideia de investimento'},v=>patch({kind:v as ProactiveAnalysis['kind']}))}
|
||||
<ProactiveSupplyBrowser value={value} onChange={onChange} territoryId={territoryId} recordId={recordId}/>
|
||||
<fieldset className="space-y-3 rounded border p-3"><legend>Evidências e proveniência</legend><p>Registe factos e interpretações em campos separados. Listas de empresas, hotéis, restaurantes, contactos e documentos internos permanecem internos; não são copiados para a divulgação.</p>
|
||||
{value.evidence.map((e,i)=>{const change=(p:Partial<ProactiveEvidence>)=>patch({evidence:value.evidence.map(x=>x.id===e.id?{...x,...p}:x)});return <fieldset key={e.id} className="space-y-2 rounded border p-3"><legend>Evidência {i+1}</legend>
|
||||
{value.evidence.map((e,i)=>{const change=(p:Partial<ProactiveEvidence>)=>patch({evidence:value.evidence.map(x=>x.id===e.id?{...x,...p}:x)});if(e.observation_id)return <fieldset key={e.id} className="space-y-2 rounded border p-3"><legend>Evidência interna da oferta {i+1}</legend><p>{e.statement}</p><p>Fonte: {e.source} · {e.locator}</p><p>Período: {e.period} · Abrangência: {e.scope}</p><p>{e.coverage}</p><p>A proveniência é recuperada do original. A data indica consulta documental, sem confirmação de actividade actual.</p>{text(`Interpretação ou hipótese ${i+1}`,e.interpretation,v=>change({interpretation:v}))}<button type="button" onClick={()=>removeEvidence(e.id)}>Retirar evidência {i+1} e a sua comparação deste rascunho</button></fieldset>;return <fieldset key={e.id} className="space-y-2 rounded border p-3"><legend>Evidência {i+1}</legend>
|
||||
{select(`Finalidade da evidência ${i+1}`,e.purpose??'',{local:'Evidência local do caso',methodology:'Referência metodológica — não prova procura local'},v=>change({purpose:v as 'local'|'methodology'}))}
|
||||
{select(`Categoria da evidência ${i+1}`,e.category,{activity:'Actividade',resource:'Recurso',infrastructure:'Infra-estrutura',supply:'Oferta',demand:'Procura'},v=>change({category:v}))}
|
||||
{([['statement','Facto documentado'],['interpretation','Interpretação ou hipótese'],['source','Fonte e exemplar'],['locator','Localizador ou trecho'],['period','Período da evidência'],['scope','Abrangência'],['coverage','Cobertura e limitações']]as const).map(([key,label])=><div key={key}>{text(`${label} ${i+1}`,e[key],v=>change({[key]:v}))}</div>)}
|
||||
@@ -64,7 +68,7 @@ export function ProactiveInvestmentEditor({value,onChange,options,history=[]}:{v
|
||||
|
||||
export function ProactiveSummary({value}:{value?:ProactivePublic|null}){
|
||||
if(!value)return null;
|
||||
return <section className="space-y-2 rounded border p-4" aria-label="Origem proactiva do investimento"><h2 className="text-xl font-semibold">{value.group==='ideas'?'Ideia em preparação':value.group==='needs'?'Necessidade identificada':'O que existe'}</h2><p>{value.group==='exists'?'Registo documental; consultar abrangência, cobertura e limitações das fontes.':value.state==='confirmed'?'Necessidade confirmada no âmbito documentado':'Hipótese analítica; conclusão económica por confirmar'}</p>{([['need','Necessidade'],['proposal','Proposta'],['basis','Fundamento'],['limitations','Limitações'],['next_action','Próximo passo'],['stage','Etapa da cadeia'],['resources','Recursos'],['assumptions','Pressupostos'],['conditions','Condições']]as const).map(([key,label])=>value[key]?<p key={key}><strong>{label}:</strong> {value[key]}</p>:null)}{value.relations?.map(r=><p key={r.kind+r.target_id}>Relação seleccionada: {r.label} · {r.relationship==='documented'?'Documentada':r.relationship==='proximity'?'Proximidade, sem pertença':'Hipótese de relação'}. Fundamento: {r.basis}. Não confirma contrato, acesso ou participação.</p>)}{value.evidence?.map((e,i)=><p key={e.id??i}>Evidência pública: {e.statement}; fonte: {e.source}; localizador: {e.locator}; período: {e.period}; abrangência: {e.scope}; cobertura: {e.coverage}; verificação: {e.verified_on}.</p>)}<p>{value.notice??proactiveNotice}</p><p>Sem disponibilidade de participação, viabilidade comercial ou benefício fiscal presumidos.</p></section>;
|
||||
return <section className="space-y-2 rounded border p-4" aria-label="Origem proactiva do investimento"><h2 className="text-xl font-semibold">{value.group==='ideas'?'Ideia em preparação':value.group==='needs'?'Necessidade identificada':'O que existe'}</h2><p>{value.group==='exists'?'Registo documental; consultar abrangência, cobertura e limitações das fontes.':value.state==='confirmed'?'Necessidade confirmada no âmbito documentado':'Hipótese analítica; conclusão económica por confirmar'}</p>{([['need','Necessidade'],['proposal','Proposta'],['basis','Fundamento'],['limitations','Limitações'],['next_action','Próximo passo'],['stage','Etapa da cadeia'],['resources','Recursos'],['assumptions','Pressupostos'],['conditions','Condições']]as const).map(([key,label])=>value[key]?<p key={key}><strong>{label}:</strong> {value[key]}</p>:null)}{value.internal_comparisons?.map((c,i)=><div key={i} className="rounded border p-3"><strong>Comparação interna da oferta {i+1} · {({competition:'Possível concorrência',complementarity:'Possível complementaridade',partner:'Possível parceiro, sem compromisso',uncertain:'Relação por determinar'} as Record<string,string>)[c.relationship]}</strong><p>{c.statement}</p><p>Fonte: {c.source} · {c.locator} · {c.period}</p><p>{c.rationale}</p><p>Incertezas: {c.limitations}</p></div>)}{value.relations?.map(r=><p key={r.kind+r.target_id}>Relação seleccionada: {r.label} · {r.relationship==='documented'?'Documentada':r.relationship==='proximity'?'Proximidade, sem pertença':'Hipótese de relação'}. Fundamento: {r.basis}. Não confirma contrato, acesso ou participação.</p>)}{value.evidence?.map((e,i)=><p key={e.id??i}>Evidência pública: {e.statement}; fonte: {e.source}; localizador: {e.locator}; período: {e.period}; abrangência: {e.scope}; cobertura: {e.coverage}; verificação: {e.verified_on}.</p>)}<p>{value.notice??proactiveNotice}</p><p>Sem disponibilidade de participação, viabilidade comercial ou benefício fiscal presumidos.</p></section>;
|
||||
}
|
||||
|
||||
export function ProactivePublicationFields({value,onChange,evidence,relations=[],options,reviewed,onReview}:{value:ProactivePublic;onChange:(v:ProactivePublic)=>void;evidence:ProactiveEvidence[];relations?:{id:string;label:string;relationship:string}[];options?:ProactiveOptions;reviewed:boolean;onReview:(v:boolean)=>void}){
|
||||
@@ -86,5 +90,5 @@ export function proactiveView(raw:unknown):ProactivePublic|null{
|
||||
if(typeof p.group==='string'&&typeof p.need==='string')return p as ProactivePublic;
|
||||
const a=raw as ProactiveAnalysis;
|
||||
if(!a.need||typeof a.need.problem!=='string'||!a.idea)return null;
|
||||
return {version:1,group:a.kind,state:a.need.state,need:a.kind==='exists'?'':a.need.problem,proposal:a.kind==='ideas'?a.idea.solution:'',basis:a.need.confirmation||'Fundamento por completar',limitations:[a.need.limitations,a.need.missing,a.idea.assumptions,a.idea.conditions,a.idea.pending].filter(Boolean).join('; '),next_action:a.idea.next_action,evidence_ids:a.need.evidence_ids,chain_id:a.idea.chain_id,stage:a.kind==='ideas'?a.idea.stage:'',notice:'Origem privada autorizada deste dossier. '+proactiveNotice};
|
||||
return {version:1,group:a.kind,state:a.need.state,need:a.kind==='exists'?'':a.need.problem,proposal:a.kind==='ideas'?a.idea.solution:'',basis:a.need.confirmation||'Fundamento por completar',limitations:[a.need.limitations,a.need.missing,a.idea.assumptions,a.idea.conditions,a.idea.pending].filter(Boolean).join('; '),next_action:a.idea.next_action,evidence_ids:a.need.evidence_ids,chain_id:a.idea.chain_id,stage:a.kind==='ideas'?a.idea.stage:'',notice:'Origem privada autorizada deste dossier. '+proactiveNotice,internal_comparisons:(a.supply_comparisons??[]).map(c=>{const e=a.evidence.find(e=>e.observation_id===c.observation_id);return {...c,statement:e?.statement,source:e?.source,locator:e?.locator,period:e?.period};})};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import {useEffect,useState} from 'react';
|
||||
import {button,control,Field} from '@/pages/management/shared';
|
||||
import {createOperationKey} from '@/lib/operation-key';
|
||||
import type {ProactiveAnalysis} from './proactive-investment';
|
||||
|
||||
type Observation={id:string;name:string;province:string;district:string;territory_id:string;cae:string;activity:string;sector:string;source_date:string;sheet:string;row_number:number;source_hash:string;source_name:string;source_period:string;identity_conflict:boolean};
|
||||
type Result={installed:boolean;rows:Observation[];total:number|null;duplicate_sightings?:number;page?:number;per_page?:number;sectors?:string[];territory_coverage?:{unmapped:number;province_only:number};limitations:string;status:string};
|
||||
export function ProactiveSupplyBrowser({value,onChange,territoryId,recordId}:{value:ProactiveAnalysis;onChange:(v:ProactiveAnalysis)=>void;territoryId?:string;recordId?:number}){
|
||||
const [activity,setActivity]=useState('');const [sector,setSector]=useState('');
|
||||
const [result,setResult]=useState<Result|null>(null);const [busy,setBusy]=useState(false);const [error,setError]=useState('');
|
||||
const [query,setQuery]=useState<{territory:string;activity:string;sector:string;page:number}|null>(null);
|
||||
useEffect(()=>{setResult(null);setQuery(null);setSector('');setError('');setBusy(false);},[territoryId]);
|
||||
useEffect(()=>{
|
||||
if(!query)return;const controller=new AbortController();setBusy(true);setError('');
|
||||
const p=new URLSearchParams({territory_id:query.territory,activity:query.activity,sector:query.sector,page:String(query.page)});if(recordId)p.set('record_id',String(recordId));
|
||||
fetch('/minhas-oportunidades/oferta-interna?'+p,{headers:{Accept:'application/json'},credentials:'same-origin',signal:controller.signal})
|
||||
.then(async r=>{if(!r.ok)throw new Error(r.status===403?'Consulta interna fora do âmbito de acesso desta sessão.':'Não foi possível consultar a base interna. A oferta permanece por determinar.');return r.json() as Promise<Result>;})
|
||||
.then(setResult).catch(e=>{if(e.name!=='AbortError'){setResult(null);setError(e.message);}}).finally(()=>{if(!controller.signal.aborted)setBusy(false);});
|
||||
return ()=>controller.abort();
|
||||
},[query,recordId]);
|
||||
const selected=value.supply_comparisons??[];
|
||||
const compare=(row:Observation)=>{
|
||||
if(selected.some(c=>c.observation_id===row.id))return;
|
||||
const proof=value.evidence.find(e=>e.observation_id===row.id);
|
||||
onChange({...value,supply_comparisons:[...selected,{observation_id:row.id,relationship:'uncertain',rationale:'',limitations:'Actividade actual, capacidade, procura e disponibilidade para parceria por confirmar.'}],evidence:proof?value.evidence:[...value.evidence,{
|
||||
id:createOperationKey(),observation_id:row.id,category:'supply',purpose:'local',access:'internal',document_id:'',
|
||||
statement:`Registo administrativo: ${row.name}; CAE ${row.cae}; ${row.activity||'actividade por classificar'}; ${row.district}, ${row.province}. Não confirma actividade actual.`,
|
||||
interpretation:'',source:`${row.source_name}; SHA256 ${row.source_hash}`,locator:`${row.sheet}!linha ${row.row_number}`,
|
||||
period:`${row.source_date}; ${row.source_period}`,scope:`${row.district}, ${row.province}`,
|
||||
coverage:result?.limitations??'Cobertura incompleta; situação actual por confirmar.',verified_on:new Date().toISOString().slice(0,10),
|
||||
}]});
|
||||
};
|
||||
return <fieldset className="space-y-3 rounded border p-3"><legend>Consultar oferta existente — base interna</legend>
|
||||
<p>Consulta a localização escolhida nesta ficha e os seus territórios subordinados. Os filtros por sector e actividade CAE não limitam a análise ao sector da ideia: podem existir concorrentes, complementos e parceiros noutros sectores. Nenhuma empresa é publicada.</p>
|
||||
<Field label="Sector da oferta (classificação CAE)"><select className={control} aria-label="Sector da oferta (classificação CAE)" value={sector} onChange={e=>setSector(e.target.value)}><option value="">Todos os sectores registados</option>{result?.sectors?.map(s=><option key={s}>{s}</option>)}</select></Field>
|
||||
<Field label="Actividade ou código CAE da oferta"><input className={control} aria-label="Actividade ou código CAE da oferta" value={activity} onChange={e=>setActivity(e.target.value)}/></Field>
|
||||
<button type="button" className={button} disabled={!territoryId||busy} onClick={()=>setQuery({territory:territoryId!,activity,sector,page:1})}>{busy?'A consultar…':'Consultar oferta da localização'}</button>
|
||||
{!territoryId&&<p>Escolha a localização para consultar. Sem localização, a oferta permanece por determinar.</p>}
|
||||
{error&&<p role="alert">{error}</p>}
|
||||
{result&&<><p role="status">{result.status}</p><p>{result.limitations}</p>{result.territory_coverage&&<p>Cobertura territorial da base nacional: {result.territory_coverage.unmapped} observações sem correspondência territorial única; {result.territory_coverage.province_only} apenas com província. Estas últimas não comprovam oferta num distrito específico.</p>}{result.total!==null&&<p>{result.total} observações distintas; {result.duplicate_sightings??0} repetições dos mesmos campos administrativos conservadas na origem. Não é uma contagem de empresas únicas.</p>}
|
||||
{result.rows.map(row=><article key={row.id} className="rounded border p-3"><h3 className="font-semibold">{row.name}</h3><p>{row.district}, {row.province} · {row.sector||'Sector por classificar'} · CAE {row.cae||'não indicado'} · {row.activity||'Actividade por classificar'}</p><p>Fonte: {row.source_name} · {row.sheet}, linha {row.row_number} · {row.source_date} · {row.source_period}</p><details><summary>Rastreabilidade do exemplar</summary><p>SHA256: {row.source_hash}</p><p>Observação: {row.id}</p></details>{row.identity_conflict&&<p>Identificador partilhado por denominações divergentes; identidade por conferir. Os registos não foram fundidos.</p>}<button type="button" className={button} disabled={busy||selected.some(c=>c.observation_id===row.id)||selected.length>=40||value.evidence.length>=60} onClick={()=>compare(row)}>Comparar com a ideia</button></article>)}
|
||||
{query&&<div className="flex gap-3"><button type="button" disabled={busy||query.page<=1} onClick={()=>setQuery({...query,page:query.page-1})}>Página anterior</button><span>Página {result.page??1}</span><button type="button" disabled={busy||query.page*25>=(result.total??0)} onClick={()=>setQuery({...query,page:query.page+1})}>Página seguinte</button></div>}
|
||||
</>}
|
||||
{selected.map((c,i)=>{const proof=value.evidence.find(e=>e.observation_id===c.observation_id);const patch=(p:Partial<typeof c>)=>onChange({...value,supply_comparisons:selected.map(x=>x.observation_id===c.observation_id?{...x,...p}:x)});return <fieldset className="space-y-2 rounded border p-3" key={c.observation_id}><legend>Comparação {i+1}</legend><p>{proof?.statement??'Evidência retirada: volte a associar a observação antes de guardar.'}</p><p>{proof?.source} · {proof?.locator}</p><p>Ideia em preparação: {value.idea.solution||'Descreva a ideia no campo existente abaixo.'}</p><Field label={`Relação com a ideia ${i+1}`}><select className={control} value={c.relationship} onChange={e=>patch({relationship:e.target.value as typeof c.relationship})}><option value="uncertain">Relação por determinar</option><option value="competition">Possível concorrência</option><option value="complementarity">Possível complementaridade</option><option value="partner">Possível parceiro — sem compromisso</option></select></Field><Field label={`Fundamento da comparação ${i+1}`}><textarea className={control} value={c.rationale} onChange={e=>patch({rationale:e.target.value})}/></Field><Field label={`Incertezas da comparação ${i+1}`}><textarea className={control} value={c.limitations} onChange={e=>patch({limitations:e.target.value})}/></Field><button type="button" onClick={()=>onChange({...value,supply_comparisons:selected.filter(x=>x.observation_id!==c.observation_id)})}>Retirar comparação {i+1}</button></fieldset>;})}
|
||||
{!!selected.length&&<p>Use estas comparações para fundamentar a necessidade abaixo e seleccione as evidências correspondentes. A fonte administrativa não confirma procura nem insuficiência de oferta. Guarde no rascunho existente; a continuidade para o dossier conserva o percurso já entregue.</p>}
|
||||
</fieldset>;
|
||||
}
|
||||
@@ -485,7 +485,7 @@ export default function OpportunityDraft({
|
||||
<p>Seleccione apenas o nível territorial conhecido; não exige coordenadas, montante ou promotor.</p>
|
||||
{(['province_id','district_id','admin_post_id','locality_id']as const).map((key,index)=><TerritoryLevel key={`proactive-${key}-${path[index-1]?.id??''}`} index={index} parent={path[index-1]?.id??''} selected={path[index]} endpoint="/minhas-oportunidades/territory-options" fieldError={errors[key]} onChange={value=>{const next=[...path.slice(0,index),...(value?[value]:[])];setPath(next);setData(d=>({...d,province_id:next[0]?.id??'',district_id:next[1]?.id??'',admin_post_id:next[2]?.id??'',locality_id:next[3]?.id??''}));}}/>)}
|
||||
</fieldset>}
|
||||
<fieldset disabled={busy || readOnly || unavailable}><ProactiveInvestmentEditor value={data.proactive as ProactiveAnalysis|undefined} onChange={v=>setData(d=>({...d,proactive:v}))} options={proactiveOptions} history={proactiveHistory}/></fieldset>
|
||||
<fieldset disabled={busy || readOnly || unavailable}><ProactiveInvestmentEditor value={data.proactive as ProactiveAnalysis|undefined} onChange={v=>setData(d=>({...d,proactive:v}))} options={proactiveOptions} history={proactiveHistory} territoryId={path.at(-1)?.id} recordId={record?.id}/></fieldset>
|
||||
{!profileMode && (
|
||||
<fieldset
|
||||
disabled={busy || readOnly || unavailable}
|
||||
|
||||
@@ -166,6 +166,7 @@ Route::middleware(['auth', 'verified', EnsurePortalProfileAdministration::class]
|
||||
});
|
||||
|
||||
Route::middleware(['auth', 'verified', EnsurePortalProfiles::class])->group(function () {
|
||||
Route::get('/minhas-oportunidades/oferta-interna', [\App\Http\Controllers\ProactiveSupplyController::class,'index'])->middleware('throttle:60,1');
|
||||
$profiles = PortalProfileController::class;
|
||||
Route::get('/minhas-oportunidades', [$profiles, 'opportunities']);
|
||||
Route::get('/minhas-oportunidades/lotes/agronegocio', [\App\Http\Controllers\CorridorAgribusinessController::class, 'show']);
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Opportunity;
|
||||
use App\Models\User;
|
||||
use App\Services\OpportunityManagement;
|
||||
use App\Services\ProactiveSupply;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\Support\ProactiveInvestmentFixture as F;
|
||||
use Tests\TestCase;
|
||||
|
||||
final class ProactiveSupplyTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
private User $actor;
|
||||
private string $territory;
|
||||
private string $observation;
|
||||
protected function setUp():void
|
||||
{
|
||||
parent::setUp();F::guard();$this->withoutVite();
|
||||
config(['portal.profile_access_enabled'=>true,'portal.opportunity_specialized_drafts_enabled'=>true,'portal.managers'=>[],'investor-path.enabled'=>true,'suitecrm.enabled'=>false,'investmoz_ai.enabled'=>false]);
|
||||
$this->actor=F::operator();$this->login($this->actor);
|
||||
$input=F::input('Gurué');$this->territory=$input['district_id'];
|
||||
$hash=hash('sha256','SYNTHETIC-QA-SUPPLY-SOURCE');
|
||||
DB::table('proactive_supply_sources')->insert(['sha256'=>$hash,'name'=>'SYNTHETIC-QA-SUPPLY.xlsx','period'=>'2020','limitations'=>'Synthetic, not a real market source','receipt'=>'{}','imported_at'=>now()]);
|
||||
$this->observation=hash('sha256','SYNTHETIC-QA-OBSERVATION');
|
||||
$row=['id'=>$this->observation,'source_hash'=>$hash,'sheet'=>'QA','row_number'=>2,'entity_key'=>hash('sha256','QA-ENTITY'),'name'=>'PRIVATE-UNIQUE-QA-SUPPLIER','province'=>'Zambézia','district'=>'Gurué','cae'=>'33120','activity'=>'SYNTHETIC QA repair','sector'=>'SYNTHETIC QA manufacturing','source_date'=>'2020','fingerprint'=>hash('sha256','QA-FINGERPRINT'),'duplicate_of'=>null,'identity_conflict'=>false,'territory_id'=>$this->territory,'territory_resolution'=>'exact_normalized_province_district'];
|
||||
DB::table('proactive_supply_observations')->insert($row);
|
||||
DB::table('proactive_supply_observations')->insert([...$row,'id'=>hash('sha256','QA-REPEATED-OBSERVATION'),'row_number'=>3,'duplicate_of'=>$this->observation]);
|
||||
}
|
||||
private function login(User $u):void{$this->actingAs($u)->withSession(['portal.mfa_actor'=>$u->id,'portal.mfa_at'=>time(),'portal.access_revision'=>(int)$u->portal_access_revision]);}
|
||||
private function url(string $extra=''):string{return '/minhas-oportunidades/oferta-interna?territory_id='.$this->territory.$extra;}
|
||||
public function test_private_query_filters_supply_and_preserves_source_and_duplicate_count():void
|
||||
{
|
||||
$r=$this->getJson($this->url('&activity=SYNTHETIC%20QA%20repair'))->assertOk()->assertJsonPath('total',1)->assertJsonPath('duplicate_sightings',1)->assertJsonPath('rows.0.source_name','SYNTHETIC-QA-SUPPLY.xlsx')->assertJsonPath('rows.0.row_number',2)->assertJsonPath('rows.0.source_date','2020');
|
||||
$this->assertStringContainsString('no-store',$r->headers->get('Cache-Control'));
|
||||
$this->assertArrayNotHasKey('contact',$r->json('rows.0'));
|
||||
$this->assertArrayNotHasKey('nuit',$r->json('rows.0'));
|
||||
}
|
||||
public function test_no_match_means_insufficient_information_not_absent_market():void
|
||||
{
|
||||
$this->getJson($this->url('&activity=NO-SUCH-SYNTHETIC-ACTIVITY'))->assertOk()->assertJsonPath('total',0)->assertJsonPath('status','Informação insuficiente nesta base e nestes filtros.');
|
||||
}
|
||||
public function test_unassigned_user_cannot_read_internal_register():void
|
||||
{
|
||||
$this->login(User::factory()->create());$this->getJson($this->url())->assertForbidden();
|
||||
$this->getJson('/minhas-oportunidades/oferta-interna?territory_id=not-a-uuid')->assertForbidden();
|
||||
}
|
||||
public function test_comparison_persists_source_lineage_and_rejects_forgery_and_public_reclassification():void
|
||||
{
|
||||
$input=F::input('Gurué');$a=$input['proactive'];$id=(string)\Illuminate\Support\Str::uuid();
|
||||
$a['evidence'][]=['id'=>$id,'interpretation'=>'SYNTHETIC QA possible complement, not a contract','verified_on'=>'2026-10-28',...app(ProactiveSupply::class)->evidence($this->actor,$this->observation,null)];
|
||||
$a['supply_comparisons']=[['observation_id'=>$this->observation,'relationship'=>'complementarity','rationale'=>'SYNTHETIC QA compare servicing with tea-chain idea','limitations'=>'No current capacity or demand proved']];
|
||||
$input['proactive']=$a;
|
||||
$oid=$this->postJson('/minhas-oportunidades/drafts',$input)->assertCreated()->json('id');$o=Opportunity::findOrFail($oid);
|
||||
$saved=app(OpportunityManagement::class)->values($o)['proactive'];$this->assertSame($this->observation,$saved['supply_comparisons'][0]['observation_id']);
|
||||
$this->assertSame('internal',$saved['evidence'][2]['access']);$this->assertStringContainsString('SYNTHETIC-QA-SUPPLY.xlsx',$saved['evidence'][2]['source']);
|
||||
$a['evidence'][2]['source']='Forged source';$a['evidence'][2]['statement']='Forged supply fact';
|
||||
$this->putJson('/minhas-oportunidades/'.$oid.'/draft',['etag'=>app(OpportunityManagement::class)->etag($o),'proactive'=>$a,'reason'=>'SYNTHETIC QA canonical source lineage is server-resolved'])->assertOk();
|
||||
$saved=app(OpportunityManagement::class)->values($o->fresh())['proactive'];$this->assertStringContainsString('PRIVATE-UNIQUE-QA-SUPPLIER',$saved['evidence'][2]['statement']);
|
||||
$a['evidence'][2]['access']='public';$this->putJson('/minhas-oportunidades/'.$oid.'/draft',['etag'=>app(OpportunityManagement::class)->etag($o),'proactive'=>$a,'reason'=>'SYNTHETIC QA internal list cannot become public'])->assertUnprocessable();
|
||||
$a['evidence'][2]['access']='internal';$a['supply_comparisons'][0]['observation_id']=str_repeat('f',64);
|
||||
$this->putJson('/minhas-oportunidades/'.$oid.'/draft',['etag'=>app(OpportunityManagement::class)->etag($o),'proactive'=>$a,'reason'=>'SYNTHETIC QA unknown supply observation rejected'])->assertUnprocessable();
|
||||
}
|
||||
public function test_sector_filter_does_not_infer_absence_and_comparison_requires_its_evidence():void
|
||||
{
|
||||
$this->getJson($this->url('§or=NO-SUCH-SECTOR'))->assertOk()->assertJsonPath('total',0);
|
||||
$input=F::input('Gurué');$input['proactive']['supply_comparisons']=[['observation_id'=>$this->observation,'relationship'=>'partner','rationale'=>'SYNTHETIC QA prospective partner','limitations'=>'Not contacted, no agreement']];
|
||||
$this->postJson('/minhas-oportunidades/drafts',$input)->assertUnprocessable();
|
||||
}
|
||||
public function test_database_policy_hides_internal_rows_without_actor_context():void
|
||||
{
|
||||
$role='supply_qa_'.bin2hex(random_bytes(6));
|
||||
DB::statement('CREATE ROLE '.$role.' NOLOGIN');
|
||||
DB::statement('GRANT USAGE ON SCHEMA public TO '.$role);
|
||||
DB::statement('GRANT SELECT ON proactive_supply_sources,proactive_supply_observations TO '.$role);
|
||||
DB::select("select set_config('app.actor_user_id','',true),set_config('app.proactive_supply_read','',true)");
|
||||
try{
|
||||
DB::statement('SET LOCAL ROLE '.$role);
|
||||
$this->assertSame(0,DB::table('proactive_supply_observations')->count());
|
||||
$this->assertSame(0,DB::table('proactive_supply_sources')->count());
|
||||
DB::select("select set_config('app.actor_user_id',?,true),set_config('app.proactive_supply_read','internal',true)",[(string)$this->actor->id]);
|
||||
$this->assertNotNull(DB::table('proactive_supply_observations')->where('id',$this->observation)->first());
|
||||
}finally{DB::statement('RESET ROLE');}
|
||||
}
|
||||
public function test_preserved_repetitions_cannot_inflate_saved_comparisons():void
|
||||
{
|
||||
$this->expectException(\Illuminate\Validation\ValidationException::class);
|
||||
$this->expectExceptionMessage('Esta observação administrativa é repetida.');
|
||||
$comparison=['relationship'=>'competition','rationale'=>'SYNTHETIC QA duplicate must not count as another competitor','limitations'=>'Historical observation,not a unique company'];
|
||||
app(ProactiveSupply::class)->comparisons($this->actor,[
|
||||
[...$comparison,'observation_id'=>$this->observation],
|
||||
[...$comparison,'observation_id'=>hash('sha256','QA-REPEATED-OBSERVATION')],
|
||||
],null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
"""Map only unique canonical names within the same province, then export privately."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sqlite3
|
||||
import unicodedata
|
||||
|
||||
def norm(v):
|
||||
return ' '.join(''.join(c for c in unicodedata.normalize('NFKD',v).casefold() if not unicodedata.combining(c)).split())
|
||||
|
||||
p=argparse.ArgumentParser()
|
||||
p.add_argument('--preview',required=True)
|
||||
p.add_argument('--geography',required=True)
|
||||
a=p.parse_args()
|
||||
root=Path(a.preview)
|
||||
geos=json.loads(Path(a.geography).read_text(encoding='utf-8'))
|
||||
byid={r['id']:r for r in geos}
|
||||
territories={}
|
||||
provinces={}
|
||||
subterritories={}
|
||||
def province_name(v):
|
||||
n=norm(v)
|
||||
return 'maputo provincia' if n=='maputo' else n
|
||||
for g in geos:
|
||||
if g['location_type']=='province':provinces.setdefault(norm(g['name']),[]).append(g['id'])
|
||||
if g['location_type'] not in ('district','admin_post','locality'):continue
|
||||
parent=byid.get(g['parent_id']);visited=set()
|
||||
while parent and parent['location_type']!='province' and parent['id'] not in visited:
|
||||
visited.add(parent['id']);parent=byid.get(parent['parent_id'])
|
||||
if not parent:continue
|
||||
target=territories if g['location_type']=='district' else subterritories
|
||||
target.setdefault((norm(parent['name']),norm(g['name'])),[]).append(g['id'])
|
||||
db=sqlite3.connect(root/'supply-preview.sqlite')
|
||||
db.row_factory=sqlite3.Row
|
||||
resolution={}
|
||||
with (root/'observations.jsonl').open('w',encoding='utf-8',newline='\n') as f:
|
||||
for row in db.execute('select * from records order by source_hash,sheet,row_number'):
|
||||
r=dict(row)
|
||||
key=(province_name(r['province']),norm(r['district']))
|
||||
hits=territories.get(key,[])
|
||||
lower_level=False
|
||||
if not hits:
|
||||
hits=subterritories.get(key,[])
|
||||
lower_level=bool(hits)
|
||||
province_hits=provinces.get(province_name(r['province']),[])
|
||||
province_only=not r['district'] and len(province_hits)==1
|
||||
r['territory_id']=hits[0] if len(hits)==1 else (province_hits[0] if province_only else None)
|
||||
r['territory_resolution']=('exact_normalized_province_subterritory' if lower_level else 'exact_normalized_province_district') if len(hits)==1 else ('province_only_district_unknown' if province_only else ('ambiguous' if hits else 'unresolved'))
|
||||
r['identity_conflict']=bool(r['identity_conflict'])
|
||||
resolution[r['territory_resolution']]=resolution.get(r['territory_resolution'],0)+1
|
||||
f.write(json.dumps(r,ensure_ascii=False)+'\n')
|
||||
result={'rows':db.execute('select count(*) from records').fetchone()[0],'territory_resolution':resolution,'sha256':hashlib.file_digest((root/'observations.jsonl').open('rb'),'sha256').hexdigest(),'mapping':'case/accent/whitespace; exact district in province, otherwise unique named subordinate territory within that province; source label Maputo distinct from Maputo Cidade maps explicitly to Maputo Província; city names retain canonical administrative level, no fuzzy matching','publication':False}
|
||||
(root/'export.json').write_text(json.dumps(result,ensure_ascii=False,indent=2),encoding='utf-8')
|
||||
print(json.dumps(result,ensure_ascii=False))
|
||||
@@ -0,0 +1,136 @@
|
||||
"""Controlled delivery of the accepted internal snapshot. Never publishes entities.
|
||||
|
||||
Preview is the default. Apply is an authenticated deployment operation, guarded by
|
||||
the exact destination, accepted source hashes and a fresh verified backup/restore.
|
||||
The rehearsal uses a disposable network-isolated PostgreSQL container only.
|
||||
"""
|
||||
import argparse
|
||||
import datetime as dt
|
||||
import gzip
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
BASELINE = '569a1cd750f3d2a0b87f130e992e73e070bbd1ab'
|
||||
RAW_SHA256 = 'dec9db62a52a8bac5f1e2675aecbeb4239f2b1f4fa521db01693c32bdfeef01e'
|
||||
ROWS = 361464
|
||||
FIELDS = ['id','source_hash','sheet','row_number','entity_key','name','province','district','cae','activity','sector','source_date','fingerprint','duplicate_of','identity_conflict','territory_id','territory_resolution']
|
||||
SOURCES = {
|
||||
'1ba4c4114b16b407e7746712d00d4f681246bbbc1b42351e96a31f7129acad30':148556,
|
||||
'1b5087b464e39e88c42def39655c2391aaaf684b4c832d6a77ff274b98889f00':212631,
|
||||
'f02e086ed4973ef17234aa6cc54b98fbb7a6952ffb16ec4dc4f7d0c72af6400c':277,
|
||||
'83b4f1200988e99834c2ae3915346fbf237d128308554b67b5a080d4956bf506':0,
|
||||
}
|
||||
|
||||
def csv_value(value):
|
||||
if value is None:return r'\N'
|
||||
if isinstance(value,bool):value='t' if value else 'f'
|
||||
value=str(value)
|
||||
if any(c in value for c in ('\x00','\r','\n')):raise ValueError('Unexpected multiline/NUL source projection')
|
||||
return '"'+value.replace('"','""')+'"'
|
||||
|
||||
def fail_unless(ok,message):
|
||||
if not ok:raise RuntimeError(message)
|
||||
|
||||
def sql_command(container=None):
|
||||
if container:return ['docker','exec','-i',container,'psql','-p','55441','-U','postgres','-d','proactive_supply_delivery_20261028_test','-XqAt','-v','ON_ERROR_STOP=1']
|
||||
return ['runuser','-u','postgres','--','psql','-d','teste_investidor','-XqAt','-v','ON_ERROR_STOP=1']
|
||||
|
||||
def query(command,text):
|
||||
return subprocess.check_output(command,input=text.encode(),stderr=subprocess.PIPE).decode().strip()
|
||||
|
||||
def prepare(package,output):
|
||||
manifest=json.loads((package/'import-manifest.json').read_text())
|
||||
preview=json.loads((package/'preview.json').read_text())
|
||||
scan=json.loads((package/'scan.json').read_text())
|
||||
schema=package/'schema.sql'
|
||||
fail_unless(hashlib.file_digest(schema.open('rb'),'sha256').hexdigest()==manifest['schema_sql_sha256'],'Schema SQL differs from reviewed candidate migration')
|
||||
fail_unless(manifest['candidate']=='a022ed9848b41ba342b4209414c2bbf2ebd0a7a3','Unexpected candidate')
|
||||
fail_unless(set(s['sha256']for s in preview['sources'])==set(SOURCES),'Unexpected originals')
|
||||
fail_unless(scan['exit_code']==0 and set(scan['source_hashes'])==set(SOURCES),'Hash-specific source scan receipt missing')
|
||||
fail_unless(preview['records']==ROWS,'Incomplete preview')
|
||||
fail_unless(hashlib.file_digest((package/'observations.jsonl.gz').open('rb'),'sha256').hexdigest()==manifest['compressed_sha256'],'Compressed snapshot changed')
|
||||
rawhash=hashlib.sha256();counts=dict.fromkeys(SOURCES,0);n=0
|
||||
seen=set()
|
||||
with output.open('w',encoding='utf-8',newline='\n')as sql:
|
||||
os.chmod(output,0o600)
|
||||
sql.write("BEGIN;\nSET LOCAL lock_timeout='10s';\nSELECT pg_advisory_xact_lock(hashtext('proactive-supply-delivery-v1'));\n")
|
||||
sql.write("CREATE TEMP TABLE supply_delivery_sources (LIKE public.proactive_supply_sources INCLUDING DEFAULTS) ON COMMIT DROP;\n")
|
||||
sql.write("CREATE TEMP TABLE supply_delivery_observations (LIKE public.proactive_supply_observations INCLUDING DEFAULTS) ON COMMIT DROP;\n")
|
||||
sql.write("COPY supply_delivery_sources (sha256,name,period,limitations,receipt,imported_at) FROM STDIN WITH (FORMAT csv,NULL '\\N');\n")
|
||||
now=dt.datetime.now(dt.timezone.utc).isoformat()
|
||||
for s in preview['sources']:
|
||||
receipt={'original':s,'transport_sha256':RAW_SHA256,'candidate':manifest['candidate'],'scan':scan,'access':'internal','publication':False,'counting':'administrative observations;not unique companies;repeated projections retained but excluded from query total'}
|
||||
sql.write(','.join(map(csv_value,[s['sha256'],s['name'],s['period'],preview['limitations'],json.dumps(receipt,ensure_ascii=False),now]))+'\n')
|
||||
sql.write('\\.\n')
|
||||
sql.write("COPY supply_delivery_observations ("+','.join(FIELDS)+") FROM STDIN WITH (FORMAT csv,NULL '\\N');\n")
|
||||
with gzip.open(package/'observations.jsonl.gz','rb')as stream:
|
||||
for line in stream:
|
||||
rawhash.update(line);r=json.loads(line)
|
||||
fail_unless(set(r)==set(FIELDS),'Unexpected observation fields')
|
||||
fail_unless(r['source_hash']in SOURCES,'Unknown source')
|
||||
expected=hashlib.sha256((r['source_hash']+'|'+r['sheet']+'|'+str(r['row_number'])).encode()).hexdigest()
|
||||
fail_unless(r['id']==expected and r['id']not in seen,'Invalid or repeated row identity')
|
||||
seen.add(r['id']);n+=1;counts[r['source_hash']]+=1
|
||||
sql.write(','.join(csv_value(r[k])for k in FIELDS)+'\n')
|
||||
sql.write('\\.\n')
|
||||
fail_unless(rawhash.hexdigest()==RAW_SHA256 and n==ROWS and counts==SOURCES,'Snapshot checksum/count mismatch')
|
||||
sql.write(r'''
|
||||
CREATE UNIQUE INDEX supply_delivery_observations_id ON supply_delivery_observations(id);
|
||||
ANALYZE supply_delivery_observations;
|
||||
DO $$ BEGIN
|
||||
IF (SELECT count(*) FROM supply_delivery_observations)<>361464 THEN RAISE EXCEPTION 'Incomplete internal base'; END IF;
|
||||
IF (SELECT count(*) FROM supply_delivery_observations WHERE duplicate_of IS NOT NULL)<>147165 THEN RAISE EXCEPTION 'Repeated projection count differs'; END IF;
|
||||
IF EXISTS(SELECT 1 FROM supply_delivery_observations x LEFT JOIN supply_delivery_observations y ON y.id=x.duplicate_of WHERE x.duplicate_of IS NOT NULL AND (y.id IS NULL OR x.id=y.id OR x.fingerprint<>y.fingerprint)) THEN RAISE EXCEPTION 'Invalid duplicate lineage'; END IF;
|
||||
IF EXISTS(SELECT 1 FROM supply_delivery_observations x LEFT JOIN public.geo_locations g ON g.id=x.territory_id WHERE x.territory_id IS NOT NULL AND (g.id IS NULL OR g.deleted_at IS NOT NULL)) THEN RAISE EXCEPTION 'Destination geography differs'; END IF;
|
||||
IF EXISTS(SELECT 1 FROM supply_delivery_observations x JOIN public.proactive_supply_observations y ON x.id=y.id WHERE to_jsonb(x)<>to_jsonb(y)) THEN RAISE EXCEPTION 'Previously installed observation differs;no overwrite'; END IF;
|
||||
IF EXISTS(SELECT 1 FROM supply_delivery_sources x JOIN public.proactive_supply_sources y ON x.sha256=y.sha256 WHERE (x.name,x.period,x.limitations) IS DISTINCT FROM (y.name,y.period,y.limitations)) THEN RAISE EXCEPTION 'Existing original metadata differs'; END IF;
|
||||
END $$;
|
||||
INSERT INTO public.proactive_supply_sources SELECT * FROM supply_delivery_sources ON CONFLICT(sha256) DO NOTHING;
|
||||
INSERT INTO public.proactive_supply_observations SELECT * FROM supply_delivery_observations ON CONFLICT(id) DO NOTHING;
|
||||
SELECT json_build_object('records',count(*),'distinct_observations',count(*) FILTER(WHERE duplicate_of IS NULL),'repeated_projections',count(*) FILTER(WHERE duplicate_of IS NOT NULL),'not_unique_companies',true,'published',false) FROM public.proactive_supply_observations;
|
||||
COMMIT;
|
||||
''')
|
||||
return {'candidate':manifest['candidate'],'records':n,'source_counts':counts,'raw_sha256':rawhash.hexdigest(),'sql_bytes':output.stat().st_size,'mode':'prepared;no database write','unique_company_count':None,'duplicate_suppression':'WHERE duplicate_of IS NULL;retained administrative sightings do not count as separate offers','national_coverage':'partial','investment_conclusion':'hypothesis only;does not prove demand or viability'}
|
||||
|
||||
def main():
|
||||
p=argparse.ArgumentParser();p.add_argument('--package',type=Path,required=True);p.add_argument('--work',type=Path,required=True);p.add_argument('--mode',choices=['preview','rehearsal','apply'],default='preview');p.add_argument('--container');p.add_argument('--backup',type=Path)
|
||||
a=p.parse_args();a.work.mkdir(mode=0o700,parents=True,exist_ok=True);output=a.work/'controlled-import.sql'
|
||||
receipt=prepare(a.package,a.work/'controlled-import.sql')
|
||||
if a.mode=='preview':print(json.dumps(receipt));return
|
||||
fail_unless(os.geteuid()==0,'Authenticated technical deployment identity required')
|
||||
if a.mode=='rehearsal':
|
||||
fail_unless(a.container is not None,'Disposable QA container required')
|
||||
info=json.loads(subprocess.check_output(['docker','inspect',a.container]))[0]
|
||||
fail_unless(info['HostConfig']['NetworkMode']=='none' and not info['HostConfig'].get('PortBindings'),'QA must have no network or host ports')
|
||||
else:
|
||||
fail_unless(not a.container and os.environ.get('SSH_CONNECTION'),'Existing authenticated SSH deployment session required')
|
||||
installed=json.loads(Path('/var/lib/investmoz-deploy/installed.json').read_text())
|
||||
fail_unless(installed['commit']==BASELINE,'Reconcile installed code before preloading the new private base')
|
||||
fail_unless(a.backup is not None,'Fresh backup/restore receipt required')
|
||||
base=a.backup.resolve();fail_unless(base.is_relative_to('/var/backups/investmoz-proactive-supply-delivery-20261028'),'Unexpected backup root')
|
||||
b=json.loads((base/'backup-receipt.json').read_text());r=json.loads((base/'restore-receipt.json').read_text())
|
||||
stamp=dt.datetime.fromisoformat(b['created_at'].replace('Z','+00:00'))
|
||||
fail_unless(dt.timedelta(0)<=dt.datetime.now(dt.timezone.utc)-stamp<=dt.timedelta(hours=24),'Backup is no longer fresh;take another before apply')
|
||||
fail_unless(b['target']['database']=='teste_investidor' and int(b['target']['port'])==5432,'Backup identity mismatch')
|
||||
fail_unless(r['restore_verified'] and r['source_dump_sha256']==b['sha256'],'Restore does not match backup')
|
||||
fail_unless(hashlib.file_digest((base/'database.dump').open('rb'),'sha256').hexdigest()==b['sha256'],'Backup bytes changed')
|
||||
command=sql_command(a.container)
|
||||
identity=query(command,"SELECT current_database()||'|'||current_setting('port');")
|
||||
expected='proactive_supply_delivery_20261028_test|55441'if a.mode=='rehearsal'else'teste_investidor|5432'
|
||||
fail_unless(identity==expected,'Wrong actual database identity')
|
||||
# Existing pipeline does not execute migrations. Schema installation is a
|
||||
# separate, reviewed deployment step; this loader never creates it on active.
|
||||
fail_unless(query(command,"SELECT count(*) FROM pg_class WHERE oid IN(to_regclass('public.proactive_supply_sources'),to_regclass('public.proactive_supply_observations')) AND relrowsecurity AND relforcerowsecurity;")=='2','Reviewed private schema/RLS not installed')
|
||||
before=query(command,'SELECT count(*) FROM public.proactive_supply_observations;')
|
||||
with output.open('rb')as stream:
|
||||
result=subprocess.run(command,stdin=stream,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
|
||||
fail_unless(result.returncode==0,'Import rolled back;'+result.stderr.decode(errors='replace')[-1000:])
|
||||
lines=[line for line in result.stdout.decode().splitlines()if line.startswith('{')]
|
||||
receipt.update(mode=a.mode,database=identity,before=int(before),after=json.loads(lines[-1]),idempotent_no_overwrite=True,active_existing_business_data_touched=False)
|
||||
(a.work/'import-receipt.json').write_text(json.dumps(receipt,indent=2));os.chmod(a.work/'import-receipt.json',0o600)
|
||||
print(json.dumps(receipt))
|
||||
|
||||
if __name__=='__main__':main()
|
||||
@@ -0,0 +1,181 @@
|
||||
"""Read-only XLSX ingestion into a private, indexed analysis snapshot. No publication.
|
||||
|
||||
Every source row remains addressable. Matching identifiers suggest the same entity;
|
||||
they never imply a current trading status, contract, or an exhaustive census.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sqlite3
|
||||
import unicodedata
|
||||
import xml.etree.ElementTree as ET
|
||||
import zipfile
|
||||
|
||||
NS = '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}'
|
||||
SOURCES = {
|
||||
'companies.xlsx': ('1ba4c4114b16b407e7746712d00d4f681246bbbc1b42351e96a31f7129acad30', 'EMPRESAS_LIST 2014-2022.xlsx', '2014–2022; data por registo quando disponível'),
|
||||
'licensed.xlsx': ('1b5087b464e39e88c42def39655c2391aaaf684b4c832d6a77ff274b98889f00', 'Empresas Licenciadas.xlsx', 'Data por registo; cobertura temporal global não determinada'),
|
||||
'tourism.xlsx': ('f02e086ed4973ef17234aa6cc54b98fbb7a6952ffb16ec4dc4f7d0c72af6400c', 'CADASTRO DE EMPREENDIMENTOS TURISTICOS 3, 4 E 5 ESTRELAS 30.10.24 18,00h.xlsx', '30/10/2024 (nome do exemplar); situação actual não confirmada'),
|
||||
'cae.xlsx': ('83b4f1200988e99834c2ae3915346fbf237d128308554b67b5a080d4956bf506', 'CAE eBAU.xlsx', 'Versão não indicada'),
|
||||
}
|
||||
|
||||
def norm(v):
|
||||
return ''.join(c for c in unicodedata.normalize('NFKD', str(v)).casefold() if not unicodedata.combining(c)).strip()
|
||||
|
||||
def digest(v):
|
||||
return hashlib.sha256(v.encode('utf-8')).hexdigest()
|
||||
|
||||
def rows(path):
|
||||
# Shared strings are spooled to a temporary on-disk SQLite lookup, not a huge XML tree.
|
||||
with zipfile.ZipFile(path) as z, sqlite3.connect('') as strings:
|
||||
strings.execute('create table strings(id integer primary key,value text)')
|
||||
if 'xl/sharedStrings.xml' in z.namelist():
|
||||
with z.open('xl/sharedStrings.xml') as f:
|
||||
i = 0
|
||||
for _, e in ET.iterparse(f, events=('end',)):
|
||||
if e.tag == NS+'si':
|
||||
strings.execute('insert into strings values(?,?)', (i, ''.join(t.text or '' for t in e.iter(NS+'t'))))
|
||||
i += 1
|
||||
e.clear()
|
||||
book = ET.fromstring(z.read('xl/workbook.xml'))
|
||||
rels = {r.attrib['Id']: r.attrib['Target'] for r in ET.fromstring(z.read('xl/_rels/workbook.xml.rels'))}
|
||||
for sheet in book.find(NS+'sheets'):
|
||||
target = rels[sheet.attrib['{http://schemas.openxmlformats.org/officeDocument/2006/relationships}id']]
|
||||
target = target.lstrip('/') if target.startswith('/') else 'xl/'+target
|
||||
with z.open(target) as f:
|
||||
for _, e in ET.iterparse(f, events=('end',)):
|
||||
if e.tag != NS+'row':
|
||||
continue
|
||||
result = {}
|
||||
for c in e:
|
||||
col = ''.join(ch for ch in c.attrib.get('r','') if ch.isalpha())
|
||||
v = c.findtext(NS+'v', '')
|
||||
if c.attrib.get('t') == 's':
|
||||
hit = strings.execute('select value from strings where id=?', (int(v),)).fetchone()
|
||||
v = hit[0] if hit else ''
|
||||
elif c.attrib.get('t') == 'inlineStr':
|
||||
v = ''.join(t.text or '' for t in c.iter(NS+'t'))
|
||||
result[col] = ' '.join(v.split())
|
||||
yield sheet.attrib['name'], int(e.attrib['r']), result
|
||||
e.clear()
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument('--sources', required=True)
|
||||
p.add_argument('--output', required=True)
|
||||
p.add_argument('--only-tourism', action='store_true', help='Rebuild only tourism in an existing private preview')
|
||||
p.add_argument('--only-missing-names', action='store_true', help='Add nonempty company rows without a social denomination; never substitute the personal applicant name')
|
||||
a = p.parse_args()
|
||||
output = Path(a.output)
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
manifest = {'version':1, 'access':'internal', 'sources':[], 'publication':False,
|
||||
'limitations':'Registos administrativos históricos; cobertura incompleta, actividade actual, capacidade, procura e compromissos não confirmados. Ausência de registo significa informação insuficiente.'}
|
||||
for alias, (expected, name, period) in SOURCES.items():
|
||||
path = Path(a.sources)/alias
|
||||
actual = hashlib.file_digest(path.open('rb'), 'sha256').hexdigest()
|
||||
if actual != expected:
|
||||
raise ValueError('Original changed: '+alias)
|
||||
manifest['sources'].append({'alias':alias,'name':name,'sha256':actual,'bytes':path.stat().st_size,'period':period})
|
||||
db = sqlite3.connect(output/'supply-preview.sqlite')
|
||||
db.executescript('''create table if not exists records(id text primary key, source_hash text, sheet text, row_number integer, entity_key text, name text, province text, district text, cae text, activity text, sector text, source_date text, fingerprint text, duplicate_of text, identity_conflict integer default 0);
|
||||
create index if not exists supply_place on records(province,district);
|
||||
create index if not exists supply_entity on records(entity_key);
|
||||
create index if not exists supply_fingerprint on records(fingerprint);''')
|
||||
labels = {}
|
||||
hierarchy = {}
|
||||
for _, row, c in rows(Path(a.sources)/'cae.xlsx'):
|
||||
if row > 1 and c.get('B') and c.get('F'):
|
||||
labels[c['B']] = c['F']
|
||||
hierarchy[c['A']] = c
|
||||
# Supplied CAE hierarchy labels retained. Sector is the top-level CAE section,
|
||||
# not an invented correspondence to the portal's twelve navigation sectors.
|
||||
sector_by_code = {}
|
||||
for node in hierarchy.values():
|
||||
ancestor=node
|
||||
visited=set()
|
||||
while ancestor.get('D')!='1' and ancestor.get('C') in hierarchy and ancestor['A'] not in visited:
|
||||
visited.add(ancestor['A'])
|
||||
ancestor=hierarchy[ancestor['C']]
|
||||
if ancestor.get('D')=='1':
|
||||
sector_by_code[node['B']]=ancestor['F']
|
||||
counts = {}
|
||||
if a.only_tourism:
|
||||
db.execute('delete from records where source_hash=?',(SOURCES['tourism.xlsx'][0],))
|
||||
aliases=('companies.xlsx','licensed.xlsx') if a.only_missing_names else (('tourism.xlsx',) if a.only_tourism else ('companies.xlsx','licensed.xlsx','tourism.xlsx'))
|
||||
for alias in aliases:
|
||||
source_hash, _, period = SOURCES[alias]
|
||||
count = 0
|
||||
province = ''
|
||||
previous_sheet=''
|
||||
for sheet, row, c in rows(Path(a.sources)/alias):
|
||||
if alias == 'tourism.xlsx':
|
||||
if sheet!=previous_sheet:
|
||||
province=''
|
||||
previous_sheet=sheet
|
||||
heading=norm(c.get('C','')).replace(' ','')
|
||||
provinces={'cidadedemaputo':'MAPUTO CIDADE','maputocidade':'MAPUTO CIDADE','provinciademaputo':'MAPUTO PROVINCIA','maputoprovincia':'MAPUTO PROVINCIA'}
|
||||
provinces.update({'provinciade'+norm(n).replace(' ',''):n for n in ['Gaza','Inhambane','Sofala','Manica','Tete','Zambézia','Nampula','Cabo Delgado','Niassa']})
|
||||
if heading in provinces and not c.get('D'):
|
||||
province=provinces[heading]
|
||||
continue
|
||||
# Explicit province section and ordinal identify an establishment.
|
||||
# Missing licence/room/contact never excludes an authorised record.
|
||||
name = c.get('C','')
|
||||
if not name or not c.get('B','').isdigit():
|
||||
continue
|
||||
district = c.get('E','') if sheet=='Folha1' else ''
|
||||
cae, activity, date, identifier = '', 'Alojamento (âmbito do exemplar; categoria por conferir)', period, ''
|
||||
sector = labels.get('I','Alojamento e restauração — exemplar de alojamento')
|
||||
else:
|
||||
if row == 1:
|
||||
continue
|
||||
old = alias == 'companies.xlsx'
|
||||
name = c.get('L','')
|
||||
if a.only_missing_names and name:
|
||||
continue
|
||||
if not any(c.values()):
|
||||
continue
|
||||
# A missing social denomination is a data gap, not a reason to
|
||||
# discard an authorised source row or publish an applicant's name.
|
||||
name = name or 'Denominação não indicada'
|
||||
province, district = c.get('Y' if old else 'V',''), c.get('X' if old else 'U','')
|
||||
cae = c.get('S' if old else 'O','')
|
||||
activity = labels.get(cae, '')
|
||||
sector = sector_by_code.get(cae,'')
|
||||
date = c.get('D','')
|
||||
# Keep source serial verbatim; Excel origin must be demonstrated before conversion.
|
||||
date = ('Serial Excel original: '+date) if date.replace('.','',1).isdigit() else (date or 'Não indicada')
|
||||
nuit = c.get('K','')
|
||||
identifier = ('nuit:'+nuit) if len(nuit)==9 and nuit.isdigit() and len(set(nuit))>1 else ''
|
||||
rid = digest(source_hash+'|'+sheet+'|'+str(row))
|
||||
entity = digest(identifier) if identifier else digest('unresolved:'+source_hash+'|'+sheet+'|'+str(row))
|
||||
# Same projected administrative observation, not byte-identical rows:
|
||||
# include licence and process identifiers; do not collapse distinct licences.
|
||||
licence = (c.get('G','') if sheet!='Folha1' else '') if alias=='tourism.xlsx' else c.get('A','')
|
||||
process = '' if alias=='tourism.xlsx' else c.get('B','')
|
||||
fp = digest('|'.join(map(norm,[identifier,name,province,district,cae,date,licence,process])))
|
||||
duplicate = db.execute('select id from records where fingerprint=? and id<>? limit 1',(fp,rid)).fetchone()
|
||||
db.execute('insert or ignore into records values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)', (rid,source_hash,sheet,row,entity,name,province,district,cae,activity,sector,date,fp,duplicate[0] if duplicate else None,0))
|
||||
count += 1
|
||||
if count % 10000 == 0:
|
||||
db.commit()
|
||||
db.commit()
|
||||
counts[alias] = count
|
||||
print(json.dumps({'source':alias,'rows_processed':count}), flush=True)
|
||||
db.execute('update records set identity_conflict=0')
|
||||
db.execute("update records set identity_conflict=1 where entity_key in (select entity_key from records where name<>'Denominação não indicada' group by entity_key having count(distinct lower(name))>1)")
|
||||
db.commit()
|
||||
counts={alias:db.execute('select count(*) from records where source_hash=?',(SOURCES[alias][0],)).fetchone()[0] for alias in ('companies.xlsx','licensed.xlsx','tourism.xlsx')}
|
||||
manifest.update(rows_processed=counts, records=db.execute('select count(*) from records').fetchone()[0],
|
||||
duplicate_sightings=db.execute('select count(*) from records where duplicate_of is not null').fetchone()[0],
|
||||
conflicting_identity_sightings=db.execute('select count(*) from records where identity_conflict=1').fetchone()[0],
|
||||
unnamed_observations=db.execute("select count(*) from records where name='Denominação não indicada'").fetchone()[0],
|
||||
unclassified_activity=db.execute("select count(*) from records where activity=''").fetchone()[0],
|
||||
unresolved_territory=db.execute("select count(*) from records where district='' or province=''").fetchone()[0])
|
||||
db.close()
|
||||
(output/'preview.json').write_text(json.dumps(manifest,ensure_ascii=False,indent=2),encoding='utf-8')
|
||||
print(json.dumps({k:v for k,v in manifest.items() if k not in ('sources',)},ensure_ascii=False))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
$root=dirname(__DIR__);
|
||||
$operation='test';
|
||||
if(!in_array($operation,['routes','test'],true)||basename($root)!=='portal-investidor-supply-qa-20261028')throw new RuntimeException('Explicit isolated QA workspace and routes/test operation required.');
|
||||
foreach(['storage/framework/views','storage/framework/cache','storage/framework/sessions','bootstrap/cache','storage/logs','outputs/proactive-supply-20261028','outputs/proactive-investment-20261027']as$dir)if(!is_dir($root.'/'.$dir))mkdir($root.'/'.$dir,0700,true);
|
||||
foreach(['APP_BASE_PATH'=>$root,'APP_ENV'=>'testing','APP_URL'=>'http://127.0.0.1:18768','APP_KEY'=>'base64:'.base64_encode(str_repeat('Q',32)),'DB_CONNECTION'=>'pgsql','DB_HOST'=>'127.0.0.1','DB_PORT'=>'55441','DB_DATABASE'=>'proactive_investment_20261027_test','DB_USERNAME'=>'postgres','DB_PASSWORD'=>'','DB_URL'=>'','CACHE_STORE'=>'array','SESSION_DRIVER'=>'array','QUEUE_CONNECTION'=>'sync','MAIL_MAILER'=>'array'] as $k=>$v){putenv($k.'='.$v);$_ENV[$k]=$_SERVER[$k]=$v;}
|
||||
$loader=require $root.'/vendor/autoload.php';
|
||||
spl_autoload_register(function($class)use($root){foreach(['App\\'=>'app/','Tests\\'=>'tests/','Database\\Factories\\'=>'database/factories/']as$prefix=>$dir){if(str_starts_with($class,$prefix)){$file=$root.'/'.$dir.str_replace('\\','/',substr($class,strlen($prefix))).'.php';if(is_file($file)){require $file;return;}}}},true,true);
|
||||
$app=require $root.'/bootstrap/app.php';
|
||||
(new Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables)->bootstrap($app);
|
||||
(new Illuminate\Foundation\Bootstrap\LoadConfiguration)->bootstrap($app);
|
||||
$c=config('database.connections.pgsql');
|
||||
if($app->configurationIsCached()||$c['host']!=='127.0.0.1'||(int)$c['port']!==55441||$c['database']!=='proactive_investment_20261027_test'||!empty($c['url']))throw new RuntimeException('QA guard');
|
||||
$pdo=new PDO('pgsql:host=127.0.0.1;port=55441;dbname='.$c['database'],$c['username'],$c['password']);
|
||||
if($pdo->query('select current_database()')->fetchColumn()!==$c['database'])throw new RuntimeException('QA identity');
|
||||
|
||||
if(($argv[1]??'')==='test')return;
|
||||
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
|
||||
config(['portal.profile_access_enabled'=>true,'portal.opportunity_specialized_drafts_enabled'=>true,'investor-path.enabled'=>true,'investor-path.private_preparation'=>true,'suitecrm.enabled'=>false,'investmoz_ai.enabled'=>false,'session.driver'=>'file','session.secure'=>false]);
|
||||
|
||||
Illuminate\Support\Facades\Vite::useBuildDirectory('build-qa-opportunities');
|
||||
config(['session.cookie'=>'proactive_qa_session','session.http_only'=>true]);
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/** Explicit QA-only tooling: no active environment or .env fallback. */
|
||||
require __DIR__.'/proactive-supply-qa-bootstrap.php';
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
$operation=$argv[1]??'';
|
||||
if($operation==='test'){
|
||||
$argv=['phpunit','-c','phpunit.investmoz.xml','tests/Feature/ProactiveSupplyTest.php','tests/Feature/ProactiveInvestmentTest.php'];
|
||||
if(getenv('PROACTIVE_SUPPLY_FILTER')){$argv[]='--filter';$argv[]=getenv('PROACTIVE_SUPPLY_FILTER');}
|
||||
$argc=count($argv);$_SERVER['argv']=$argv;$_SERVER['argc']=$argc;
|
||||
require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';exit;
|
||||
}
|
||||
Tests\Support\ProactiveInvestmentFixture::guard();
|
||||
$out=dirname(__DIR__).'/outputs/proactive-supply-20261028';
|
||||
if($operation==='geography'){
|
||||
file_put_contents($out.'/private/geography.json',json_encode(DB::table('geo_locations')->whereNull('deleted_at')->get(['id','name','parent_id','location_type']),JSON_UNESCAPED_UNICODE|JSON_THROW_ON_ERROR));
|
||||
echo "QA geography exported privately.\n";exit;
|
||||
}
|
||||
if($operation==='schema'){
|
||||
if(!Schema::hasTable('proactive_supply_sources')){
|
||||
(require dirname(__DIR__).'/database/migrations/2026_10_28_180000_create_proactive_supply_observations.php')->up();
|
||||
}
|
||||
echo "QA schema available; no existing table altered.\n";exit;
|
||||
}
|
||||
if(in_array($operation,['import','extend'],true)){
|
||||
$dir=$out.'/private-v2';
|
||||
$manifest=json_decode(file_get_contents($dir.'/preview.json'),true,512,JSON_THROW_ON_ERROR);
|
||||
$scan=json_decode(file_get_contents($out.'/private/scan.json'),true,512,JSON_THROW_ON_ERROR);
|
||||
if(($scan['exit_code']??1)!==0||array_diff(array_column($manifest['sources'],'sha256'),$scan['source_hashes']??[]))throw new RuntimeException('Hash-specific malware scan required');
|
||||
$file=$dir.'/observations.jsonl';$hash=hash_file('sha256',$file);
|
||||
if(($argv[2]??'')!==$hash)throw new RuntimeException('Review preview and supply exact JSONL hash');
|
||||
$before=DB::table('proactive_supply_observations')->count();
|
||||
$extension=$operation==='extend';$previousHash=$argv[3]??null;
|
||||
if($extension){
|
||||
$delta=json_decode(file_get_contents($dir.'/extension-preview.json'),true,512,JSON_THROW_ON_ERROR);
|
||||
if(($delta['sha256']??null)!==$hash||($delta['previous_sha256']??null)!==$previousHash||empty($delta['no_replacement'])||$delta['previous_observations_unchanged']+$delta['new_observations']!==$manifest['records'])throw new RuntimeException('Extension must prove all previously accepted observations unchanged and bind both snapshot hashes.');
|
||||
}
|
||||
DB::transaction(function()use($manifest,$file,$hash,$extension,$previousHash){
|
||||
DB::select("select pg_advisory_xact_lock(hashtext('proactive-supply-import-v1'))");
|
||||
foreach($manifest['sources']as$s){
|
||||
$existing=DB::table('proactive_supply_sources')->where('sha256',$s['sha256'])->first();
|
||||
if($existing && data_get(json_decode($existing->receipt,true),'transport_sha256')!==($extension?$previousHash:$hash))throw new RuntimeException('Existing source has a different prepared classification snapshot; preserve it and prepare an explicit mapping delta instead of silently replacing observations.');
|
||||
if($extension){
|
||||
if(!$existing)throw new RuntimeException('An extension must reference the already imported originals.');
|
||||
$receipt=json_decode($existing->receipt,true);
|
||||
$history=$receipt['extensions']??[];
|
||||
if(!in_array($hash,array_column($history,'transport_sha256'),true)){
|
||||
$history[]=['transport_sha256'=>$hash,'rows_in_extended_preview'=>$manifest['records'],'at'=>now()->toIso8601String(),'operation'=>'append previously unrepresented source rows;existing observations immutable'];
|
||||
DB::table('proactive_supply_sources')->where('sha256',$s['sha256'])->update(['receipt'=>json_encode([...$receipt,'extensions'=>$history])]);
|
||||
}
|
||||
}
|
||||
DB::table('proactive_supply_sources')->insertOrIgnore(['sha256'=>$s['sha256'],'name'=>$s['name'],'period'=>$s['period'],'limitations'=>$manifest['limitations'],'receipt'=>json_encode(['source'=>$s,'preview_rows'=>$manifest['records'],'transport_sha256'=>$hash,'scan'=>'Defender custom scan, no threats, hash-specific receipt','scope'=>'isolated QA, internal analysis only']),'imported_at'=>now()]);
|
||||
}
|
||||
$f=fopen($file,'rb');$batch=[];$n=0;
|
||||
while(($line=fgets($f))!==false){
|
||||
$r=json_decode($line,true,512,JSON_THROW_ON_ERROR);
|
||||
if(!in_array($r['source_hash'],array_column($manifest['sources'],'sha256'),true))throw new RuntimeException('Unknown source');
|
||||
$batch[]=$r;$n++;
|
||||
if(count($batch)===500){DB::table('proactive_supply_observations')->insertOrIgnore($batch);$batch=[];}
|
||||
}
|
||||
fclose($f);if($batch)DB::table('proactive_supply_observations')->insertOrIgnore($batch);
|
||||
if($n!==$manifest['records'])throw new RuntimeException('Incomplete candidate');
|
||||
});
|
||||
$receipt=['before'=>$before,'after'=>DB::table('proactive_supply_observations')->count(),'sha256'=>$hash,'extension'=>$extension,'previous_snapshot'=>$previousHash,'publication'=>false,'database'=>'proactive_investment_20261027_test','port'=>55441];
|
||||
file_put_contents($out.'/import-'.($extension?'extension':($before?'replay':'first')).'.json',json_encode($receipt,JSON_PRETTY_PRINT));echo json_encode($receipt)."\n";exit;
|
||||
}
|
||||
throw new RuntimeException('Use geography,schema,import <reviewed hash>,extend <reviewed hash> <previous snapshot hash>,or test;exclusively isolated QA.');
|
||||
Reference in New Issue
Block a user