From 7628645fb67055376b8529da6b0bc9b44498e91b Mon Sep 17 00:00:00 2001 From: nala Date: Tue, 27 Oct 2026 11:59:38 +0200 Subject: [PATCH] Accept isolated SGF-RECEIVABLES-SIM-v1 delivery Record acceptance without applying 070000 or grants. Preserve previously authorised workshop technical installation and operational/fiscal blocks. Exclude credentials and real-data preservation reports. --- AGENTS.md | 4 + docs/SERVICE_RECEIVABLES_ACCEPTANCE.md | 23 ++++++ docs/SERVICE_RECEIVABLES_DELIVERY.md | 4 + docs/WORKSHOP_RESOURCE_DELIVERY.md | 12 +++ scripts/workshop-dev-install.php | 100 +++++++++++++++++++++++++ 5 files changed, 143 insertions(+) create mode 100644 docs/SERVICE_RECEIVABLES_ACCEPTANCE.md create mode 100644 scripts/workshop-dev-install.php diff --git a/AGENTS.md b/AGENTS.md index 8481c30..dd0cfc1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,9 @@ # SGF Tempo — instruções locais +- Encerramento de 27/10/2026: entrega isolada `SGF-RECEIVABLES-SIM-v1` aceite pelo utilizador, com registo e checkpoint exclusivamente local. Ver `docs/SERVICE_RECEIVABLES_ACCEPTANCE.md`. Esta aceitação substitui a pendência de aceitação funcional, mas NÃO aplica `070000` nem as oito concessões, não activa utilização real e mantém bloqueios operacionais/fiscais. Oficinas permanece aceite, com `080000` e os quatro direitos de abertura já aplicados tecnicamente; não repetir a sua aprovação. Sem custos industriais ou publicação. + +- Autorização específica posterior de 27/10/2026 executada: apenas `080000` aplicada em `sgf_tempo_dev`, por caminho explícito e proprietário `sgf_dev_owner`; quatro concessões auditadas para conta #1, tenant #1 / Tempo Lda #1 / SEGI #1: `workshop.view/open/assign/authorize`. Ver actualização de `docs/WORKSHOP_RESOURCE_DELIVERY.md`. `070000` permanece pendente, sem aceitação de recebimentos implícita. Dados/acessos anteriores preservados; sete tabelas de oficina vazias. Aplicação mantém `sgf_dev_app`; guardas operacionais e de emissão real preservadas e verificadas. Técnico/verificador reais continuam por identificar, sem concessões. Esta instalação não activa utilização operacional; indicações anteriores de `080000`/quatro direitos pendentes são históricas. + - Aceitação específica de 27/10/2026: o utilizador aceitou a entrega isolada `WORKSHOP-RESOURCE-RECORD-v1` e autorizou o registo e checkpoint Git exclusivamente local, sem credenciais/dados reais. Ver `docs/WORKSHOP_RESOURCE_ACCEPTANCE.md`. Esta aceitação não aplica `080000`, não concede direitos, não activa uso operacional e não abrange a aceitação de recebimentos. `070000` permanece separada e pendente. Técnico/verificador reais continuam por identificar. Sem custos industriais, publicação ou novo incremento. Esta aceitação substitui apenas a anterior pendência de aceitação de oficinas. - Autorização de 27/10/2026: `WORKSHOP-RESOURCE-RECORD-v1`, ordem/autorização/tarefas/tempos/materiais/verificação, exclusivamente sintético em `sgf_tempo_test`. Ver `docs/WORKSHOP_RESOURCE_DELIVERY.md`. Início de trabalho exige autorização; relato de ocorrência passada fora do âmbito é permitido para auditoria, permanentemente sinalizado, sem autorização retroactiva ou verificação automática. Verificador distinto do autor. Migração `080000` e concessões reais NÃO aplicadas; confirmação específica antes de qualquer aplicação. `070000` de recebimentos continua pendente e não deve ser aplicada por arrasto. Técnico/verificador reais não identificados; não criar contas reais ou atribuir supervisão à Samira. Preservar entregas, sem presumir aceitação de recebimentos/oficina. Sem stock/custos/facturação/recebimentos/lançamentos ou publicação; guardas operacionais/fiscais mantidas. diff --git a/docs/SERVICE_RECEIVABLES_ACCEPTANCE.md b/docs/SERVICE_RECEIVABLES_ACCEPTANCE.md new file mode 100644 index 0000000..fb61755 --- /dev/null +++ b/docs/SERVICE_RECEIVABLES_ACCEPTANCE.md @@ -0,0 +1,23 @@ +# Aceitação — SGF-RECEIVABLES-SIM-v1 + +Em 27/10/2026 (Africa/Johannesburg), o utilizador pediu o encerramento da entrega de recebimentos sintéticos, com registo de aceitação e checkpoint local, excluindo credenciais e dados reais, mantendo a migração `070000` e as concessões por aplicar. + +## Âmbito aceite + +A entrega descrita em [SERVICE_RECEIVABLES_DELIVERY.md](SERVICE_RECEIVABLES_DELIVERY.md), exclusivamente sintética e isolada em `sgf_tempo_test`: obrigação de 1 000,00 MZN, recebimentos de 400,00 e 600,00, afectações explícitas, saldo final zero e duas reconciliações simuladas, com funções separadas, idempotência, histórico e eventos duráveis. Liquidação e reconciliação permanecem conceitos distintos. + +Reutilizam-se as provas já preservadas no relatório: login real no navegador isolado, testes e concorrência. Não se atribui ao utilizador uma nova sessão de demonstração nesta data. A última regressão relevante de oficinas/recebimentos passou com 41 testes e 832 asserções; a ocorrência intermitente anterior na preparação de uma factura sintética permanece documentada em [WORKSHOP_RESOURCE_DELIVERY.md](WORKSHOP_RESOURCE_DELIVERY.md). Não se repetiram testes por este encerramento documental. + +## Limites + +- `2026_10_27_070000_create_simulated_receivables` continua por aplicar em desenvolvimento; as oito concessões propostas continuam sem aplicação. +- Nenhum recebimento real, recibo fiscal, ligação bancária, movimentação de dinheiro, lançamento, fecho ou publicação é autorizado. +- Guardas de ambiente/base e bloqueios de emissão/configuração reais preservados. Aceitar o ensaio não autoriza utilização operacional. +- Oficinas mantém a aceitação própria e a instalação técnica posterior da `080000` e quatro direitos de abertura. Técnico e verificador reais continuam sem concessões e por identificar. Não se reabre a aprovação de oficinas. +- Custos industriais continuam apenas em especificação. + +## Checkpoint + +O commit que contém este registo preserva a aceitação e as alterações posteriores ao checkpoint `21a91f2`, incluindo a documentação/procedimento da instalação técnica já autorizada de oficinas. Não executa novamente esse procedimento. O hash do novo checkpoint é comunicado na entrega, sem o inserir no próprio conteúdo que identifica. + +Credenciais, ambientes, dados reais e relatórios locais de preservação permanecem excluídos. Os campos de aceitação nas provas JSON anteriores descrevem o estado no momento da demonstração; este registo documenta a aceitação posterior sem reescrever essas provas históricas. diff --git a/docs/SERVICE_RECEIVABLES_DELIVERY.md b/docs/SERVICE_RECEIVABLES_DELIVERY.md index 656cda3..eb68963 100644 --- a/docs/SERVICE_RECEIVABLES_DELIVERY.md +++ b/docs/SERVICE_RECEIVABLES_DELIVERY.md @@ -99,6 +99,10 @@ Contas/âmbito conferidos por leitura através de `scripts/receivables-grants-pr Exigem confirmação específica antes da aplicação, separada da migração. Sem gestão destes acessos, herança, emissão, banco, reconhecimento ou fecho. Não foram aplicadas às contas reais; apenas equivalentes sintéticos receberam direitos na fixture de testes. Uma eventual concessão local não permite executar recebimentos reais nem contornar o bloqueio. +## Aceitação posterior — 27/10/2026 + +Entrega isolada encerrada por aceitação do utilizador, registada em [SERVICE_RECEIVABLES_ACCEPTANCE.md](SERVICE_RECEIVABLES_ACCEPTANCE.md), com checkpoint exclusivamente local. As referências anteriores a aceitação pendente descrevem o estado da entrega técnica antes deste encerramento. `070000` e as oito concessões permanecem por aplicar; os bloqueios operacionais e fiscais mantêm-se. Oficinas não exige nova aprovação e custos industriais não foram implementados. + ## Ficheiros e limites Código novo: `app/Modules/Services/ServiceReceivables.php`, `Http/ServiceReceivablesController.php`, `resources/js/ServiceReceivablesWorkspace.jsx`; integração em `routes/web.php` e `resources/js/app.jsx`. Migração/SQL acima. Testes e fixtures: `tests/Feature/ServiceReceivablesTest.php`, `tests/Support/ReceivablesFixture.php`; `InvoiceFixture` ganhou parâmetro de preço com valor anterior preservado por omissão. Scripts: `receivables-fixture.php`, `receivables-worker.php`, `receivables-concurrency.php`, `receivables-proof.php`, `receivables-grants-preview.php`. Documentação e provas referidas neste relatório. diff --git a/docs/WORKSHOP_RESOURCE_DELIVERY.md b/docs/WORKSHOP_RESOURCE_DELIVERY.md index 88f106c..a1a81c1 100644 --- a/docs/WORKSHOP_RESOURCE_DELIVERY.md +++ b/docs/WORKSHOP_RESOURCE_DELIVERY.md @@ -109,6 +109,18 @@ Não incluem gestão de acessos, custos/margens, stock, fiscalidade ou emissão. Entrega isolada aceite expressamente pelo utilizador em 27/10/2026. Ver [registo de aceitação e limites do checkpoint](WORKSHOP_RESOURCE_ACCEPTANCE.md). A aceitação não aplica migrações ou concessões, não activa uso operacional e não abrange a aceitação de recebimentos. A referência anterior a ausência de aceitação presumida descreve correctamente a entrega antes desta confirmação. +## Aplicação específica posterior em desenvolvimento — 27/10/2026 + +Após autorização específica do utilizador, foi aplicada **apenas** `2026_10_27_080000_create_workshop_resource_records` em `sgf_tempo_dev`, por caminho explícito, com `sgf_dev_owner` separado da aplicação. O registo de migrações acrescentou exactamente essa entrada; `070000` permanece pendente. As indicações anteriores de migração/concessões pendentes descrevem o estado anterior a esta autorização. + +Foram concedidos exactamente quatro direitos à conta existente Osmane #1, tenant #1 / Tempo Lda #1 / SEGI #1: `workshop.view`, `workshop.open`, `workshop.assign`, `workshop.authorize`. Cada concessão foi auditada. Não se criaram contas ou direitos para técnico/verificador, nem direitos de registo/verificação ou gestão de acessos. + +O procedimento `scripts/workshop-dev-install.php` comparou em memória as impressões dos registos preexistentes antes/depois, incluindo acessos e auditoria anteriores. Só se acrescentaram o registo da migração, quatro concessões e quatro eventos de auditoria. As sete tabelas novas estão vazias. O resumo local fica em `storage/app/private/workshop-dev-install-proof.json`, excluído do Git; não exporta conteúdos dos registos ou credenciais. + +Verificação no papel normal `sgf_dev_app`: listagem bloqueada e vazia; guarda, abertura, registo e verificação recusados com 403; `sgf_wo_environment()` recusou a base de desenvolvimento; emissão real continua recusada. Não se inseriram ordens ou apontamentos de demonstração em desenvolvimento. Não foram alterados código funcional, ferramentas ou dependências, pelo que não se repetiu a demonstração aceite ou a compilação. O procedimento de instalação é deliberadamente não repetível sem revisão do estado. + +O checkpoint aceite `21a91f2` mantém-se preservado. Esta aplicação não aceita recebimentos, não activa utilização operacional, não implementa custos industriais e não autoriza publicação. + ## Ficheiros e limites Principais alterações: `app/Modules/Workshop/WorkshopWorkflow.php`, `Http/WorkshopController.php`, `resources/js/WorkshopWorkspace.jsx`, integração em `resources/js/app.jsx` e `routes/web.php`; migração/SQL acima; `tests/Support/WorkshopFixture.php`, `tests/Feature/WorkshopTest.php`; scripts `workshop-fixture.php`, `workshop-worker.php`, `workshop-concurrency.php`, `workshop-proof.php` e `workshop-permissions-preview.php`; documentação/provas desta entrega. diff --git a/scripts/workshop-dev-install.php b/scripts/workshop-dev-install.php new file mode 100644 index 0000000..39f65a4 --- /dev/null +++ b/scripts/workshop-dev-install.php @@ -0,0 +1,100 @@ +make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap(); +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Artisan; +use App\Modules\Identity\Services\LocalDatabaseGuard; +use App\Modules\Identity\Services\Permissions; +use App\Modules\Identity\Services\Audit; +use App\Modules\Workshop\WorkshopWorkflow; +use App\Shared\Context\CompanyContext; + +function ensureWorkshop(bool $ok,string $message): void { if(!$ok) throw new RuntimeException($message); } +function workshopFingerprint(string $table,?int $max=null): array { + ensureWorkshop((bool)preg_match('/^[a-z_]+$/D',$table),'Unexpected table identifier.'); + $where=$max===null?'':' WHERE id <= '.$max; + $r=DB::selectOne('SELECT count(*) AS n, md5(COALESCE(string_agg(row_to_json(t)::text, E\'\\n\' ORDER BY row_to_json(t)::text),\'\')) AS h FROM (SELECT * FROM "'.$table.'"'.$where.') t'); + return [(int)$r->n,$r->h]; // In-memory comparison only; no record contents exported. +} +ensureWorkshop(LocalDatabaseGuard::allows(),'Own SGF development application connection required.'); +ensureWorkshop($argv===[$argv[0],'--apply-confirmed-080000-and-four-grants'],'Explicit confirmed operation argument required.'); +$migration='2026_10_27_080000_create_workshop_resource_records'; +$other='2026_10_27_070000_create_simulated_receivables'; +$tables=array_map(fn($r)=>$r->tablename,DB::select("SELECT tablename FROM pg_tables WHERE schemaname='public' ORDER BY tablename")); +$newTables=['wo_equipment','wo_orders','wo_entries','wo_decisions','wo_commands','wo_events','wo_outbox']; +ensureWorkshop(!DB::table('migrations')->whereIn('migration',[$migration,$other])->exists(),'Expected both migrations pending; refuse changed state.'); +ensureWorkshop(!array_intersect($tables,$newTables),'Workshop tables already exist; review state before proceeding.'); +$rights=['workshop.view','workshop.open','workshop.assign','workshop.authorize']; +$checkScope=function()use($rights): void { + ensureWorkshop(DB::table('users')->where(['id'=>1,'name'=>'Osmane Nala','email'=>'nala@tempo.co.mz'])->exists(),'Confirmed opener identity differs.'); + ensureWorkshop(DB::table('companies')->where(['id'=>1,'tenant_id'=>1,'name'=>'Tempo Lda'])->exists(),'Confirmed company differs.'); + ensureWorkshop(DB::table('units')->where(['id'=>1,'tenant_id'=>1,'company_id'=>1,'name'=>'SEGI'])->exists(),'Confirmed unit differs.'); + ensureWorkshop(DB::table('company_memberships')->where(['user_id'=>1,'tenant_id'=>1,'company_id'=>1])->exists(),'Existing membership required.'); + $p=app(Permissions::class); $p->require(1,1,1,'company.view');$p->require(1,1,1,'unit.view',1); + ensureWorkshop(!DB::table('permission_grants')->where('permission','like','workshop.%')->exists(),'Unexpected existing workshop grants; review first.'); +}; +$checkScope(); +$baseline=[];foreach($tables as $t)$baseline[$t]=workshopFingerprint($t); +$max=[];foreach(['permission_grants','identity_audit'] as $t)$max[$t]=(int)DB::table($t)->max('id'); +$migrationRows=DB::table('migrations')->orderBy('id')->get()->map(fn($r)=>(array)$r)->all(); +$guardHash=hash_file('sha256',base_path('app/Modules/Workshop/WorkshopWorkflow.php')); +$secrets=json_decode(file_get_contents('C:/laragon/data/sgf-tempo/secrets/database.json'),true,32,JSON_THROW_ON_ERROR); +$owner=config('database.connections.'.config('database.default')); +$owner['username']='sgf_dev_owner';$owner['password']=$secrets['dev_owner'];$owner['url']=null; +config(['database.connections.sgf_workshop_owner'=>$owner]);unset($owner,$secrets); +try { + $connection=DB::connection('sgf_workshop_owner'); + $identity=$connection->selectOne('SELECT current_database() AS db,current_user AS role,inet_server_port() AS port,rolsuper,rolcreatedb,rolcreaterole,rolbypassrls FROM pg_roles WHERE rolname=current_user'); + ensureWorkshop($identity->db==='sgf_tempo_dev'&&$identity->role==='sgf_dev_owner'&&(int)$identity->port===55432&&!$identity->rolsuper&&!$identity->rolcreatedb&&!$identity->rolcreaterole&&!$identity->rolbypassrls,'Unexpected migration owner/destination.'); + $connection->transaction(function()use($migration,$other,$baseline,$newTables,$connection): void { + $applicationConnection=DB::getDefaultConnection();DB::setDefaultConnection('sgf_workshop_owner'); + try { + $exit=Artisan::call('migrate',['--database'=>'sgf_workshop_owner','--path'=>'database/migrations/'.$migration.'.php','--force'=>true,'--no-interaction'=>true]); + ensureWorkshop($exit===0,'Targeted migration failed.'); + foreach($baseline as $t=>$fp)if($t!=='migrations')ensureWorkshop(workshopFingerprint($t)===$fp,'Existing table changed during migration: '.$t); + ensureWorkshop(!DB::table('migrations')->where('migration',$other)->exists(),'070000 must remain pending.'); + foreach($newTables as $t)ensureWorkshop(DB::table($t)->count()===0,'New workshop table must be empty.'); + } finally { DB::setDefaultConnection($applicationConnection); } + }); +} finally { + DB::purge('sgf_workshop_owner');config(['database.connections.sgf_workshop_owner'=>null]); +} +ensureWorkshop(LocalDatabaseGuard::allows(),'Application connection must remain sgf_dev_app.'); +$newMigrationRows=DB::table('migrations')->orderBy('id')->get()->map(fn($r)=>(array)$r)->all(); +ensureWorkshop(count($newMigrationRows)===count($migrationRows)+1&&array_slice($newMigrationRows,0,count($migrationRows))===$migrationRows&&end($newMigrationRows)['migration']===$migration,'Unexpected migration ledger delta.'); +$grants=DB::transaction(function()use($checkScope,$rights,$baseline,$max): array { + DB::table('companies')->where('id',1)->lockForUpdate()->firstOrFail();$checkScope(); + $grants=[]; + foreach($rights as $right){ + $g=['user_id'=>1,'tenant_id'=>1,'company_id'=>1,'unit_id'=>1,'permission'=>$right]; + DB::table('permission_grants')->insert($g+['created_at'=>now(),'updated_at'=>now()]); + Audit::record('workshop.initial_grant',null,1,1,1,subject:1,details:['permission'=>$right,'reason'=>'Four opener rights specifically authorised; operational use remains blocked.']); + $grants[]=$g; + } + foreach($baseline as $t=>$fp)if($t!=='migrations')ensureWorkshop(workshopFingerprint($t,$max[$t]??null)===$fp,'Prior records changed: '.$t); + ensureWorkshop(DB::table('permission_grants')->where('id','>',$max['permission_grants'])->count()===4,'Exactly four new grants required.'); + ensureWorkshop(DB::table('identity_audit')->where('id','>',$max['identity_audit'])->where('action','workshop.initial_grant')->count()===4,'Four audit events required.'); + ensureWorkshop(DB::table('permission_grants')->where('permission','like','workshop.%')->count()===4,'No technician/verifier grants permitted.'); + app(CompanyContext::class)->selectFor(1,1,1,1); + $listing=app(WorkshopWorkflow::class)->listing(1); + ensureWorkshop($listing['blocked']&&$listing['orders']===[],'Operational listing must remain blocked.'); + foreach(['guard','open','record','verify'] as $entry){ + try { + $w=app(WorkshopWorkflow::class); + match($entry){'guard'=>WorkshopWorkflow::guard(),'open'=>$w->open(1,[],'blocked-probe'),'record'=>$w->record(0,1,[],'blocked-probe'),'verify'=>$w->verify(0,1,[],'blocked-probe')}; + throw new RuntimeException('Operational command failed to refuse.'); + }catch(\Symfony\Component\HttpKernel\Exception\HttpException $e){ensureWorkshop($e->getStatusCode()===403,'Expected operational refusal.');} + } + return $grants; +}); +try { DB::select('SELECT sgf_wo_environment()');throw new RuntimeException('SQL environment guard did not refuse.'); } +catch(\Illuminate\Database\QueryException $e){ensureWorkshop(str_contains($e->getMessage(),'SGF_WO_TEST_DATABASE_ONLY'),'Unexpected SQL guard response.');} +ensureWorkshop(hash_file('sha256',base_path('app/Modules/Workshop/WorkshopWorkflow.php'))===$guardHash,'Operational guard code changed.'); +foreach($newTables as $t)ensureWorkshop(DB::table($t)->count()===0,'Workshop tables must remain empty.'); +try { \App\Modules\Services\ServiceInvoicing::guard();throw new RuntimeException('Real issuance did not refuse.'); } +catch(\Symfony\Component\HttpKernel\Exception\HttpException $e){ensureWorkshop($e->getStatusCode()===403,'Expected real issuance refusal.');} +$proof=['date'=>'2026-10-27','database'=>'sgf_tempo_dev','migration_applied'=>$migration,'migration_070000_applied'=>false,'preexisting_tables_checked'=>count($baseline),'prior_records_and_accesses_preserved'=>true,'new_empty_tables'=>$newTables,'new_grants'=>$grants,'audited_new_grants'=>4,'technician_and_verifier_grants'=>0,'application_role'=>'sgf_dev_app','operational_listing_blocked'=>true,'operational_commands_denied'=>['guard'=>403,'open'=>403,'record'=>403,'verify'=>403],'sql_environment_guard_refused'=>true,'real_issuance_blocked'=>true,'guard_source_unchanged'=>true,'real_operations_created'=>false]; +file_put_contents(storage_path('app/private/workshop-dev-install-proof.json'),json_encode($proof,JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_THROW_ON_ERROR)); +echo "Applied only 080000 and four audited grants; prior records preserved, seven new tables empty, 070000 pending, operational/issuance guards verified.\n";