Binary file not shown.
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
require dirname(__DIR__).'/vendor/autoload.php';
|
||||
$root = dirname(__DIR__);
|
||||
$settings = json_decode(file_get_contents('C:/laragon/data/sgf-tempo/secrets/payables-demo-compras.json'), true, 32, JSON_THROW_ON_ERROR);
|
||||
if (($settings['APP_ENV'] ?? '') !== 'payable-testing' || ($settings['DB_HOST'] ?? '') !== '127.0.0.1' || realpath($settings['HOST_ROOT'] ?? '') !== realpath($root)
|
||||
|| ! preg_match('/^compras_receipts_test_([a-f0-9]{12})$/D', $settings['DB_DATABASE'] ?? '', $matches) || $settings['DB_USERNAME'] !== 'payable_qa_'.$matches[1]) {
|
||||
throw new LogicException('Wrong pilot destination.');
|
||||
}
|
||||
|
||||
if (! in_array('--apply-authorized', $argv ?? [], true)) {
|
||||
throw new LogicException('Explicit activation flag required.');
|
||||
}
|
||||
$authorization = json_decode(file_get_contents($root.'/docs/proofs/administrative-geography-installation-authorization.json'), true, 32, JSON_THROW_ON_ERROR);
|
||||
$installation = json_decode(file_get_contents($root.'/docs/proofs/administrative-geography-installation.json'), true, 32, JSON_THROW_ON_ERROR);
|
||||
$settingsFile = 'C:/laragon/data/sgf-tempo/secrets/payables-demo-compras.json';
|
||||
$before = file_get_contents($settingsFile);
|
||||
if ($authorization['scope'] !== 'install_two_nullable_geography_columns_and_enable_selection' || $installation['result'] !== 'passed'
|
||||
|| ! $installation['new_references_all_null'] || ! $installation['restore_database_removed']
|
||||
|| filter_var($settings['SGC_TWO_FACTOR_ENABLED'] ?? false, FILTER_VALIDATE_BOOLEAN)
|
||||
|| filter_var($settings['SGC_GEOGRAPHY_ENABLED'] ?? false, FILTER_VALIDATE_BOOLEAN)
|
||||
|| file_exists($root.'/docs/proofs/administrative-geography-activation.json')) {
|
||||
throw new LogicException('Activation authorization or current state differs.');
|
||||
}
|
||||
$after = [...$settings, 'SGC_GEOGRAPHY_ENABLED' => 'true'];
|
||||
$protectedBefore = $settings;
|
||||
$protectedAfter = $after;
|
||||
unset($protectedBefore['SGC_GEOGRAPHY_ENABLED'], $protectedAfter['SGC_GEOGRAPHY_ENABLED']);
|
||||
if ($protectedBefore !== $protectedAfter) {
|
||||
throw new LogicException('Unrelated setting change.');
|
||||
}
|
||||
$backup = dirname($installation['backup']).'/settings-before.json';
|
||||
if (file_put_contents($backup, $before, LOCK_EX) !== strlen($before)) {
|
||||
throw new RuntimeException('Settings backup failed.');
|
||||
}
|
||||
$encoded = json_encode($after, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR).PHP_EOL;
|
||||
$temporary = $settingsFile.'.geography-'.bin2hex(random_bytes(4)).'.tmp';
|
||||
if (file_put_contents($temporary, $encoded, LOCK_EX) !== strlen($encoded)) {
|
||||
throw new RuntimeException('Settings preparation failed.');
|
||||
}
|
||||
if (hash_file('sha256', $settingsFile) !== hash('sha256', $before)) {
|
||||
unlink($temporary);
|
||||
throw new LogicException('Concurrent setting modification.');
|
||||
}
|
||||
if (! rename($temporary, $settingsFile)) {
|
||||
throw new RuntimeException('Settings activation failed; prepared copy retained privately.');
|
||||
}
|
||||
$confirmed = json_decode(file_get_contents($settingsFile), true, 32, JSON_THROW_ON_ERROR);
|
||||
if ($confirmed !== $after) {
|
||||
throw new RuntimeException('Settings verification failed.');
|
||||
}
|
||||
$result = ['checked_at' => gmdate('c'), 'result' => 'passed', 'environment' => 'http://127.0.0.1:8005', 'authorization' => 'administrative-geography-installation-authorization.json', 'installation' => 'administrative-geography-installation.json', 'changed_setting' => 'SGC_GEOGRAPHY_ENABLED', 'selection_enabled' => true, 'other_settings_preserved' => true, 'settings_backup' => $backup, 'mfa_enabled' => false, 'business_commands' => false, 'automatic_classification' => false];
|
||||
file_put_contents($root.'/docs/proofs/administrative-geography-activation.json', json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES).PHP_EOL);
|
||||
echo 'Geographic selection enabled only in the local pilot; all other settings preserved.'.PHP_EOL;
|
||||
@@ -0,0 +1,18 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import assert from 'node:assert/strict';
|
||||
const source = await fs.readFile('scripts/supplier-evaluation-check.mjs', 'utf8');
|
||||
const common = source.slice(source.indexOf('const base ='), source.indexOf('const author ='));
|
||||
await new Function('fs', 'assert', 'return (async()=>{' + common + `
|
||||
const records=[];
|
||||
for(const role of ['admin','warehouse']) {
|
||||
const viewer=client(); await viewer.enter(role);
|
||||
assert.equal((await viewer.request('/contexto','POST',{area:'procurement',option:'procurement.internal'})).status,302);
|
||||
for(const path of ['/auditoria/eventos','/auditoria/eventos.csv']) {
|
||||
const result=await viewer.request(path,'GET',undefined,false);
|
||||
assert.equal(result.status,403);
|
||||
records.push({role,path,status:result.status});
|
||||
}
|
||||
}
|
||||
await fs.writeFile('docs/proofs/audit-metadata-http.json',JSON.stringify({checked_at:new Date().toISOString(),environment:base,result:'passed',records,pilot_grants_changed:false,business_commands:false,visual_review:false,coverage:'Denial before explicit permission; positive access exercised in disposable databases.'},null,2));
|
||||
console.log('Auditoria recusa acesso no piloto sem nova competência explícita.');
|
||||
` + '})()')(fs, assert);
|
||||
@@ -0,0 +1,12 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import assert from 'node:assert/strict';
|
||||
const source = await fs.readFile('scripts/supplier-evaluation-check.mjs', 'utf8');
|
||||
const proofName = process.argv.includes('--additional') ? 'candidate-additional-geography-http.json' : 'candidate-geography-http.json';
|
||||
const common = source.slice(source.indexOf('const base ='), source.indexOf('const author ='));
|
||||
await new Function('fs','assert','return (async()=>{'+common+`
|
||||
const guest=await fetch(base+'/candidaturas/1/divisao-administrativa',{headers:{Accept:'application/json'},redirect:'manual'}); assert.equal(guest.status,401);
|
||||
const staff=client(); await staff.enter('admin'); assert.equal((await staff.request('/contexto','POST',{area:'procurement',option:'procurement.internal'})).status,302); const denied=await staff.request('/candidaturas/1/divisao-administrativa','GET',undefined,false); assert.equal(denied.status,401);
|
||||
const catalogue=await staff.request('/api/divisao-administrativa'); assert.equal(catalogue.status,200); assert.equal(catalogue.page.data.length,11);
|
||||
await fs.writeFile('docs/proofs/${proofName}',JSON.stringify({checked_at:new Date().toISOString(),environment:base,result:'passed',guest_status:401,staff_without_candidate_status:401,existing_staff_catalogue_status:200,registration_commands:false,profile_commands:false,portal_configuration_commands:false,business_commands:false,mfa_activation:false,positive_candidate_session_coverage:'disposable tests only',visual_review:false},null,2));
|
||||
console.log('Candidate catalogue denied to guests and staff-only sessions; no candidate registration or profile changes.');
|
||||
`+'})()')(fs,assert);
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
require dirname(__DIR__).'/vendor/autoload.php';
|
||||
$root = dirname(__DIR__);
|
||||
$settings = json_decode(file_get_contents('C:/laragon/data/sgf-tempo/secrets/payables-demo-compras.json'), true, 32, JSON_THROW_ON_ERROR);
|
||||
if (($settings['APP_ENV'] ?? '') !== 'payable-testing' || ($settings['DB_HOST'] ?? '') !== '127.0.0.1' || realpath($settings['HOST_ROOT'] ?? '') !== realpath($root)
|
||||
|| ! preg_match('/^compras_receipts_test_([a-f0-9]{12})$/D', $settings['DB_DATABASE'] ?? '', $matches) || $settings['DB_USERNAME'] !== 'payable_qa_'.$matches[1]) {
|
||||
throw new LogicException('Wrong pilot destination.');
|
||||
}
|
||||
$app = require $root.'/bootstrap/app.php';
|
||||
$app->make(Kernel::class)->bootstrap();
|
||||
$base = DB::connection('pgsql')->getConfig();
|
||||
unset($base['name']);
|
||||
config(['database.connections.geography_pilot_read' => [...$base, 'url' => null, 'host' => $settings['DB_HOST'], 'port' => $settings['DB_PORT'],
|
||||
'database' => $settings['DB_DATABASE'], 'username' => $settings['DB_USERNAME'], 'password' => $settings['DB_PASSWORD']]]);
|
||||
$db = DB::connection('geography_pilot_read');
|
||||
$result = $db->transaction(function () use ($db, $settings) {
|
||||
$db->statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY');
|
||||
$identity = $db->selectOne('select current_database() as db, current_user as role');
|
||||
if ($identity->db !== $settings['DB_DATABASE'] || $identity->role !== $settings['DB_USERNAME']) {
|
||||
throw new LogicException('Identity mismatch.');
|
||||
}
|
||||
$schema = Schema::connection('geography_pilot_read');
|
||||
|
||||
return ['checked_at' => gmdate('c'), 'environment' => 'http://127.0.0.1:8005', 'result' => 'passed',
|
||||
'candidate_accounts' => $db->table('candidate_accounts')->count(),
|
||||
'candidate_profile_versions' => $db->table('candidate_profile_versions')->count(),
|
||||
'candidate_portals_configured' => $db->table('candidate_portal_versions')->count(),
|
||||
'warehouse_column_installed' => $schema->hasColumn('warehouses', 'geography_reference'),
|
||||
'equipment_column_installed' => $schema->hasColumn('equipment', 'geography_reference'),
|
||||
'operational_flag' => filter_var($settings['SGC_OPERATIONAL_GEOGRAPHY_ENABLED'] ?? false, FILTER_VALIDATE_BOOLEAN),
|
||||
'operational_migration_applied' => $db->table('migrations')->where('migration', '2026_11_03_000005_add_operational_geography_references')->exists(),
|
||||
'supplier_column_installed' => $schema->hasColumn('suppliers', 'geography_reference'),
|
||||
'company_column_installed' => $schema->hasColumn('tenants', 'geography_reference'),
|
||||
'migration_applied' => $db->table('migrations')->where('migration', '2026_11_03_000004_add_administrative_geography_references')->exists(),
|
||||
'mfa_accounts_enrolled' => $db->table('users')->where(fn ($q) => $q->whereNotNull('two_factor_secret')->orWhereNotNull('two_factor_recovery_codes')->orWhereNotNull('two_factor_confirmed_at'))->count(),
|
||||
'access_version' => (int) $db->table('organization_access')->where('tenant_id', 1)->value('version'),
|
||||
'business_commands' => false, 'geography_flag' => filter_var($settings['SGC_GEOGRAPHY_ENABLED'] ?? false, FILTER_VALIDATE_BOOLEAN),
|
||||
'classified_suppliers' => $db->table('suppliers')->whereNotNull('geography_reference')->count(),
|
||||
'classified_companies' => $db->table('tenants')->whereNotNull('geography_reference')->count()];
|
||||
});
|
||||
if (! $result['migration_applied'] || ! $result['geography_flag'] || $result['warehouse_column_installed'] || $result['equipment_column_installed'] || $result['operational_migration_applied'] || $result['operational_flag'] || $result['mfa_accounts_enrolled']) {
|
||||
throw new LogicException('Pilot differs from the expected operational geography preparation scope.');
|
||||
}
|
||||
$proofName = match (true) {
|
||||
in_array('--received-availability', $argv ?? [], true) => 'received-availability-pilot.json',
|
||||
in_array('--territory', $argv ?? [], true) => 'candidate-territory-pilot.json',
|
||||
in_array('--additional', $argv ?? [], true) => 'candidate-additional-geography-pilot.json',
|
||||
default => 'candidate-geography-pilot.json',
|
||||
};
|
||||
file_put_contents($root.'/docs/proofs/'.$proofName, json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES).PHP_EOL);
|
||||
|
||||
echo 'Candidate geography checked read-only: no profile or portal commands; operational geography remains pending.'.PHP_EOL;
|
||||
@@ -0,0 +1,14 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import assert from 'node:assert/strict';
|
||||
const source=await fs.readFile('scripts/supplier-evaluation-check.mjs','utf8');
|
||||
const common=source.slice(source.indexOf('const base ='),source.indexOf('const author ='));
|
||||
await new Function('fs','assert','return (async()=>{'+common+`
|
||||
const viewer=client(); await viewer.enter('admin');
|
||||
assert.equal((await viewer.request('/contexto','POST',{area:'people',option:'people.internal'})).status,302);
|
||||
const index=await viewer.request('/pessoas'); assert.equal(index.status,200);
|
||||
const missing=await viewer.request('/pessoas/999999/divisao-administrativa','GET',undefined,false); assert.equal(missing.status,404);
|
||||
assert.equal((await viewer.request('/api/divisao-administrativa','GET',undefined,false)).status,403);
|
||||
let inboxes=0; for(const record of index.page.props.processes.data.slice(0,2)) { const page=await viewer.request('/pessoas/'+record.id+'/recebidas'); if(page.status===200) { assert.equal(page.page.props.geographyEnabled,true); inboxes++; } else { assert.equal(page.status,403); } }
|
||||
await fs.writeFile('docs/proofs/candidate-territory-http.json',JSON.stringify({checked_at:new Date().toISOString(),environment:base,result:'passed',people_index_status:200,unknown_process_catalogue_status:404,procurement_catalogue_in_people_context_status:403,visible_inboxes_checked:inboxes,business_commands:false,process_or_portal_created:false,profile_commands:false,triage_commands:false,visual_review:false,positive_process_filter_coverage:'disposable tests'},null,2));
|
||||
console.log('People access remains scoped; no new processes, profiles or triage decisions.');
|
||||
`+'})()')(fs,assert);
|
||||
@@ -0,0 +1,24 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import assert from 'node:assert/strict';
|
||||
const source = await fs.readFile('scripts/supplier-evaluation-check.mjs', 'utf8');
|
||||
const common = source.slice(source.indexOf('const base ='), source.indexOf('const author ='));
|
||||
await new Function('fs', 'assert', 'return (async()=>{' + common + `
|
||||
const records=[];
|
||||
for(const role of ['admin','warehouse']) {
|
||||
const viewer=client(); await viewer.enter(role);
|
||||
assert.equal((await viewer.request('/contexto','POST',{area:'procurement',option:'procurement.internal'})).status,302);
|
||||
const index=await viewer.request('/equipamentos'); assert.equal(index.status,200);
|
||||
let detailsChecked=0;
|
||||
for(const equipment of index.page.props.equipment.slice(0,2)) {
|
||||
const detail=await viewer.request('/equipamentos/'+equipment.id); assert.equal(detail.status,200);
|
||||
assert.equal(detail.page.props.equipment.id,equipment.id);
|
||||
assert.ok(Array.isArray(detail.page.props.purchases.data));
|
||||
assert.ok(Array.isArray(detail.page.props.movements.data));
|
||||
assert.ok(detail.page.props.movements.data.every(row=>row.snapshot===undefined));
|
||||
detailsChecked++;
|
||||
}
|
||||
records.push({role,detailsChecked});
|
||||
}
|
||||
await fs.writeFile('docs/proofs/equipment-integration-http.json',JSON.stringify({checked_at:new Date().toISOString(),environment:base,result:'passed',records,business_commands:false,grants_changed:false,visual_review:false,coverage:'Existing equipment only; complete native journeys exercised in disposable databases.'},null,2));
|
||||
console.log('Equipamentos consultados no piloto; sem novas compras ou movimentos.');
|
||||
` + '})()')(fs, assert);
|
||||
@@ -0,0 +1,21 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import assert from 'node:assert/strict';
|
||||
const source = await fs.readFile('scripts/supplier-evaluation-check.mjs', 'utf8');
|
||||
const common = source.slice(source.indexOf('const base ='), source.indexOf('const author ='));
|
||||
await new Function('fs', 'assert', 'return (async()=>{' + common + `
|
||||
const records=[];
|
||||
for(const role of ['admin','procurement','finance']) {
|
||||
const viewer=client(); await viewer.enter(role);
|
||||
assert.equal((await viewer.request('/contexto','POST',{area:'procurement',option:'procurement.internal'})).status,302);
|
||||
const suppliers=await viewer.request('/fornecedores'); assert.equal(suppliers.status,200); assert.equal(suppliers.page.props.geographyEnabled,true);
|
||||
const catalogue=await viewer.request('/api/divisao-administrativa'); assert.equal(catalogue.status,200); assert.equal(catalogue.page.data.length,11);
|
||||
assert.equal((await viewer.request('/api/divisao-administrativa?parent=2')).status,200);
|
||||
const supplier=suppliers.page.props.suppliers.data[0]; assert.ok(supplier); assert.equal(supplier.geography_reference,null);
|
||||
const detail=await viewer.request('/fornecedores/'+supplier.id); assert.equal(detail.status,200); assert.equal(detail.page.props.geography,null);
|
||||
if(role==='procurement') { const edit=await viewer.request('/fornecedores/'+supplier.id+'/editar'); assert.equal(edit.status,200); assert.equal(edit.page.props.geographyEnabled,true); }
|
||||
if(role==='admin') { const parameters=await viewer.request('/administracao/parametrizacao'); assert.equal(parameters.status,200); assert.equal(parameters.page.props.geographyEnabled,true); assert.equal(parameters.page.props.geography,null); }
|
||||
records.push({role,geographicSelectionAvailable:true,existingSupplierUnclassified:true});
|
||||
}
|
||||
await fs.writeFile('docs/proofs/administrative-geography-installation-http.json',JSON.stringify({checked_at:new Date().toISOString(),environment:base,result:'passed',records,business_commands:false,classification_commands:false,approval_changes:false,mfa_activation:false,visual_review:false},null,2));
|
||||
console.log('Geographic selection available in company and supplier pages; no classification submitted.');
|
||||
` + '})()')(fs, assert);
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
require dirname(__DIR__).'/vendor/autoload.php';
|
||||
$root = dirname(__DIR__);
|
||||
$settings = json_decode(file_get_contents('C:/laragon/data/sgf-tempo/secrets/payables-demo-compras.json'), true, 32, JSON_THROW_ON_ERROR);
|
||||
if (($settings['APP_ENV'] ?? '') !== 'payable-testing' || ($settings['DB_HOST'] ?? '') !== '127.0.0.1' || realpath($settings['HOST_ROOT'] ?? '') !== realpath($root)
|
||||
|| ! preg_match('/^compras_receipts_test_([a-f0-9]{12})$/D', $settings['DB_DATABASE'] ?? '', $matches) || $settings['DB_USERNAME'] !== 'payable_qa_'.$matches[1]) {
|
||||
throw new LogicException('Wrong pilot destination.');
|
||||
}
|
||||
$app = require $root.'/bootstrap/app.php';
|
||||
$app->make(Kernel::class)->bootstrap();
|
||||
$base = DB::connection('pgsql')->getConfig();
|
||||
unset($base['name']);
|
||||
config(['database.connections.geography_pilot_read' => [...$base, 'url' => null, 'host' => $settings['DB_HOST'], 'port' => $settings['DB_PORT'],
|
||||
'database' => $settings['DB_DATABASE'], 'username' => $settings['DB_USERNAME'], 'password' => $settings['DB_PASSWORD']]]);
|
||||
$db = DB::connection('geography_pilot_read');
|
||||
$result = $db->transaction(function () use ($db, $settings) {
|
||||
$db->statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY');
|
||||
$identity = $db->selectOne('select current_database() as db, current_user as role');
|
||||
if ($identity->db !== $settings['DB_DATABASE'] || $identity->role !== $settings['DB_USERNAME']) {
|
||||
throw new LogicException('Identity mismatch.');
|
||||
}
|
||||
$schema = Schema::connection('geography_pilot_read');
|
||||
|
||||
return ['checked_at' => gmdate('c'), 'environment' => 'http://127.0.0.1:8005', 'result' => 'passed',
|
||||
'supplier_column_installed' => $schema->hasColumn('suppliers', 'geography_reference'),
|
||||
'company_column_installed' => $schema->hasColumn('tenants', 'geography_reference'),
|
||||
'migration_applied' => $db->table('migrations')->where('migration', '2026_11_03_000004_add_administrative_geography_references')->exists(),
|
||||
'mfa_accounts_enrolled' => $db->table('users')->where(fn ($q) => $q->whereNotNull('two_factor_secret')->orWhereNotNull('two_factor_recovery_codes')->orWhereNotNull('two_factor_confirmed_at'))->count(),
|
||||
'access_version' => (int) $db->table('organization_access')->where('tenant_id', 1)->value('version'),
|
||||
'business_commands' => false, 'geography_flag' => filter_var($settings['SGC_GEOGRAPHY_ENABLED'] ?? false, FILTER_VALIDATE_BOOLEAN),
|
||||
'classified_suppliers' => $db->table('suppliers')->whereNotNull('geography_reference')->count(),
|
||||
'classified_companies' => $db->table('tenants')->whereNotNull('geography_reference')->count()];
|
||||
});
|
||||
if (! $result['migration_applied'] || ! $result['supplier_column_installed'] || ! $result['company_column_installed'] || $result['mfa_accounts_enrolled'] || ! $result['geography_flag'] || $result['classified_suppliers'] || $result['classified_companies']) {
|
||||
throw new LogicException('Pilot differs from the authorized installed and unclassified geography scope.');
|
||||
}
|
||||
file_put_contents($root.'/docs/proofs/administrative-geography-installation-pilot.json', json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES).PHP_EOL);
|
||||
echo 'Geography pilot inspected read-only: columns empty and selection enabled; no business commands.'.PHP_EOL;
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
require dirname(__DIR__).'/vendor/autoload.php';
|
||||
$root = dirname(__DIR__);
|
||||
$settings = json_decode(file_get_contents('C:/laragon/data/sgf-tempo/secrets/payables-demo-compras.json'), true, 32, JSON_THROW_ON_ERROR);
|
||||
if (($settings['APP_ENV'] ?? '') !== 'payable-testing' || ($settings['DB_HOST'] ?? '') !== '127.0.0.1' || realpath($settings['HOST_ROOT'] ?? '') !== realpath($root)
|
||||
|| ! preg_match('/^compras_receipts_test_([a-f0-9]{12})$/D', $settings['DB_DATABASE'] ?? '', $matches) || $settings['DB_USERNAME'] !== 'payable_qa_'.$matches[1]) {
|
||||
throw new LogicException('Wrong pilot destination.');
|
||||
}
|
||||
$app = require $root.'/bootstrap/app.php';
|
||||
$app->make(Kernel::class)->bootstrap();
|
||||
$base = DB::connection('pgsql')->getConfig();
|
||||
unset($base['name']);
|
||||
config(['database.connections.geography_pilot_read' => [...$base, 'url' => null, 'host' => $settings['DB_HOST'], 'port' => $settings['DB_PORT'],
|
||||
'database' => $settings['DB_DATABASE'], 'username' => $settings['DB_USERNAME'], 'password' => $settings['DB_PASSWORD']]]);
|
||||
$db = DB::connection('geography_pilot_read');
|
||||
$result = $db->transaction(function () use ($db, $settings) {
|
||||
$db->statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY');
|
||||
$identity = $db->selectOne('select current_database() as db, current_user as role');
|
||||
if ($identity->db !== $settings['DB_DATABASE'] || $identity->role !== $settings['DB_USERNAME']) {
|
||||
throw new LogicException('Identity mismatch.');
|
||||
}
|
||||
$schema = Schema::connection('geography_pilot_read');
|
||||
|
||||
return ['checked_at' => gmdate('c'), 'environment' => 'http://127.0.0.1:8005', 'result' => 'passed',
|
||||
'supplier_column_installed' => $schema->hasColumn('suppliers', 'geography_reference'),
|
||||
'company_column_installed' => $schema->hasColumn('tenants', 'geography_reference'),
|
||||
'migration_applied' => $db->table('migrations')->where('migration', '2026_11_03_000004_add_administrative_geography_references')->exists(),
|
||||
'mfa_accounts_enrolled' => $db->table('users')->where(fn ($q) => $q->whereNotNull('two_factor_secret')->orWhereNotNull('two_factor_recovery_codes')->orWhereNotNull('two_factor_confirmed_at'))->count(),
|
||||
'access_version' => (int) $db->table('organization_access')->where('tenant_id', 1)->value('version'),
|
||||
'business_commands' => false, 'geography_flag' => config('geography.enabled')];
|
||||
});
|
||||
if ($result['migration_applied'] || $result['supplier_column_installed'] || $result['company_column_installed'] || $result['mfa_accounts_enrolled'] || $result['geography_flag']) {
|
||||
throw new LogicException('Pilot differs from the expected uninstalled geography scope.');
|
||||
}
|
||||
file_put_contents($root.'/docs/proofs/administrative-geography-pilot.json', json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES).PHP_EOL);
|
||||
echo 'Geography pilot inspected read-only: columns absent; no business commands.'.PHP_EOL;
|
||||
@@ -0,0 +1,17 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import assert from 'node:assert/strict';
|
||||
const source = await fs.readFile('scripts/supplier-evaluation-check.mjs', 'utf8');
|
||||
const common = source.slice(source.indexOf('const base ='), source.indexOf('const author ='));
|
||||
await new Function('fs', 'assert', 'return (async()=>{' + common + `
|
||||
const records=[];
|
||||
const admin=client(); await admin.enter('admin');
|
||||
assert.equal((await admin.request('/contexto','POST',{area:'procurement',option:'procurement.internal'})).status,302);
|
||||
const page=await admin.request('/administracao/divisao-administrativa'); assert.equal(page.status,200); assert.equal(page.page.props.total,10729); assert.equal(page.page.props.records.length,50); assert.equal(page.page.encryptHistory,true);
|
||||
const filtered=await admin.request('/administracao/divisao-administrativa?issue=blank_designation'); assert.equal(filtered.status,200); assert.equal(filtered.page.props.total,9);
|
||||
const exported=await admin.request('/administracao/divisao-administrativa.csv?issue=blank_designation','GET',undefined,false); assert.equal(exported.status,200); assert.ok(exported.text.startsWith('Linha,ID,Provincia'));
|
||||
const buyer=client(); await buyer.enter('procurement'); await buyer.request('/contexto','POST',{area:'procurement',option:'procurement.internal'}); assert.equal((await buyer.request('/administracao/divisao-administrativa','GET',undefined,false)).status,403);
|
||||
records.push({role:'admin',pendingRecords:10729,firstPage:50,blankDesignations:9,exportStatus:200});
|
||||
records.push({role:'procurement',reviewStatus:403});
|
||||
await fs.writeFile('docs/proofs/administrative-geography-review-http.json',JSON.stringify({checked_at:new Date().toISOString(),environment:base,result:'passed',records,business_commands:false,classification_commands:false,approval_changes:false,mfa_activation:false,visual_review:false},null,2));
|
||||
console.log('Administrative review and filtered CSV checked read-only; no geography or entity changes.');
|
||||
` + '})()')(fs, assert);
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Laravel\Fortify\Features;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
require dirname(__DIR__).'/vendor/autoload.php';
|
||||
$root = dirname(__DIR__);
|
||||
if (! in_array('--apply-authorized', $argv, true)) {
|
||||
throw new LogicException('Explicit preparation flag required.');
|
||||
}
|
||||
$s = json_decode(file_get_contents('C:/laragon/data/sgf-tempo/secrets/payables-demo-compras.json'), true, 32, JSON_THROW_ON_ERROR);
|
||||
if (($s['APP_ENV'] ?? '') !== 'payable-testing' || ($s['DB_HOST'] ?? '') !== '127.0.0.1' || realpath($s['HOST_ROOT'] ?? '') !== realpath($root) || ! preg_match('/^compras_receipts_test_([a-f0-9]{12})$/D', $s['DB_DATABASE'] ?? '', $m) || $s['DB_USERNAME'] !== 'payable_qa_'.$m[1]) {
|
||||
throw new LogicException('Wrong pilot destination.');
|
||||
}
|
||||
$app = require $root.'/bootstrap/app.php';
|
||||
$app->make(Kernel::class)->bootstrap();
|
||||
$owner = DB::connection('pgsql')->getConfig();
|
||||
unset($owner['name']);
|
||||
if ($owner['host'] !== '127.0.0.1') {
|
||||
throw new LogicException('Local server required.');
|
||||
}
|
||||
$qa = [...$owner, 'url' => null, 'database' => $s['DB_DATABASE'], 'username' => $s['DB_USERNAME'], 'password' => $s['DB_PASSWORD']];
|
||||
config(['database.connections.receipt_install' => $qa, 'database.connections.receipt_owner' => [...$owner, 'url' => null, 'database' => 'postgres']]);
|
||||
DB::setDefaultConnection('receipt_install');
|
||||
$db = DB::connection();
|
||||
$admin = DB::connection('receipt_owner');
|
||||
$identity = $db->selectOne('select current_database() as db, current_user as role');
|
||||
if ($identity->db !== $s['DB_DATABASE'] || $identity->role !== $s['DB_USERNAME']) {
|
||||
throw new LogicException('Identity mismatch.');
|
||||
}
|
||||
$fingerprint = function ($connection, $tables) {
|
||||
$out = [];
|
||||
foreach ($tables as $table) {
|
||||
$rows = $connection->select('select row_to_json(t)::text as row from "'.str_replace('"', '""', $table).'" t order by row_to_json(t)::text');
|
||||
$out[$table] = hash('sha256', json_encode(array_column($rows, 'row'), JSON_THROW_ON_ERROR));
|
||||
}
|
||||
|
||||
return $out;
|
||||
};
|
||||
$pg = function ($binary, $args, $connection) {
|
||||
$p = new Process(['C:/Program Files/PostgreSQL/17/bin/'.$binary.'.exe', ...$args], null, ['PGHOST' => $connection['host'], 'PGPORT' => (string) $connection['port'], 'PGUSER' => $connection['username'], 'PGPASSWORD' => $connection['password']], timeout: 120);
|
||||
$p->run();
|
||||
if (! $p->isSuccessful()) {
|
||||
throw new RuntimeException('Backup/restore failed.');
|
||||
}
|
||||
};
|
||||
|
||||
$tables = array_column($db->select("select tablename from pg_tables where schemaname='public' order by tablename"), 'tablename');
|
||||
$migration = 'database/migrations/2026_11_03_000004_add_administrative_geography_references.php';
|
||||
$authorization = json_decode(file_get_contents($root.'/docs/proofs/administrative-geography-installation-authorization.json'), true, 32, JSON_THROW_ON_ERROR);
|
||||
if ($authorization['scope'] !== 'install_two_nullable_geography_columns_and_enable_selection'
|
||||
|| $authorization['environment'] !== 'http://127.0.0.1:8005'
|
||||
|| $authorization['proposal_sha256'] !== hash_file('sha256', $root.'/docs/proposta-instalacao-geografia.md')
|
||||
|| $authorization['migration_sha256'] !== hash_file('sha256', $root.'/'.$migration)
|
||||
|| file_exists($root.'/docs/proofs/administrative-geography-installation.json')
|
||||
|| Features::enabled(Features::twoFactorAuthentication())
|
||||
|| filter_var($s['SGC_TWO_FACTOR_ENABLED'] ?? false, FILTER_VALIDATE_BOOLEAN)
|
||||
|| filter_var($s['SGC_GEOGRAPHY_ENABLED'] ?? false, FILTER_VALIDATE_BOOLEAN)) {
|
||||
throw new LogicException('Authorization or pilot state does not match empty geography installation.');
|
||||
}
|
||||
foreach (['tenants', 'suppliers'] as $table) {
|
||||
if (Schema::connection('receipt_install')->hasColumn($table, 'geography_reference')) {
|
||||
throw new LogicException('Geography schema already present; inspect proof instead of repeating.');
|
||||
}
|
||||
}
|
||||
$oldColumns = [];
|
||||
foreach (['tenants', 'suppliers'] as $table) {
|
||||
$oldColumns[$table] = Schema::connection('receipt_install')->getColumnListing($table);
|
||||
}
|
||||
$projected = function ($connection) use ($oldColumns) {
|
||||
$out = [];
|
||||
foreach ($oldColumns as $table => $columns) {
|
||||
$selected = implode(',', array_map(fn ($c) => '"'.str_replace('"', '""', $c).'"', $columns));
|
||||
$rows = $connection->select('select row_to_json(t)::text as row from (select '.$selected.' from "'.$table.'") t order by row_to_json(t)::text');
|
||||
$out[$table] = hash('sha256', json_encode(array_column($rows, 'row'), JSON_THROW_ON_ERROR));
|
||||
}
|
||||
|
||||
return $out;
|
||||
};
|
||||
$checkEmpty = function ($connection, $name) use ($tables, $oldColumns) {
|
||||
foreach ($oldColumns as $table => $columns) {
|
||||
if (Schema::connection($name)->getColumnListing($table) !== [...$columns, 'geography_reference']
|
||||
|| $connection->table($table)->whereNotNull('geography_reference')->exists()) {
|
||||
throw new LogicException('Unexpected geography structure or nonempty references.');
|
||||
}
|
||||
}
|
||||
if (array_column($connection->select("select tablename from pg_tables where schemaname='public' order by tablename"), 'tablename') !== $tables) {
|
||||
throw new LogicException('Unexpected table created.');
|
||||
}
|
||||
};
|
||||
$restore = 'compras_pilot_restore_'.bin2hex(random_bytes(6));
|
||||
$created = false;
|
||||
try {
|
||||
$db->beginTransaction();
|
||||
$db->statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ');
|
||||
$baseline = $fingerprint($db, $tables);
|
||||
$beforeRows = $projected($db);
|
||||
$migrationCount = $db->table('migrations')->count();
|
||||
$directory = 'C:/laragon/data/sgf-tempo/backups/compras-geography-installation-'.gmdate('Ymd-His').'-'.bin2hex(random_bytes(3));
|
||||
if (! mkdir($directory, 0700, true)) {
|
||||
throw new RuntimeException('Backup unavailable.');
|
||||
}
|
||||
$dump = $directory.'/before.dump';
|
||||
$snapshot = $db->selectOne('select pg_export_snapshot() as snapshot')->snapshot;
|
||||
$pg('pg_dump', ['--format=custom', '--snapshot='.$snapshot, '--file='.$dump, '--dbname='.$qa['database']], $qa);
|
||||
$db->commit();
|
||||
$admin->statement('CREATE DATABASE "'.$restore.'"');
|
||||
$created = true;
|
||||
$pg('pg_restore', ['--no-owner', '--no-privileges', '--exit-on-error', '--dbname='.$restore, $dump], $owner);
|
||||
config(['database.connections.receipt_restore' => [...$owner, 'url' => null, 'database' => $restore]]);
|
||||
$restored = DB::connection('receipt_restore');
|
||||
if ($fingerprint($restored, $tables) !== $baseline) {
|
||||
throw new LogicException('Restored backup differs.');
|
||||
}
|
||||
if (Artisan::call('migrate', ['--database' => 'receipt_restore', '--path' => $migration, '--force' => true]) !== 0) {
|
||||
throw new RuntimeException('Disposable migration failed.');
|
||||
}
|
||||
$protected = array_values(array_diff($tables, ['tenants', 'suppliers', 'migrations']));
|
||||
$checkEmpty($restored, 'receipt_restore');
|
||||
if ($fingerprint($restored, $protected) !== array_intersect_key($baseline, array_flip($protected)) || $projected($restored) !== $beforeRows || $restored->table('migrations')->count() !== $migrationCount + 1) {
|
||||
throw new LogicException('Unexpected dry-run changes.');
|
||||
}
|
||||
$db->transaction(function () use ($db, $tables, $baseline, $fingerprint, $migration, $protected, $projected, $beforeRows, $migrationCount, $checkEmpty) {
|
||||
$db->statement("SET LOCAL lock_timeout = '5s'");
|
||||
foreach ($tables as $table) {
|
||||
$db->statement('LOCK TABLE "'.str_replace('"', '""', $table).'" IN SHARE MODE');
|
||||
}
|
||||
if ($fingerprint($db, $tables) !== $baseline) {
|
||||
throw new LogicException('Pilot changed after backup; abort.');
|
||||
}
|
||||
if (Artisan::call('migrate', ['--database' => 'receipt_install', '--path' => $migration, '--force' => true]) !== 0) {
|
||||
throw new RuntimeException('Pilot migration failed.');
|
||||
}
|
||||
$checkEmpty($db, 'receipt_install');
|
||||
if ($fingerprint($db, $protected) !== array_intersect_key($baseline, array_flip($protected)) || $projected($db) !== $beforeRows || $db->table('migrations')->count() !== $migrationCount + 1) {
|
||||
throw new LogicException('Unexpected pilot changes.');
|
||||
}
|
||||
});
|
||||
$result = ['checked_at' => gmdate('c'), 'result' => 'passed', 'environment' => 'http://127.0.0.1:8005', 'scope' => $authorization['scope'], 'migration' => $migration, 'migration_sha256' => hash_file('sha256', $root.'/'.$migration), 'backup' => $dump, 'backup_sha256' => hash_file('sha256', $dump), 'restored_tables_checked' => count($tables), 'protected_tables_checked' => count($protected), 'existing_company_and_supplier_columns_preserved' => true, 'new_references_all_null' => true, 'pilot_migration_applied' => true, 'feature_activated' => false, 'mfa_enabled' => false, 'passwords_or_grants_changed' => false, 'restore_database_removed' => false];
|
||||
} finally {
|
||||
if ($db->transactionLevel() > 0) {
|
||||
$db->rollBack();
|
||||
}
|
||||
DB::purge('receipt_restore');
|
||||
if ($created) {
|
||||
$admin->statement('DROP DATABASE "'.$restore.'" WITH (FORCE)');
|
||||
}
|
||||
}
|
||||
$result['restore_database_removed'] = true;
|
||||
file_put_contents($root.'/docs/proofs/administrative-geography-installation.json', json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES).PHP_EOL);
|
||||
echo 'Two empty geography columns installed; all prior values preserved; activation remains separate.'.PHP_EOL;
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Laravel\Fortify\Features;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
require dirname(__DIR__).'/vendor/autoload.php';
|
||||
$root = dirname(__DIR__);
|
||||
if (! in_array('--apply-authorized', $argv, true)) {
|
||||
throw new LogicException('Explicit preparation flag required.');
|
||||
}
|
||||
$s = json_decode(file_get_contents('C:/laragon/data/sgf-tempo/secrets/payables-demo-compras.json'), true, 32, JSON_THROW_ON_ERROR);
|
||||
if (($s['APP_ENV'] ?? '') !== 'payable-testing' || ($s['DB_HOST'] ?? '') !== '127.0.0.1' || realpath($s['HOST_ROOT'] ?? '') !== realpath($root) || ! preg_match('/^compras_receipts_test_([a-f0-9]{12})$/D', $s['DB_DATABASE'] ?? '', $m) || $s['DB_USERNAME'] !== 'payable_qa_'.$m[1]) {
|
||||
throw new LogicException('Wrong pilot destination.');
|
||||
}
|
||||
$app = require $root.'/bootstrap/app.php';
|
||||
$app->make(Kernel::class)->bootstrap();
|
||||
$owner = DB::connection('pgsql')->getConfig();
|
||||
unset($owner['name']);
|
||||
if ($owner['host'] !== '127.0.0.1') {
|
||||
throw new LogicException('Local server required.');
|
||||
}
|
||||
$qa = [...$owner, 'url' => null, 'database' => $s['DB_DATABASE'], 'username' => $s['DB_USERNAME'], 'password' => $s['DB_PASSWORD']];
|
||||
config(['database.connections.receipt_install' => $qa, 'database.connections.receipt_owner' => [...$owner, 'url' => null, 'database' => 'postgres']]);
|
||||
DB::setDefaultConnection('receipt_install');
|
||||
$db = DB::connection();
|
||||
$admin = DB::connection('receipt_owner');
|
||||
$identity = $db->selectOne('select current_database() as db, current_user as role');
|
||||
if ($identity->db !== $s['DB_DATABASE'] || $identity->role !== $s['DB_USERNAME']) {
|
||||
throw new LogicException('Identity mismatch.');
|
||||
}
|
||||
$fingerprint = function ($connection, $tables) {
|
||||
$out = [];
|
||||
foreach ($tables as $table) {
|
||||
$rows = $connection->select('select row_to_json(t)::text as row from "'.str_replace('"', '""', $table).'" t order by row_to_json(t)::text');
|
||||
$out[$table] = hash('sha256', json_encode(array_column($rows, 'row'), JSON_THROW_ON_ERROR));
|
||||
}
|
||||
|
||||
return $out;
|
||||
};
|
||||
$pg = function ($binary, $args, $connection) {
|
||||
$p = new Process(['C:/Program Files/PostgreSQL/17/bin/'.$binary.'.exe', ...$args], null, ['PGHOST' => $connection['host'], 'PGPORT' => (string) $connection['port'], 'PGUSER' => $connection['username'], 'PGPASSWORD' => $connection['password']], timeout: 120);
|
||||
$p->run();
|
||||
if (! $p->isSuccessful()) {
|
||||
throw new RuntimeException('Backup/restore failed.');
|
||||
}
|
||||
};
|
||||
$tables = array_column($db->select("select tablename from pg_tables where schemaname='public' order by tablename"), 'tablename');
|
||||
$migration = 'database/migrations/2026_11_03_000001_add_two_factor_columns_to_users.php';
|
||||
$proposalFile = $root.'/docs/proofs/mfa-pilot-schema-proposal.json';
|
||||
$authorization = json_decode(ltrim(file_get_contents($root.'/docs/proofs/mfa-pilot-schema-authorization.json'), "\xEF\xBB\xBF"), true, 32, JSON_THROW_ON_ERROR);
|
||||
$proposal = json_decode(ltrim(file_get_contents($proposalFile), "\xEF\xBB\xBF"), true, 32, JSON_THROW_ON_ERROR);
|
||||
if ($authorization['proposal_sha256'] !== hash_file('sha256', $proposalFile) || $authorization['scope'] !== 'install_nullable_mfa_columns_only' || $authorization['environment'] !== 'http://127.0.0.1:8005' || $proposal['migration_sha256'] !== hash_file('sha256', $root.'/'.$migration) || file_exists($root.'/docs/proofs/mfa-pilot-schema-installation.json') || Features::enabled(Features::twoFactorAuthentication()) || filter_var($s['SGC_TWO_FACTOR_ENABLED'] ?? false, FILTER_VALIDATE_BOOLEAN)) {
|
||||
throw new LogicException('Wrong authorization, destination, feature state or already installed.');
|
||||
}
|
||||
foreach (['two_factor_secret', 'two_factor_recovery_codes', 'two_factor_confirmed_at'] as $column) {
|
||||
if (Schema::connection('receipt_install')->hasColumn('users', $column)) {
|
||||
throw new LogicException('MFA schema already exists; inspect before repeating.');
|
||||
}
|
||||
}
|
||||
$restore = 'compras_pilot_restore_'.bin2hex(random_bytes(6));
|
||||
$created = false;
|
||||
try {
|
||||
$db->beginTransaction();
|
||||
$db->statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ');
|
||||
$baseline = $fingerprint($db, $tables);
|
||||
$directory = 'C:/laragon/data/sgf-tempo/backups/compras-mfa-installation-'.gmdate('Ymd-His').'-'.bin2hex(random_bytes(3));
|
||||
if (! mkdir($directory, 0700, true)) {
|
||||
throw new RuntimeException('Backup unavailable.');
|
||||
}
|
||||
$dump = $directory.'/before.dump';
|
||||
$snapshot = $db->selectOne('select pg_export_snapshot() as snapshot')->snapshot;
|
||||
$pg('pg_dump', ['--format=custom', '--snapshot='.$snapshot, '--file='.$dump, '--dbname='.$qa['database']], $qa);
|
||||
$db->commit();
|
||||
$admin->statement('CREATE DATABASE "'.$restore.'"');
|
||||
$created = true;
|
||||
$pg('pg_restore', ['--no-owner', '--no-privileges', '--exit-on-error', '--dbname='.$restore, $dump], $owner);
|
||||
config(['database.connections.receipt_restore' => [...$owner, 'url' => null, 'database' => $restore]]);
|
||||
$restored = DB::connection('receipt_restore');
|
||||
if ($fingerprint($restored, $tables) !== $baseline) {
|
||||
throw new LogicException('Restored backup differs.');
|
||||
}
|
||||
$columns = Schema::connection('receipt_restore')->getColumnListing('users');
|
||||
$userRows = function ($connection) use ($columns) {
|
||||
$selected = implode(',', array_map(fn ($c) => '"'.str_replace('"', '""', $c).'"', $columns));
|
||||
|
||||
return $connection->select('select row_to_json(t)::text as row from (select '.$selected.' from users) t order by row_to_json(t)::text');
|
||||
};
|
||||
$beforeUsers = $userRows($restored);
|
||||
$migrationCount = $restored->table('migrations')->count();
|
||||
if (Artisan::call('migrate', ['--database' => 'receipt_restore', '--path' => $migration, '--force' => true]) !== 0) {
|
||||
throw new RuntimeException('Disposable migration failed.');
|
||||
}
|
||||
$protected = array_values(array_diff($tables, ['users', 'migrations']));
|
||||
if ($fingerprint($restored, $protected) !== array_intersect_key($baseline, array_flip($protected)) || $userRows($restored) != $beforeUsers || $restored->table('migrations')->count() !== $migrationCount + 1) {
|
||||
throw new LogicException('Unexpected migration changes.');
|
||||
}
|
||||
foreach (['two_factor_secret', 'two_factor_recovery_codes', 'two_factor_confirmed_at'] as $column) {
|
||||
if ($restored->table('users')->whereNotNull($column)->exists()) {
|
||||
throw new LogicException('Unexpected enrollment data.');
|
||||
}
|
||||
}
|
||||
$db->transaction(function () use ($db, $tables, $baseline, $fingerprint, $migration, $protected, $userRows, $beforeUsers, $migrationCount) {
|
||||
$db->statement("SET LOCAL lock_timeout = '5s'");
|
||||
foreach ($tables as $table) {
|
||||
$db->statement('LOCK TABLE "'.str_replace('"', '""', $table).'" IN SHARE MODE');
|
||||
}
|
||||
if ($fingerprint($db, $tables) !== $baseline) {
|
||||
throw new LogicException('Pilot changed after backup; abort.');
|
||||
}
|
||||
if (Artisan::call('migrate', ['--database' => 'receipt_install', '--path' => $migration, '--force' => true]) !== 0) {
|
||||
throw new RuntimeException('Pilot migration failed.');
|
||||
}
|
||||
if ($fingerprint($db, $protected) !== array_intersect_key($baseline, array_flip($protected)) || $userRows($db) != $beforeUsers || $db->table('migrations')->count() !== $migrationCount + 1) {
|
||||
throw new LogicException('Pilot values changed unexpectedly.');
|
||||
}
|
||||
foreach (['two_factor_secret', 'two_factor_recovery_codes', 'two_factor_confirmed_at'] as $column) {
|
||||
if ($db->table('users')->whereNotNull($column)->exists()) {
|
||||
throw new LogicException('Fields not empty.');
|
||||
}
|
||||
}
|
||||
});
|
||||
$result = ['checked_at' => gmdate('c'), 'result' => 'passed', 'environment' => 'http://127.0.0.1:8005', 'scope' => 'install_nullable_mfa_columns_only', 'migration' => $migration, 'migration_sha256' => hash_file('sha256', $root.'/'.$migration), 'backup' => $dump, 'backup_sha256' => hash_file('sha256', $dump), 'restored_tables_checked' => count($tables), 'protected_tables_checked' => count($protected), 'existing_user_columns_preserved' => true, 'existing_users' => $restored->table('users')->count(), 'enrollment_created' => false, 'pilot_migration_applied' => true, 'pilot_configuration_changed' => false, 'passwords_or_grants_changed' => false, 'restore_database_removed' => false];
|
||||
} finally {
|
||||
if ($db->transactionLevel() > 0) {
|
||||
$db->rollBack();
|
||||
}
|
||||
DB::purge('receipt_restore');
|
||||
if ($created) {
|
||||
$admin->statement('DROP DATABASE "'.$restore.'" WITH (FORCE)');
|
||||
}
|
||||
}
|
||||
$result['restore_database_removed'] = true;
|
||||
file_put_contents($root.'/docs/proofs/mfa-pilot-schema-installation.json', json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES).PHP_EOL);
|
||||
echo 'Three empty MFA fields installed; feature remains disabled.'.PHP_EOL;
|
||||
@@ -1,63 +1,120 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
require dirname(__DIR__).'/vendor/autoload.php';
|
||||
$root = dirname(__DIR__);
|
||||
if (!in_array('--apply-authorized', $argv, true)) { throw new LogicException('Explicit authorized installation required.'); }
|
||||
if (! in_array('--apply-authorized', $argv, true)) {
|
||||
throw new LogicException('Explicit authorized installation required.');
|
||||
}
|
||||
$s = json_decode(file_get_contents('C:/laragon/data/sgf-tempo/secrets/payables-demo-compras.json'), true, 32, JSON_THROW_ON_ERROR);
|
||||
if (($s['APP_ENV'] ?? '') !== 'payable-testing' || ($s['DB_HOST'] ?? '') !== '127.0.0.1' || realpath($s['HOST_ROOT'] ?? '') !== realpath($root) || !preg_match('/^compras_receipts_test_([a-f0-9]{12})$/D', $s['DB_DATABASE'] ?? '', $m) || $s['DB_USERNAME'] !== 'payable_qa_'.$m[1]) { throw new LogicException('Wrong pilot destination.'); }
|
||||
if (($s['APP_ENV'] ?? '') !== 'payable-testing' || ($s['DB_HOST'] ?? '') !== '127.0.0.1' || realpath($s['HOST_ROOT'] ?? '') !== realpath($root) || ! preg_match('/^compras_receipts_test_([a-f0-9]{12})$/D', $s['DB_DATABASE'] ?? '', $m) || $s['DB_USERNAME'] !== 'payable_qa_'.$m[1]) {
|
||||
throw new LogicException('Wrong pilot destination.');
|
||||
}
|
||||
$app = require $root.'/bootstrap/app.php';
|
||||
$app->make(Kernel::class)->bootstrap();
|
||||
$owner = DB::connection('pgsql')->getConfig(); unset($owner['name']);
|
||||
if ($owner['host'] !== '127.0.0.1') { throw new LogicException('Local server required.'); }
|
||||
$qa = [...$owner, 'url'=>null, 'database'=>$s['DB_DATABASE'], 'username'=>$s['DB_USERNAME'], 'password'=>$s['DB_PASSWORD']];
|
||||
config(['database.connections.receipt_install'=>$qa, 'database.connections.receipt_owner'=>[...$owner,'url'=>null,'database'=>'postgres']]);
|
||||
DB::setDefaultConnection('receipt_install');
|
||||
$db = DB::connection(); $admin = DB::connection('receipt_owner');
|
||||
$identity = $db->selectOne('select current_database() as db, current_user as role');
|
||||
if ($identity->db !== $s['DB_DATABASE'] || $identity->role !== $s['DB_USERNAME']) { throw new LogicException('Identity mismatch.'); }
|
||||
$proofPath = $root.'/docs/proofs/receipt-block-installation.json';
|
||||
if (file_exists($proofPath)) { throw new LogicException('Installation proof exists; inspect before repeating.'); }
|
||||
$tables = array_column($db->select("select tablename from pg_tables where schemaname='public' order by tablename"),'tablename');
|
||||
if (in_array('receipt_documents',$tables,true)) { throw new LogicException('Document table already exists.'); }
|
||||
$fingerprint = function($connection,$tables) { $out=[]; foreach($tables as $table) { $rows=$connection->select('select row_to_json(t)::text as row from "'.str_replace('"','""',$table).'" t order by row_to_json(t)::text'); $out[$table]=hash('sha256',json_encode(array_column($rows,'row'),JSON_THROW_ON_ERROR)); } return $out; };
|
||||
$pg = function($binary,$args,$connection) { $p=new Process(['C:/Program Files/PostgreSQL/17/bin/'.$binary.'.exe',...$args],null,['PGHOST'=>$connection['host'],'PGPORT'=>(string)$connection['port'],'PGUSER'=>$connection['username'],'PGPASSWORD'=>$connection['password']],timeout:120); $p->run(); if(!$p->isSuccessful()) { throw new RuntimeException('Backup/restore failed.'); } };
|
||||
$restore='compras_pilot_restore_'.bin2hex(random_bytes(6)); $created=false;
|
||||
try {
|
||||
$db->beginTransaction(); $db->statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ');
|
||||
$baseline=$fingerprint($db,$tables);
|
||||
$directory='C:/laragon/data/sgf-tempo/backups/compras-receipt-documents-'.gmdate('Ymd-His').'-'.bin2hex(random_bytes(3));
|
||||
if(!mkdir($directory,0700,true)) { throw new RuntimeException('Backup directory unavailable.'); }
|
||||
$dump=$directory.'/before.dump';
|
||||
$snapshot=$db->selectOne('select pg_export_snapshot() as snapshot')->snapshot;
|
||||
$pg('pg_dump',['--format=custom','--snapshot='.$snapshot,'--file='.$dump,'--dbname='.$qa['database']],$qa);
|
||||
$db->commit();
|
||||
$admin->statement('CREATE DATABASE "'.$restore.'"'); $created=true;
|
||||
$pg('pg_restore',['--no-owner','--no-privileges','--exit-on-error','--dbname='.$restore,$dump],$owner);
|
||||
config(['database.connections.receipt_restore'=>[...$owner,'url'=>null,'database'=>$restore]]);
|
||||
if($fingerprint(DB::connection('receipt_restore'),$tables)!==$baseline) { throw new LogicException('Restored backup differs.'); }
|
||||
$disk=Storage::disk('receipt_documents'); $probe='.installation-'.bin2hex(random_bytes(8));
|
||||
try { $disk->put($probe,'private-storage-probe'); if($disk->get($probe)!=='private-storage-probe') { throw new RuntimeException('Storage unavailable.'); } } finally { $disk->delete($probe); }
|
||||
$migration='database/migrations/2026_11_03_000002_create_receipt_documents.php';
|
||||
$db->transaction(function() use($db,$tables,$baseline,$fingerprint,$migration) {
|
||||
foreach($tables as $table) { $db->statement('LOCK TABLE "'.str_replace('"','""',$table).'" IN SHARE MODE'); }
|
||||
if($fingerprint($db,$tables)!==$baseline) { throw new LogicException('Pilot changed after backup; abort.'); }
|
||||
if(Artisan::call('migrate',['--database'=>'receipt_install','--path'=>$migration,'--force'=>true])!==0) { throw new RuntimeException('Target migration failed.'); }
|
||||
$protected=array_values(array_diff($tables,['migrations']));
|
||||
if($fingerprint($db,$protected)!==array_intersect_key($baseline,array_flip($protected))) { throw new LogicException('Business data changed.'); }
|
||||
if($db->table('receipt_documents')->count()!==0 || $db->table('migrations')->where('migration','2026_11_03_000001_add_two_factor_columns_to_users')->exists()) { throw new LogicException('Unexpected installation state.'); }
|
||||
});
|
||||
$proof=['checked_at'=>gmdate('c'),'environment'=>'http://127.0.0.1:8005','result'=>'passed','authorization'=>'User explicitly authorized receipt document block installation','migration'=>$migration,'backup'=>$dump,'backup_sha256'=>hash_file('sha256',$dump),'restored_tables_checked'=>count($tables),'protected_tables_checked'=>count($tables)-1,'business_data_unchanged'=>true,'documents_created'=>0,'mfa_enabled'=>false,'restore_database_removed'=>false];
|
||||
} finally {
|
||||
if($db->transactionLevel()>0) { $db->rollBack(); }
|
||||
DB::purge('receipt_restore');
|
||||
if($created) { $admin->statement('DROP DATABASE "'.$restore.'" WITH (FORCE)'); }
|
||||
$owner = DB::connection('pgsql')->getConfig();
|
||||
unset($owner['name']);
|
||||
if ($owner['host'] !== '127.0.0.1') {
|
||||
throw new LogicException('Local server required.');
|
||||
}
|
||||
$proof['restore_database_removed']=true;
|
||||
file_put_contents($proofPath,json_encode($proof,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES).PHP_EOL);
|
||||
$qa = [...$owner, 'url' => null, 'database' => $s['DB_DATABASE'], 'username' => $s['DB_USERNAME'], 'password' => $s['DB_PASSWORD']];
|
||||
config(['database.connections.receipt_install' => $qa, 'database.connections.receipt_owner' => [...$owner, 'url' => null, 'database' => 'postgres']]);
|
||||
DB::setDefaultConnection('receipt_install');
|
||||
$db = DB::connection();
|
||||
$admin = DB::connection('receipt_owner');
|
||||
$identity = $db->selectOne('select current_database() as db, current_user as role');
|
||||
if ($identity->db !== $s['DB_DATABASE'] || $identity->role !== $s['DB_USERNAME']) {
|
||||
throw new LogicException('Identity mismatch.');
|
||||
}
|
||||
$proofPath = $root.'/docs/proofs/receipt-block-installation.json';
|
||||
if (file_exists($proofPath)) {
|
||||
throw new LogicException('Installation proof exists; inspect before repeating.');
|
||||
}
|
||||
$tables = array_column($db->select("select tablename from pg_tables where schemaname='public' order by tablename"), 'tablename');
|
||||
if (in_array('receipt_documents', $tables, true)) {
|
||||
throw new LogicException('Document table already exists.');
|
||||
}
|
||||
$fingerprint = function ($connection, $tables) {
|
||||
$out = [];
|
||||
foreach ($tables as $table) {
|
||||
$rows = $connection->select('select row_to_json(t)::text as row from "'.str_replace('"', '""', $table).'" t order by row_to_json(t)::text');
|
||||
$out[$table] = hash('sha256', json_encode(array_column($rows, 'row'), JSON_THROW_ON_ERROR));
|
||||
}
|
||||
|
||||
return $out;
|
||||
};
|
||||
$pg = function ($binary, $args, $connection) {
|
||||
$p = new Process(['C:/Program Files/PostgreSQL/17/bin/'.$binary.'.exe', ...$args], null, ['PGHOST' => $connection['host'], 'PGPORT' => (string) $connection['port'], 'PGUSER' => $connection['username'], 'PGPASSWORD' => $connection['password']], timeout: 120);
|
||||
$p->run();
|
||||
if (! $p->isSuccessful()) {
|
||||
throw new RuntimeException('Backup/restore failed.');
|
||||
}
|
||||
};
|
||||
$restore = 'compras_pilot_restore_'.bin2hex(random_bytes(6));
|
||||
$created = false;
|
||||
try {
|
||||
$db->beginTransaction();
|
||||
$db->statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ');
|
||||
$baseline = $fingerprint($db, $tables);
|
||||
$directory = 'C:/laragon/data/sgf-tempo/backups/compras-receipt-documents-'.gmdate('Ymd-His').'-'.bin2hex(random_bytes(3));
|
||||
if (! mkdir($directory, 0700, true)) {
|
||||
throw new RuntimeException('Backup directory unavailable.');
|
||||
}
|
||||
$dump = $directory.'/before.dump';
|
||||
$snapshot = $db->selectOne('select pg_export_snapshot() as snapshot')->snapshot;
|
||||
$pg('pg_dump', ['--format=custom', '--snapshot='.$snapshot, '--file='.$dump, '--dbname='.$qa['database']], $qa);
|
||||
$db->commit();
|
||||
$admin->statement('CREATE DATABASE "'.$restore.'"');
|
||||
$created = true;
|
||||
$pg('pg_restore', ['--no-owner', '--no-privileges', '--exit-on-error', '--dbname='.$restore, $dump], $owner);
|
||||
config(['database.connections.receipt_restore' => [...$owner, 'url' => null, 'database' => $restore]]);
|
||||
if ($fingerprint(DB::connection('receipt_restore'), $tables) !== $baseline) {
|
||||
throw new LogicException('Restored backup differs.');
|
||||
}
|
||||
$disk = Storage::disk('receipt_documents');
|
||||
$probe = '.installation-'.bin2hex(random_bytes(8));
|
||||
try {
|
||||
$disk->put($probe, 'private-storage-probe');
|
||||
if ($disk->get($probe) !== 'private-storage-probe') {
|
||||
throw new RuntimeException('Storage unavailable.');
|
||||
}
|
||||
} finally {
|
||||
$disk->delete($probe);
|
||||
}
|
||||
$migration = 'database/migrations/2026_11_03_000002_create_receipt_documents.php';
|
||||
$db->transaction(function () use ($db, $tables, $baseline, $fingerprint, $migration) {
|
||||
foreach ($tables as $table) {
|
||||
$db->statement('LOCK TABLE "'.str_replace('"', '""', $table).'" IN SHARE MODE');
|
||||
}
|
||||
if ($fingerprint($db, $tables) !== $baseline) {
|
||||
throw new LogicException('Pilot changed after backup; abort.');
|
||||
}
|
||||
if (Artisan::call('migrate', ['--database' => 'receipt_install', '--path' => $migration, '--force' => true]) !== 0) {
|
||||
throw new RuntimeException('Target migration failed.');
|
||||
}
|
||||
$protected = array_values(array_diff($tables, ['migrations']));
|
||||
if ($fingerprint($db, $protected) !== array_intersect_key($baseline, array_flip($protected))) {
|
||||
throw new LogicException('Business data changed.');
|
||||
}
|
||||
if ($db->table('receipt_documents')->count() !== 0 || $db->table('migrations')->where('migration', '2026_11_03_000001_add_two_factor_columns_to_users')->exists()) {
|
||||
throw new LogicException('Unexpected installation state.');
|
||||
}
|
||||
});
|
||||
$proof = ['checked_at' => gmdate('c'), 'environment' => 'http://127.0.0.1:8005', 'result' => 'passed', 'authorization' => 'User explicitly authorized receipt document block installation', 'migration' => $migration, 'backup' => $dump, 'backup_sha256' => hash_file('sha256', $dump), 'restored_tables_checked' => count($tables), 'protected_tables_checked' => count($tables) - 1, 'business_data_unchanged' => true, 'documents_created' => 0, 'mfa_enabled' => false, 'restore_database_removed' => false];
|
||||
} finally {
|
||||
if ($db->transactionLevel() > 0) {
|
||||
$db->rollBack();
|
||||
}
|
||||
DB::purge('receipt_restore');
|
||||
if ($created) {
|
||||
$admin->statement('DROP DATABASE "'.$restore.'" WITH (FORCE)');
|
||||
}
|
||||
}
|
||||
$proof['restore_database_removed'] = true;
|
||||
file_put_contents($proofPath, json_encode($proof, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES).PHP_EOL);
|
||||
echo 'Receipt documents installed; backup restored and business data preserved.'.PHP_EOL;
|
||||
|
||||
@@ -18,7 +18,7 @@ await new Function('fs','assert','return (async()=>{'+common+`
|
||||
records.push({role,status:result.status,mfaDisabled:true});
|
||||
}
|
||||
await fs.writeFile('docs/proofs/mfa-management-http.json',JSON.stringify({checked_at:new Date().toISOString(),environment:base,result:'passed',records,
|
||||
enrollment_commands:false,password_changes:false,accounts_or_grants_changed:false,pilot_migration_applied:false,sgf_changed:false,visual_review:false,
|
||||
enrollment_commands:false,password_changes:false,accounts_or_grants_changed:false,migration_status_not_inferred_from_http:true,sgf_changed:false,visual_review:false,
|
||||
coverage:'Pilot remains disabled; enabled native management flows tested in disposable databases.'},null,2));
|
||||
console.log('Interface de segurança conferida; adesão MFA do piloto permanece desactivada.');
|
||||
`+'})()')(fs,assert);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import assert from 'node:assert/strict';
|
||||
const source = await fs.readFile('scripts/supplier-evaluation-check.mjs', 'utf8');
|
||||
const common = source.slice(source.indexOf('const base ='), source.indexOf('const author ='));
|
||||
await new Function('fs', 'assert', 'return (async()=>{' + common + `
|
||||
const records=[];
|
||||
for(const role of ['admin','warehouse','procurement','finance']) {
|
||||
const viewer=client(); await viewer.enter(role);
|
||||
assert.equal((await viewer.request('/contexto','POST',{area:'procurement',option:'procurement.internal'})).status,302);
|
||||
const stock=await viewer.request('/stock'); assert.equal(stock.status,200); assert.equal(stock.page.props.geographyEnabled,false);
|
||||
const equipment=await viewer.request('/equipamentos'); assert.equal(equipment.status,200); assert.equal(equipment.page.props.geographyEnabled,false);
|
||||
const catalogue=await viewer.request('/api/divisao-administrativa'); assert.equal(catalogue.status,200); assert.equal(catalogue.page.data.length,11);
|
||||
if(role!=='warehouse') { const suppliers=await viewer.request('/fornecedores'); assert.equal(suppliers.status,200); assert.equal(suppliers.page.props.geographyEnabled,true); }
|
||||
records.push({role,stockStatus:200,equipmentStatus:200,newControlsHidden:true,existingCatalogueAvailable:true});
|
||||
}
|
||||
await fs.writeFile('docs/proofs/operational-geography-http.json',JSON.stringify({checked_at:new Date().toISOString(),environment:base,result:'passed',records,business_commands:false,classification_commands:false,approval_changes:false,mfa_activation:false,visual_review:false},null,2));
|
||||
console.log('Operational controls remain hidden pending schema approval; current geography catalogue remains available.');
|
||||
` + '})()')(fs, assert);
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
require dirname(__DIR__).'/vendor/autoload.php';
|
||||
$root = dirname(__DIR__);
|
||||
$settings = json_decode(file_get_contents('C:/laragon/data/sgf-tempo/secrets/payables-demo-compras.json'), true, 32, JSON_THROW_ON_ERROR);
|
||||
if (($settings['APP_ENV'] ?? '') !== 'payable-testing' || ($settings['DB_HOST'] ?? '') !== '127.0.0.1' || realpath($settings['HOST_ROOT'] ?? '') !== realpath($root)
|
||||
|| ! preg_match('/^compras_receipts_test_([a-f0-9]{12})$/D', $settings['DB_DATABASE'] ?? '', $matches) || $settings['DB_USERNAME'] !== 'payable_qa_'.$matches[1]) {
|
||||
throw new LogicException('Wrong pilot destination.');
|
||||
}
|
||||
$app = require $root.'/bootstrap/app.php';
|
||||
$app->make(Kernel::class)->bootstrap();
|
||||
$base = DB::connection('pgsql')->getConfig();
|
||||
unset($base['name']);
|
||||
config(['database.connections.geography_pilot_read' => [...$base, 'url' => null, 'host' => $settings['DB_HOST'], 'port' => $settings['DB_PORT'],
|
||||
'database' => $settings['DB_DATABASE'], 'username' => $settings['DB_USERNAME'], 'password' => $settings['DB_PASSWORD']]]);
|
||||
$db = DB::connection('geography_pilot_read');
|
||||
$result = $db->transaction(function () use ($db, $settings) {
|
||||
$db->statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY');
|
||||
$identity = $db->selectOne('select current_database() as db, current_user as role');
|
||||
if ($identity->db !== $settings['DB_DATABASE'] || $identity->role !== $settings['DB_USERNAME']) {
|
||||
throw new LogicException('Identity mismatch.');
|
||||
}
|
||||
$schema = Schema::connection('geography_pilot_read');
|
||||
|
||||
return ['checked_at' => gmdate('c'), 'environment' => 'http://127.0.0.1:8005', 'result' => 'passed',
|
||||
'warehouse_column_installed' => $schema->hasColumn('warehouses', 'geography_reference'),
|
||||
'equipment_column_installed' => $schema->hasColumn('equipment', 'geography_reference'),
|
||||
'operational_flag' => filter_var($settings['SGC_OPERATIONAL_GEOGRAPHY_ENABLED'] ?? false, FILTER_VALIDATE_BOOLEAN),
|
||||
'operational_migration_applied' => $db->table('migrations')->where('migration', '2026_11_03_000005_add_operational_geography_references')->exists(),
|
||||
'supplier_column_installed' => $schema->hasColumn('suppliers', 'geography_reference'),
|
||||
'company_column_installed' => $schema->hasColumn('tenants', 'geography_reference'),
|
||||
'migration_applied' => $db->table('migrations')->where('migration', '2026_11_03_000004_add_administrative_geography_references')->exists(),
|
||||
'mfa_accounts_enrolled' => $db->table('users')->where(fn ($q) => $q->whereNotNull('two_factor_secret')->orWhereNotNull('two_factor_recovery_codes')->orWhereNotNull('two_factor_confirmed_at'))->count(),
|
||||
'access_version' => (int) $db->table('organization_access')->where('tenant_id', 1)->value('version'),
|
||||
'business_commands' => false, 'geography_flag' => filter_var($settings['SGC_GEOGRAPHY_ENABLED'] ?? false, FILTER_VALIDATE_BOOLEAN),
|
||||
'classified_suppliers' => $db->table('suppliers')->whereNotNull('geography_reference')->count(),
|
||||
'classified_companies' => $db->table('tenants')->whereNotNull('geography_reference')->count()];
|
||||
});
|
||||
if (! $result['migration_applied'] || ! $result['geography_flag'] || $result['warehouse_column_installed'] || $result['equipment_column_installed'] || $result['operational_migration_applied'] || $result['operational_flag'] || $result['mfa_accounts_enrolled']) {
|
||||
throw new LogicException('Pilot differs from the expected operational geography preparation scope.');
|
||||
}
|
||||
file_put_contents($root.'/docs/proofs/operational-geography-pilot.json', json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES).PHP_EOL);
|
||||
echo 'Geography pilot inspected read-only: operational columns absent; existing company/supplier selection preserved; no business commands.'.PHP_EOL;
|
||||
@@ -0,0 +1,23 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import assert from 'node:assert/strict';
|
||||
const source = await fs.readFile('scripts/supplier-evaluation-check.mjs', 'utf8');
|
||||
const common = source.slice(source.indexOf('const base ='), source.indexOf('const author ='));
|
||||
await new Function('fs', 'assert', 'return (async()=>{' + common + `
|
||||
const records=[];
|
||||
for(const role of ['admin','procurement','finance']) {
|
||||
const viewer=client(); await viewer.enter(role);
|
||||
assert.equal((await viewer.request('/contexto','POST',{area:'procurement',option:'procurement.internal'})).status,302);
|
||||
assert.equal((await viewer.request('/ordens/cancelamentos','GET',undefined,false)).status,503);
|
||||
const orders=await viewer.request('/ordens'); assert.equal(orders.status,200);
|
||||
let checked=0;
|
||||
for(const order of orders.page.props.orders.data.slice(0,1)) {
|
||||
const detail=await viewer.request('/ordens/'+order.id); assert.equal(detail.status,200);
|
||||
assert.equal(detail.page.props.orderCancellation.enabled,false);
|
||||
assert.equal(detail.page.props.orderCancellation.canRequest,false);
|
||||
assert.equal(detail.page.props.orderCancellationAvailable,false); checked++;
|
||||
}
|
||||
records.push({role,disabledStatus:503,ordersChecked:checked});
|
||||
}
|
||||
await fs.writeFile('docs/proofs/issued-order-cancellation-http.json',JSON.stringify({checked_at:new Date().toISOString(),environment:base,result:'passed',records,business_commands:false,policy_commands:false,cancellation_commands:false,visual_review:false},null,2));
|
||||
console.log('Circuito desligado no piloto; ordens existentes consultadas sem comandos de cancelamento.');
|
||||
` + '})()')(fs, assert);
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
require dirname(__DIR__).'/vendor/autoload.php';
|
||||
$root = dirname(__DIR__);
|
||||
$settings = json_decode(file_get_contents('C:/laragon/data/sgf-tempo/secrets/payables-demo-compras.json'), true, 32, JSON_THROW_ON_ERROR);
|
||||
if (($settings['APP_ENV'] ?? '') !== 'payable-testing' || ($settings['DB_HOST'] ?? '') !== '127.0.0.1' || realpath($settings['HOST_ROOT'] ?? '') !== realpath($root)
|
||||
|| ! preg_match('/^compras_receipts_test_([a-f0-9]{12})$/D', $settings['DB_DATABASE'] ?? '', $matches) || $settings['DB_USERNAME'] !== 'payable_qa_'.$matches[1]) {
|
||||
throw new LogicException('Wrong pilot destination.');
|
||||
}
|
||||
$app = require $root.'/bootstrap/app.php';
|
||||
$app->make(Kernel::class)->bootstrap();
|
||||
$base = DB::connection('pgsql')->getConfig();
|
||||
unset($base['name']);
|
||||
config(['database.connections.cancel_pilot_read' => [...$base, 'url' => null, 'host' => $settings['DB_HOST'], 'port' => $settings['DB_PORT'],
|
||||
'database' => $settings['DB_DATABASE'], 'username' => $settings['DB_USERNAME'], 'password' => $settings['DB_PASSWORD']]]);
|
||||
$db = DB::connection('cancel_pilot_read');
|
||||
$result = $db->transaction(function () use ($db, $settings) {
|
||||
$db->statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY');
|
||||
$identity = $db->selectOne('select current_database() as db, current_user as role');
|
||||
if ($identity->db !== $settings['DB_DATABASE'] || $identity->role !== $settings['DB_USERNAME']) {
|
||||
throw new LogicException('Identity mismatch.');
|
||||
}
|
||||
$schema = Schema::connection('cancel_pilot_read');
|
||||
$installed = $schema->hasTable('order_cancellations');
|
||||
|
||||
return ['checked_at' => gmdate('c'), 'environment' => 'http://127.0.0.1:8005', 'result' => 'passed',
|
||||
'schema_installed' => $installed, 'migration_applied' => $db->table('migrations')->where('migration', '2026_11_03_000003_create_order_cancellations')->exists(),
|
||||
'cancellations' => $installed ? $db->table('order_cancellations')->count() : 0,
|
||||
'policies' => $schema->hasTable('order_cancellation_policies') ? $db->table('order_cancellation_policies')->count() : 0,
|
||||
'mfa_accounts_enrolled' => $db->table('users')->where(fn ($q) => $q->whereNotNull('two_factor_secret')->orWhereNotNull('two_factor_recovery_codes')->orWhereNotNull('two_factor_confirmed_at'))->count(),
|
||||
'audit_permission_profiles' => $db->table('organization_profiles')->whereJsonContains('permissions', 'audit_metadata_read')->count(),
|
||||
'access_version' => (int) $db->table('organization_access')->where('tenant_id', 1)->value('version'), 'business_commands' => false];
|
||||
});
|
||||
if ($result['schema_installed'] || $result['migration_applied'] || $result['cancellations'] || $result['policies'] || $result['mfa_accounts_enrolled'] || $result['audit_permission_profiles']) {
|
||||
throw new LogicException('Pilot state differs from the authorized disabled scope.');
|
||||
}
|
||||
file_put_contents($root.'/docs/proofs/issued-order-cancellation-pilot.json', json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES).PHP_EOL);
|
||||
echo 'Pilot inspected read-only: circuit uninstalled and disabled; no policies, cancellations or MFA enrollments.'.PHP_EOL;
|
||||
@@ -0,0 +1,21 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import assert from 'node:assert/strict';
|
||||
const source = await fs.readFile('scripts/supplier-evaluation-check.mjs', 'utf8');
|
||||
const common = source.slice(source.indexOf('const base ='), source.indexOf('const author ='));
|
||||
await new Function('fs', 'assert', 'return (async()=>{' + common + `
|
||||
const viewer=client(); await viewer.enter('admin');
|
||||
assert.equal((await viewer.request('/contexto','POST',{area:'people',option:'people.internal'})).status,302);
|
||||
const index=await viewer.request('/pessoas'); assert.equal(index.status,200);
|
||||
let dossiersChecked=0;
|
||||
for(const process of index.page.props.processes.data.slice(0,2)) {
|
||||
const dossier=await viewer.request('/pessoas/'+process.id+'/dossier.json','GET',undefined,false);
|
||||
assert.equal(dossier.status,200);
|
||||
assert.equal(dossier.page.schema,'sgc.people.process-dossier.v1');
|
||||
assert.equal(dossier.page.effect,'preparation_only');
|
||||
assert.equal(dossier.page.process.tenant_id,undefined);
|
||||
assert.equal(dossier.page.process.creation_hash,undefined);
|
||||
dossiersChecked++;
|
||||
}
|
||||
await fs.writeFile('docs/proofs/people-dossier-http.json',JSON.stringify({checked_at:new Date().toISOString(),environment:base,result:'passed',dossiersChecked,business_commands:false,visual_review:false,coverage:'Existing visible processes only; service and staff journeys exercised in disposable databases.'},null,2));
|
||||
console.log('Consulta de Pessoas conferida; sem decisões ou novos registos.');
|
||||
` + '})()')(fs, assert);
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use App\Services\OrganizationAccess;
|
||||
use App\Services\OrganizationConfiguration;
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
require dirname(__DIR__).'/vendor/autoload.php';
|
||||
$root = dirname(__DIR__);
|
||||
if (! in_array('--prepare-audit-access', $argv, true)) {
|
||||
throw new LogicException('Explicit preparation flag required.');
|
||||
}
|
||||
$s = json_decode(file_get_contents('C:/laragon/data/sgf-tempo/secrets/payables-demo-compras.json'), true, 32, JSON_THROW_ON_ERROR);
|
||||
if (($s['APP_ENV'] ?? '') !== 'payable-testing' || ($s['DB_HOST'] ?? '') !== '127.0.0.1' || realpath($s['HOST_ROOT'] ?? '') !== realpath($root) || ! preg_match('/^compras_receipts_test_([a-f0-9]{12})$/D', $s['DB_DATABASE'] ?? '', $m) || $s['DB_USERNAME'] !== 'payable_qa_'.$m[1]) {
|
||||
throw new LogicException('Wrong pilot destination.');
|
||||
}
|
||||
$app = require $root.'/bootstrap/app.php';
|
||||
$app->make(Kernel::class)->bootstrap();
|
||||
$owner = DB::connection('pgsql')->getConfig();
|
||||
unset($owner['name']);
|
||||
if ($owner['host'] !== '127.0.0.1') {
|
||||
throw new LogicException('Local server required.');
|
||||
}
|
||||
$qa = [...$owner, 'url' => null, 'database' => $s['DB_DATABASE'], 'username' => $s['DB_USERNAME'], 'password' => $s['DB_PASSWORD']];
|
||||
config(['database.connections.receipt_install' => $qa, 'database.connections.receipt_owner' => [...$owner, 'url' => null, 'database' => 'postgres']]);
|
||||
DB::setDefaultConnection('receipt_install');
|
||||
$db = DB::connection();
|
||||
$admin = DB::connection('receipt_owner');
|
||||
$identity = $db->selectOne('select current_database() as db, current_user as role');
|
||||
if ($identity->db !== $s['DB_DATABASE'] || $identity->role !== $s['DB_USERNAME']) {
|
||||
throw new LogicException('Identity mismatch.');
|
||||
}
|
||||
$fingerprint = function ($connection, $tables) {
|
||||
$out = [];
|
||||
foreach ($tables as $table) {
|
||||
$rows = $connection->select('select row_to_json(t)::text as row from "'.str_replace('"', '""', $table).'" t order by row_to_json(t)::text');
|
||||
$out[$table] = hash('sha256', json_encode(array_column($rows, 'row'), JSON_THROW_ON_ERROR));
|
||||
}
|
||||
|
||||
return $out;
|
||||
};
|
||||
$pg = function ($binary, $args, $connection) {
|
||||
$p = new Process(['C:/Program Files/PostgreSQL/17/bin/'.$binary.'.exe', ...$args], null, ['PGHOST' => $connection['host'], 'PGPORT' => (string) $connection['port'], 'PGUSER' => $connection['username'], 'PGPASSWORD' => $connection['password']], timeout: 120);
|
||||
$p->run();
|
||||
if (! $p->isSuccessful()) {
|
||||
throw new RuntimeException('Backup/restore failed.');
|
||||
}
|
||||
};
|
||||
$tables = array_column($db->select("select tablename from pg_tables where schemaname='public' order by tablename"), 'tablename');
|
||||
$restore = 'compras_pilot_restore_'.bin2hex(random_bytes(6));
|
||||
$created = false;
|
||||
try {
|
||||
$db->beginTransaction();
|
||||
$db->statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ');
|
||||
$baseline = $fingerprint($db, $tables);
|
||||
$directory = 'C:/laragon/data/sgf-tempo/backups/compras-audit-access-preparation-'.gmdate('Ymd-His').'-'.bin2hex(random_bytes(3));
|
||||
if (! mkdir($directory, 0700, true)) {
|
||||
throw new RuntimeException('Backup unavailable.');
|
||||
}
|
||||
$dump = $directory.'/before.dump';
|
||||
$snapshot = $db->selectOne('select pg_export_snapshot() as snapshot')->snapshot;
|
||||
$pg('pg_dump', ['--format=custom', '--snapshot='.$snapshot, '--file='.$dump, '--dbname='.$qa['database']], $qa);
|
||||
$db->commit();
|
||||
$admin->statement('CREATE DATABASE "'.$restore.'"');
|
||||
$created = true;
|
||||
$pg('pg_restore', ['--no-owner', '--no-privileges', '--exit-on-error', '--dbname='.$restore, $dump], $owner);
|
||||
config(['database.connections.receipt_restore' => [...$owner, 'url' => null, 'database' => $restore], 'work_context.composition' => 'sgc']);
|
||||
$restored = DB::connection('receipt_restore');
|
||||
if ($fingerprint($restored, $tables) !== $baseline) {
|
||||
throw new LogicException('Restored backup differs.');
|
||||
}
|
||||
DB::setDefaultConnection('receipt_restore');
|
||||
$app->detectEnvironment(fn () => 'payable-testing');
|
||||
$request = Request::create('http://127.0.0.1:8005/administracao/parametrizacao', 'GET', server: ['REMOTE_ADDR' => '127.0.0.1']);
|
||||
$request->setLaravelSession($app['session']->driver('array'));
|
||||
$app->instance('request', $request);
|
||||
$actor = User::query()->findOrFail(6);
|
||||
auth()->guard('web')->setUser($actor);
|
||||
$request->setUserResolver(fn () => $actor);
|
||||
$request->session()->put('work_context', ['key' => 'procurement.internal', 'user_id' => $actor->id, 'tenant_id' => $actor->tenant_id]);
|
||||
$tenant = $actor->tenant_id;
|
||||
$version = (int) $restored->table('organization_access')->where('tenant_id', $tenant)->value('version');
|
||||
$beforePermissions = [];
|
||||
$profiles = [];
|
||||
foreach ([6, 8] as $id) {
|
||||
$user = User::query()->where('tenant_id', $tenant)->findOrFail($id);
|
||||
$beforePermissions[$id] = app(OrganizationAccess::class)->permissions($user, 'procurement.internal');
|
||||
if (! in_array('admin', $beforePermissions[$id], true) || in_array('audit_metadata_read', $beforePermissions[$id], true)) {
|
||||
throw new LogicException('Proposed administrators changed.');
|
||||
}
|
||||
$members = $restored->table('organization_memberships')->where('tenant_id', $tenant)->where('user_id', $id)->get();
|
||||
foreach ($members as $m) {
|
||||
if ($m->maintenance || $m->expires_at !== null) {
|
||||
throw new LogicException('Membership conditions require a separate proposal.');
|
||||
}
|
||||
}$profiles[$id] = $members->pluck('profile_id')->all();
|
||||
}
|
||||
$flow = app(OrganizationConfiguration::class);
|
||||
$flow->execute($actor, 'profile', ['version' => $version, 'name' => 'Auditoria de metadados — piloto sintético', 'permissions' => ['audit_metadata_read'], 'work_contexts' => ['procurement.internal'], 'reason' => 'Ensaio descartável da proposta de consulta de metadados.']);
|
||||
$profile = (int) $restored->table('organization_profiles')->where('tenant_id', $tenant)->max('id');
|
||||
foreach ([6, 8] as $index => $id) {
|
||||
$flow->execute($actor, 'assign', ['version' => $version + 1 + $index, 'user_id' => $id, 'profile_ids' => [...$profiles[$id], $profile], 'maintenance' => false, 'expires_at' => null, 'reason' => 'Ensaio descartável: acrescentar só consulta de metadados, preservando competências.']);
|
||||
$user = User::query()->findOrFail($id);
|
||||
$after = app(OrganizationAccess::class)->permissions($user, 'procurement.internal');
|
||||
$expected = [...$beforePermissions[$id], 'audit_metadata_read'];
|
||||
sort($after);
|
||||
sort($expected);
|
||||
if ($after !== $expected) {
|
||||
throw new LogicException('Unexpected permission change.');
|
||||
}
|
||||
}
|
||||
$protected = array_values(array_diff($tables, ['organization_profiles', 'organization_memberships', 'organization_access', 'audit_logs']));
|
||||
if ($fingerprint($restored, $protected) !== array_intersect_key($baseline, array_flip($protected))) {
|
||||
throw new LogicException('Unexpected business changes.');
|
||||
}
|
||||
$result = ['checked_at' => gmdate('c'), 'result' => 'passed', 'environment' => 'http://127.0.0.1:8005', 'tenant_id' => $tenant, 'expected_access_version' => $version, 'proposed_access_version' => $version + 3, 'actors' => [6, 8], 'new_profile' => ['name' => 'Auditoria de metadados — piloto sintético', 'permissions' => ['audit_metadata_read'], 'work_contexts' => ['procurement.internal']], 'existing_profile_ids' => $profiles, 'existing_permissions_preserved' => true, 'backup' => $dump, 'backup_sha256' => hash_file('sha256', $dump), 'restored_tables_checked' => count($tables), 'protected_tables_checked' => count($protected), 'pilot_grants_changed' => false, 'pilot_mfa_enabled' => false, 'restore_database_removed' => false];
|
||||
} finally {
|
||||
if ($db->transactionLevel() > 0) {
|
||||
$db->rollBack();
|
||||
}DB::purge('receipt_restore');
|
||||
if ($created) {
|
||||
$admin->statement('DROP DATABASE "'.$restore.'" WITH (FORCE)');
|
||||
}
|
||||
}
|
||||
$result['restore_database_removed'] = true;
|
||||
file_put_contents($root.'/docs/proofs/audit-access-preparation.json',json_encode($result,JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES).PHP_EOL);
|
||||
echo 'Audit access proposal rehearsed only in restored disposable backup; pilot grants unchanged.'.PHP_EOL;
|
||||
@@ -0,0 +1,93 @@
|
||||
"""Read-only XLSX extraction. No spreadsheet or pilot database is changed."""
|
||||
import argparse
|
||||
import collections
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import xml.etree.ElementTree as ET
|
||||
import zipfile
|
||||
|
||||
|
||||
def prepare(source, destination):
|
||||
content = Path(source).read_bytes()
|
||||
digest = hashlib.sha256(content).hexdigest()
|
||||
ns = {"m": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"}
|
||||
with zipfile.ZipFile(source) as archive:
|
||||
strings = ["".join(t.text or "" for t in item.iter("{" + ns["m"] + "}t"))
|
||||
for item in ET.fromstring(archive.read("xl/sharedStrings.xml"))]
|
||||
xml = ET.fromstring(archive.read("xl/worksheets/sheet1.xml"))
|
||||
parsed = []
|
||||
for row in xml.findall("m:sheetData/m:row", ns):
|
||||
cells = {}
|
||||
for cell in row:
|
||||
value = cell.find("m:v", ns)
|
||||
value = value.text if value is not None else "".join(cell.itertext())
|
||||
cells["".join(filter(str.isalpha, cell.get("r")))] = strings[int(value)] if cell.get("t") == "s" else value
|
||||
if any(cells.values()):
|
||||
parsed.append((int(row.get("r")), cells))
|
||||
expected = ["ID", "Provincia", "Distrito", "Posto_Administrativo", "Localidade", "Bairro_Urbano", "Aldeia_Quarteirao", "Designacao"]
|
||||
if [parsed[0][1].get(c, "") for c in "ABCDEFGH"] != expected:
|
||||
raise ValueError("Unexpected source columns; import stopped")
|
||||
records = []
|
||||
by_path = collections.defaultdict(list)
|
||||
ids = set()
|
||||
for row, cells in parsed[1:]:
|
||||
raw = {key: cells.get(col, "") for col, key in zip("ABCDEFGH", expected)}
|
||||
identifier = raw["ID"]
|
||||
if not identifier.isdecimal() or identifier in ids:
|
||||
raise ValueError("Missing or duplicate source ID; import stopped")
|
||||
ids.add(identifier)
|
||||
codes = [cells.get(c, "").strip() for c in "BCDEFG"]
|
||||
positions = [i for i, code in enumerate(codes) if code]
|
||||
level = max(positions) + 1 if positions else 0
|
||||
issues = []
|
||||
if not raw["Designacao"].strip():
|
||||
issues.append("blank_designation")
|
||||
if not level or positions != list(range(level)):
|
||||
issues.append("incomplete_code_path")
|
||||
record = {"id": identifier, "source_row": row, "raw": raw, "codes": codes,
|
||||
"level": level, "name": raw["Designacao"], "parent_id": None, "issues": issues}
|
||||
records.append(record)
|
||||
by_path[tuple(codes)].append(record)
|
||||
for record in records:
|
||||
if len(by_path[tuple(record["codes"])]) > 1:
|
||||
record["issues"].append("duplicate_code_path")
|
||||
if record["level"] > 1:
|
||||
parent = record["codes"].copy()
|
||||
parent[record["level"] - 1] = ""
|
||||
candidates = by_path.get(tuple(parent), [])
|
||||
if len(candidates) == 1:
|
||||
record["parent_id"] = candidates[0]["id"]
|
||||
else:
|
||||
record["issues"].append("missing_parent" if not candidates else "ambiguous_parent")
|
||||
by_id = {r["id"]: r for r in records}
|
||||
for record in sorted(records, key=lambda r: r["level"]):
|
||||
parent = by_id.get(record["parent_id"])
|
||||
if parent and not parent["selectable"]:
|
||||
record["issues"].append("ancestor_requires_review")
|
||||
record["selectable"] = not record["issues"]
|
||||
manifest = {"schema_version": 1, "country": "MZ", "source_file": Path(source).name,
|
||||
"source_sha256": digest, "source_sheet": "Sheet1", "edition": None,
|
||||
"authority": "INE, conforme indicação do proprietário; edição não verificada",
|
||||
"columns": expected, "records": len(records),
|
||||
"selectable": sum(r["selectable"] for r in records),
|
||||
"levels": dict(collections.Counter(r["level"] for r in records)),
|
||||
"issues": dict(collections.Counter(issue for r in records for issue in r["issues"])),
|
||||
"duplicate_code_groups": sum(len(v) > 1 for v in by_path.values())}
|
||||
result = {"manifest": manifest, "records": records}
|
||||
destination = Path(destination)
|
||||
if destination.exists():
|
||||
previous = json.loads(destination.read_text(encoding="utf-8"))
|
||||
if previous["manifest"]["source_sha256"] != digest:
|
||||
raise ValueError("Preserve previous catalogue: use a new destination for a new source revision")
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
destination.write_text(json.dumps(result, ensure_ascii=False, separators=(",", ":")) + "\n", encoding="utf-8")
|
||||
print(json.dumps(manifest, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("source")
|
||||
parser.add_argument("destination")
|
||||
args = parser.parse_args()
|
||||
prepare(args.source, args.destination)
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
require dirname(__DIR__).'/vendor/autoload.php';
|
||||
$root = dirname(__DIR__);
|
||||
if (! in_array('--prepare-issued-order-schema', $argv, true)) {
|
||||
throw new LogicException('Explicit preparation flag required.');
|
||||
}
|
||||
$s = json_decode(file_get_contents('C:/laragon/data/sgf-tempo/secrets/payables-demo-compras.json'), true, 32, JSON_THROW_ON_ERROR);
|
||||
if (($s['APP_ENV'] ?? '') !== 'payable-testing' || ($s['DB_HOST'] ?? '') !== '127.0.0.1' || realpath($s['HOST_ROOT'] ?? '') !== realpath($root) || ! preg_match('/^compras_receipts_test_([a-f0-9]{12})$/D', $s['DB_DATABASE'] ?? '', $m) || $s['DB_USERNAME'] !== 'payable_qa_'.$m[1]) {
|
||||
throw new LogicException('Wrong pilot destination.');
|
||||
}
|
||||
$app = require $root.'/bootstrap/app.php';
|
||||
$app->make(Kernel::class)->bootstrap();
|
||||
$owner = DB::connection('pgsql')->getConfig();
|
||||
unset($owner['name']);
|
||||
if ($owner['host'] !== '127.0.0.1') {
|
||||
throw new LogicException('Local server required.');
|
||||
}
|
||||
$qa = [...$owner, 'url' => null, 'database' => $s['DB_DATABASE'], 'username' => $s['DB_USERNAME'], 'password' => $s['DB_PASSWORD']];
|
||||
config(['database.connections.receipt_install' => $qa, 'database.connections.receipt_owner' => [...$owner, 'url' => null, 'database' => 'postgres']]);
|
||||
DB::setDefaultConnection('receipt_install');
|
||||
$db = DB::connection();
|
||||
$admin = DB::connection('receipt_owner');
|
||||
$identity = $db->selectOne('select current_database() as db, current_user as role');
|
||||
if ($identity->db !== $s['DB_DATABASE'] || $identity->role !== $s['DB_USERNAME']) {
|
||||
throw new LogicException('Identity mismatch.');
|
||||
}
|
||||
$fingerprint = function ($connection, $tables) {
|
||||
$out = [];
|
||||
foreach ($tables as $table) {
|
||||
$rows = $connection->select('select row_to_json(t)::text as row from "'.str_replace('"', '""', $table).'" t order by row_to_json(t)::text');
|
||||
$out[$table] = hash('sha256', json_encode(array_column($rows, 'row'), JSON_THROW_ON_ERROR));
|
||||
}
|
||||
|
||||
return $out;
|
||||
};
|
||||
$pg = function ($binary, $args, $connection) {
|
||||
$p = new Process(['C:/Program Files/PostgreSQL/17/bin/'.$binary.'.exe', ...$args], null, ['PGHOST' => $connection['host'], 'PGPORT' => (string) $connection['port'], 'PGUSER' => $connection['username'], 'PGPASSWORD' => $connection['password']], timeout: 120);
|
||||
$p->run();
|
||||
if (! $p->isSuccessful()) {
|
||||
throw new RuntimeException('Backup/restore failed.');
|
||||
}
|
||||
};
|
||||
$tables = array_column($db->select("select tablename from pg_tables where schemaname='public' order by tablename"), 'tablename');
|
||||
$migration = 'database/migrations/2026_11_03_000003_create_order_cancellations.php';
|
||||
$newTables = ['order_cancellation_policies', 'order_cancellation_policy_reviews', 'order_cancellations'];
|
||||
if (config('order_cancellation.enabled') || array_intersect($tables, $newTables)) {
|
||||
throw new LogicException('Circuit must remain disabled and uninstalled in the pilot.');
|
||||
}
|
||||
$restore = 'compras_pilot_restore_'.bin2hex(random_bytes(6));
|
||||
$created = false;
|
||||
try {
|
||||
$db->beginTransaction();
|
||||
$db->statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ');
|
||||
$baseline = $fingerprint($db, $tables);
|
||||
$directory = 'C:/laragon/data/sgf-tempo/backups/compras-issued-order-schema-'.gmdate('Ymd-His').'-'.bin2hex(random_bytes(3));
|
||||
if (! mkdir($directory, 0700, true)) {
|
||||
throw new RuntimeException('Backup unavailable.');
|
||||
}
|
||||
$dump = $directory.'/before.dump';
|
||||
$snapshot = $db->selectOne('select pg_export_snapshot() as snapshot')->snapshot;
|
||||
$pg('pg_dump', ['--format=custom', '--snapshot='.$snapshot, '--file='.$dump, '--dbname='.$qa['database']], $qa);
|
||||
$db->commit();
|
||||
$admin->statement('CREATE DATABASE "'.$restore.'"');
|
||||
$created = true;
|
||||
$pg('pg_restore', ['--no-owner', '--no-privileges', '--exit-on-error', '--dbname='.$restore, $dump], $owner);
|
||||
config(['database.connections.receipt_restore' => [...$owner, 'url' => null, 'database' => $restore]]);
|
||||
$restored = DB::connection('receipt_restore');
|
||||
if ($fingerprint($restored, $tables) !== $baseline) {
|
||||
throw new LogicException('Restored backup differs.');
|
||||
}
|
||||
$migrationCount = $restored->table('migrations')->count();
|
||||
if (Artisan::call('migrate', ['--database' => 'receipt_restore', '--path' => $migration, '--force' => true]) !== 0) {
|
||||
throw new RuntimeException('Disposable migration failed.');
|
||||
}
|
||||
$protected = array_values(array_diff($tables, ['migrations']));
|
||||
if ($fingerprint($restored, $protected) !== array_intersect_key($baseline, array_flip($protected)) || $restored->table('migrations')->count() !== $migrationCount + 1) {
|
||||
throw new LogicException('Unexpected existing data changes.');
|
||||
}
|
||||
foreach ($newTables as $table) {
|
||||
if ($restored->table($table)->count() !== 0) {
|
||||
throw new LogicException('New tables must remain empty.');
|
||||
}
|
||||
}
|
||||
$result = ['checked_at' => gmdate('c'), 'result' => 'passed', 'environment' => 'http://127.0.0.1:8005', 'scope' => 'empty_schema_only_no_activation', 'migration' => $migration, 'migration_sha256' => hash_file('sha256', $root.'/'.$migration),
|
||||
'new_empty_tables' => $newTables, 'backup' => $dump, 'backup_sha256' => hash_file('sha256', $dump), 'restored_tables_checked' => count($tables), 'protected_tables_checked' => count($protected),
|
||||
'pilot_migration_applied' => false, 'pilot_configuration_changed' => false, 'policy_publication' => false, 'cancellation_commands' => false, 'mfa_enabled' => false, 'restore_database_removed' => false];
|
||||
} finally {
|
||||
if ($db->transactionLevel() > 0) {
|
||||
$db->rollBack();
|
||||
}DB::purge('receipt_restore');
|
||||
if ($created) {
|
||||
$admin->statement('DROP DATABASE "'.$restore.'" WITH (FORCE)');
|
||||
}
|
||||
}
|
||||
$result['restore_database_removed'] = true;
|
||||
file_put_contents($root.'/docs/proofs/issued-order-schema-preparation.json', json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES).PHP_EOL);
|
||||
echo 'Empty cancellation schema rehearsed only in restored disposable backup; pilot unchanged.'.PHP_EOL;
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
require dirname(__DIR__).'/vendor/autoload.php';
|
||||
$root = dirname(__DIR__);
|
||||
if (! in_array('--prepare', $argv, true)) {
|
||||
throw new LogicException('Explicit preparation flag required.');
|
||||
}
|
||||
$s = json_decode(file_get_contents('C:/laragon/data/sgf-tempo/secrets/payables-demo-compras.json'), true, 32, JSON_THROW_ON_ERROR);
|
||||
if (($s['APP_ENV'] ?? '') !== 'payable-testing' || ($s['DB_HOST'] ?? '') !== '127.0.0.1' || realpath($s['HOST_ROOT'] ?? '') !== realpath($root) || ! preg_match('/^compras_receipts_test_([a-f0-9]{12})$/D', $s['DB_DATABASE'] ?? '', $m) || $s['DB_USERNAME'] !== 'payable_qa_'.$m[1]) {
|
||||
throw new LogicException('Wrong pilot destination.');
|
||||
}
|
||||
$app = require $root.'/bootstrap/app.php';
|
||||
$app->make(Kernel::class)->bootstrap();
|
||||
$owner = DB::connection('pgsql')->getConfig();
|
||||
unset($owner['name']);
|
||||
if ($owner['host'] !== '127.0.0.1') {
|
||||
throw new LogicException('Local server required.');
|
||||
}
|
||||
$qa = [...$owner, 'url' => null, 'database' => $s['DB_DATABASE'], 'username' => $s['DB_USERNAME'], 'password' => $s['DB_PASSWORD']];
|
||||
config(['database.connections.receipt_install' => $qa, 'database.connections.receipt_owner' => [...$owner, 'url' => null, 'database' => 'postgres']]);
|
||||
DB::setDefaultConnection('receipt_install');
|
||||
$db = DB::connection();
|
||||
$admin = DB::connection('receipt_owner');
|
||||
$identity = $db->selectOne('select current_database() as db, current_user as role');
|
||||
if ($identity->db !== $s['DB_DATABASE'] || $identity->role !== $s['DB_USERNAME']) {
|
||||
throw new LogicException('Identity mismatch.');
|
||||
}
|
||||
$fingerprint = function ($connection, $tables) {
|
||||
$out = [];
|
||||
foreach ($tables as $table) {
|
||||
$rows = $connection->select('select row_to_json(t)::text as row from "'.str_replace('"', '""', $table).'" t order by row_to_json(t)::text');
|
||||
$out[$table] = hash('sha256', json_encode(array_column($rows, 'row'), JSON_THROW_ON_ERROR));
|
||||
}
|
||||
|
||||
return $out;
|
||||
};
|
||||
$pg = function ($binary, $args, $connection) {
|
||||
$p = new Process(['C:/Program Files/PostgreSQL/17/bin/'.$binary.'.exe', ...$args], null, ['PGHOST' => $connection['host'], 'PGPORT' => (string) $connection['port'], 'PGUSER' => $connection['username'], 'PGPASSWORD' => $connection['password']], timeout: 120);
|
||||
$p->run();
|
||||
if (! $p->isSuccessful()) {
|
||||
throw new RuntimeException('Backup/restore failed.');
|
||||
}
|
||||
};
|
||||
$tables = array_column($db->select("select tablename from pg_tables where schemaname='public' order by tablename"), 'tablename');
|
||||
$migration = 'database/migrations/2026_11_03_000001_add_two_factor_columns_to_users.php';
|
||||
foreach (['two_factor_secret', 'two_factor_recovery_codes', 'two_factor_confirmed_at'] as $column) {
|
||||
if (Schema::connection('receipt_install')->hasColumn('users', $column)) {
|
||||
throw new LogicException('MFA schema already exists; inspect before repeating.');
|
||||
}
|
||||
}
|
||||
$restore = 'compras_pilot_restore_'.bin2hex(random_bytes(6));
|
||||
$created = false;
|
||||
try {
|
||||
$db->beginTransaction();
|
||||
$db->statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ');
|
||||
$baseline = $fingerprint($db, $tables);
|
||||
$directory = 'C:/laragon/data/sgf-tempo/backups/compras-mfa-preparation-'.gmdate('Ymd-His').'-'.bin2hex(random_bytes(3));
|
||||
if (! mkdir($directory, 0700, true)) {
|
||||
throw new RuntimeException('Backup unavailable.');
|
||||
}
|
||||
$dump = $directory.'/before.dump';
|
||||
$snapshot = $db->selectOne('select pg_export_snapshot() as snapshot')->snapshot;
|
||||
$pg('pg_dump', ['--format=custom', '--snapshot='.$snapshot, '--file='.$dump, '--dbname='.$qa['database']], $qa);
|
||||
$db->commit();
|
||||
$admin->statement('CREATE DATABASE "'.$restore.'"');
|
||||
$created = true;
|
||||
$pg('pg_restore', ['--no-owner', '--no-privileges', '--exit-on-error', '--dbname='.$restore, $dump], $owner);
|
||||
config(['database.connections.receipt_restore' => [...$owner, 'url' => null, 'database' => $restore]]);
|
||||
$restored = DB::connection('receipt_restore');
|
||||
if ($fingerprint($restored, $tables) !== $baseline) {
|
||||
throw new LogicException('Restored backup differs.');
|
||||
}
|
||||
$columns = Schema::connection('receipt_restore')->getColumnListing('users');
|
||||
$userRows = function ($connection) use ($columns) {
|
||||
$selected = implode(',', array_map(fn ($c) => '"'.str_replace('"', '""', $c).'"', $columns));
|
||||
|
||||
return $connection->select('select row_to_json(t)::text as row from (select '.$selected.' from users) t order by row_to_json(t)::text');
|
||||
};
|
||||
$beforeUsers = $userRows($restored);
|
||||
$migrationCount = $restored->table('migrations')->count();
|
||||
if (Artisan::call('migrate', ['--database' => 'receipt_restore', '--path' => $migration, '--force' => true]) !== 0) {
|
||||
throw new RuntimeException('Disposable migration failed.');
|
||||
}
|
||||
$protected = array_values(array_diff($tables, ['users', 'migrations']));
|
||||
if ($fingerprint($restored, $protected) !== array_intersect_key($baseline, array_flip($protected)) || $userRows($restored) != $beforeUsers || $restored->table('migrations')->count() !== $migrationCount + 1) {
|
||||
throw new LogicException('Unexpected migration changes.');
|
||||
}
|
||||
foreach (['two_factor_secret', 'two_factor_recovery_codes', 'two_factor_confirmed_at'] as $column) {
|
||||
if ($restored->table('users')->whereNotNull($column)->exists()) {
|
||||
throw new LogicException('Unexpected enrollment data.');
|
||||
}
|
||||
}
|
||||
$result = ['checked_at' => gmdate('c'), 'result' => 'passed', 'environment' => 'http://127.0.0.1:8005', 'scope' => 'prepare_schema_only_not_activation', 'migration' => $migration, 'migration_sha256' => hash_file('sha256', $root.'/'.$migration), 'backup' => $dump, 'backup_sha256' => hash_file('sha256', $dump), 'restored_tables_checked' => count($tables), 'protected_tables_checked' => count($protected), 'existing_user_columns_preserved' => true, 'existing_users' => $restored->table('users')->count(), 'enrollment_created' => false, 'pilot_migration_applied' => false, 'pilot_configuration_changed' => false, 'passwords_or_grants_changed' => false, 'restore_database_removed' => false];
|
||||
} finally {
|
||||
if ($db->transactionLevel() > 0) {
|
||||
$db->rollBack();
|
||||
}
|
||||
DB::purge('receipt_restore');
|
||||
if ($created) {
|
||||
$admin->statement('DROP DATABASE "'.$restore.'" WITH (FORCE)');
|
||||
}
|
||||
}
|
||||
$result['restore_database_removed'] = true;
|
||||
file_put_contents($root.'/docs/proofs/mfa-pilot-schema-preparation.json', json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES).PHP_EOL);
|
||||
echo 'MFA schema rehearsed only in restored disposable backup; pilot unchanged.'.PHP_EOL;
|
||||
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
require dirname(__DIR__).'/vendor/autoload.php';
|
||||
$root = dirname(__DIR__);
|
||||
if (! in_array('--prepare-operational-geography', $argv, true)) {
|
||||
throw new LogicException('Explicit read-only preparation flag required.');
|
||||
}
|
||||
$s = json_decode(file_get_contents('C:/laragon/data/sgf-tempo/secrets/payables-demo-compras.json'), true, 32, JSON_THROW_ON_ERROR);
|
||||
if (($s['APP_ENV'] ?? '') !== 'payable-testing' || ($s['DB_HOST'] ?? '') !== '127.0.0.1' || realpath($s['HOST_ROOT'] ?? '') !== realpath($root) || ! preg_match('/^compras_receipts_test_([a-f0-9]{12})$/D', $s['DB_DATABASE'] ?? '', $m) || $s['DB_USERNAME'] !== 'payable_qa_'.$m[1]) {
|
||||
throw new LogicException('Wrong pilot destination.');
|
||||
}
|
||||
$app = require $root.'/bootstrap/app.php';
|
||||
$app->make(Kernel::class)->bootstrap();
|
||||
$owner = DB::connection('pgsql')->getConfig();
|
||||
unset($owner['name']);
|
||||
if ($owner['host'] !== '127.0.0.1') {
|
||||
throw new LogicException('Local server required.');
|
||||
}
|
||||
$qa = [...$owner, 'url' => null, 'database' => $s['DB_DATABASE'], 'username' => $s['DB_USERNAME'], 'password' => $s['DB_PASSWORD']];
|
||||
config(['database.connections.receipt_install' => $qa, 'database.connections.receipt_owner' => [...$owner, 'url' => null, 'database' => 'postgres']]);
|
||||
DB::setDefaultConnection('receipt_install');
|
||||
$db = DB::connection();
|
||||
$admin = DB::connection('receipt_owner');
|
||||
$identity = $db->selectOne('select current_database() as db, current_user as role');
|
||||
if ($identity->db !== $s['DB_DATABASE'] || $identity->role !== $s['DB_USERNAME']) {
|
||||
throw new LogicException('Identity mismatch.');
|
||||
}
|
||||
$fingerprint = function ($connection, $tables) {
|
||||
$out = [];
|
||||
foreach ($tables as $table) {
|
||||
$rows = $connection->select('select row_to_json(t)::text as row from "'.str_replace('"', '""', $table).'" t order by row_to_json(t)::text');
|
||||
$out[$table] = hash('sha256', json_encode(array_column($rows, 'row'), JSON_THROW_ON_ERROR));
|
||||
}
|
||||
|
||||
return $out;
|
||||
};
|
||||
$pg = function ($binary, $args, $connection) {
|
||||
$p = new Process(['C:/Program Files/PostgreSQL/17/bin/'.$binary.'.exe', ...$args], null, ['PGHOST' => $connection['host'], 'PGPORT' => (string) $connection['port'], 'PGUSER' => $connection['username'], 'PGPASSWORD' => $connection['password']], timeout: 120);
|
||||
$p->run();
|
||||
if (! $p->isSuccessful()) {
|
||||
throw new RuntimeException('Backup/restore failed.');
|
||||
}
|
||||
};
|
||||
|
||||
$tables = array_column($db->select("select tablename from pg_tables where schemaname='public' order by tablename"), 'tablename');
|
||||
$migration = 'database/migrations/2026_11_03_000005_add_operational_geography_references.php';
|
||||
if (! filter_var($s['SGC_GEOGRAPHY_ENABLED'] ?? false, FILTER_VALIDATE_BOOLEAN) || filter_var($s['SGC_TWO_FACTOR_ENABLED'] ?? false, FILTER_VALIDATE_BOOLEAN)) {
|
||||
throw new LogicException('Geography base must remain enabled and MFA disabled.');
|
||||
}
|
||||
foreach (['warehouses', 'equipment'] as $table) {
|
||||
if (Schema::connection('receipt_install')->hasColumn($table, 'geography_reference')) {
|
||||
throw new LogicException('Operational geography already present; inspect instead of preparing again.');
|
||||
}
|
||||
}
|
||||
$oldColumns = [];
|
||||
foreach (['warehouses', 'equipment'] as $table) {
|
||||
$oldColumns[$table] = Schema::connection('receipt_install')->getColumnListing($table);
|
||||
}
|
||||
$projected = function ($connection) use ($oldColumns) {
|
||||
$out = [];
|
||||
foreach ($oldColumns as $table => $columns) {
|
||||
$selected = implode(',', array_map(fn ($c) => '"'.str_replace('"', '""', $c).'"', $columns));
|
||||
$rows = $connection->select('select row_to_json(t)::text as row from (select '.$selected.' from "'.$table.'") t order by row_to_json(t)::text');
|
||||
$out[$table] = hash('sha256', json_encode(array_column($rows, 'row'), JSON_THROW_ON_ERROR));
|
||||
}
|
||||
|
||||
return $out;
|
||||
};
|
||||
$checkEmpty = function ($connection, $name) use ($tables, $oldColumns) {
|
||||
foreach ($oldColumns as $table => $columns) {
|
||||
if (Schema::connection($name)->getColumnListing($table) !== [...$columns, 'geography_reference']
|
||||
|| $connection->table($table)->whereNotNull('geography_reference')->exists()) {
|
||||
throw new LogicException('Unexpected geography structure or nonempty references.');
|
||||
}
|
||||
}
|
||||
if (array_column($connection->select("select tablename from pg_tables where schemaname='public' order by tablename"), 'tablename') !== $tables) {
|
||||
throw new LogicException('Unexpected table created.');
|
||||
}
|
||||
};
|
||||
$restore = 'compras_pilot_restore_'.bin2hex(random_bytes(6));
|
||||
$created = false;
|
||||
try {
|
||||
$db->beginTransaction();
|
||||
$db->statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY');
|
||||
$baseline = $fingerprint($db, $tables);
|
||||
$beforeRows = $projected($db);
|
||||
$migrationCount = $db->table('migrations')->count();
|
||||
$directory = 'C:/laragon/data/sgf-tempo/backups/compras-operational-geography-preparation-'.gmdate('Ymd-His').'-'.bin2hex(random_bytes(3));
|
||||
if (! mkdir($directory, 0700, true)) {
|
||||
throw new RuntimeException('Backup unavailable.');
|
||||
}
|
||||
$dump = $directory.'/before.dump';
|
||||
$snapshot = $db->selectOne('select pg_export_snapshot() as snapshot')->snapshot;
|
||||
$pg('pg_dump', ['--format=custom', '--snapshot='.$snapshot, '--file='.$dump, '--dbname='.$qa['database']], $qa);
|
||||
$db->commit();
|
||||
$admin->statement('CREATE DATABASE "'.$restore.'"');
|
||||
$created = true;
|
||||
$pg('pg_restore', ['--no-owner', '--no-privileges', '--exit-on-error', '--dbname='.$restore, $dump], $owner);
|
||||
config(['database.connections.receipt_restore' => [...$owner, 'url' => null, 'database' => $restore]]);
|
||||
$restored = DB::connection('receipt_restore');
|
||||
if ($fingerprint($restored, $tables) !== $baseline) {
|
||||
throw new LogicException('Restored backup differs.');
|
||||
}
|
||||
if (Artisan::call('migrate', ['--database' => 'receipt_restore', '--path' => $migration, '--force' => true]) !== 0) {
|
||||
throw new RuntimeException('Disposable migration failed.');
|
||||
}
|
||||
$protected = array_values(array_diff($tables, ['warehouses', 'equipment', 'migrations']));
|
||||
$checkEmpty($restored, 'receipt_restore');
|
||||
if ($fingerprint($restored, $protected) !== array_intersect_key($baseline, array_flip($protected)) || $projected($restored) !== $beforeRows || $restored->table('migrations')->count() !== $migrationCount + 1) {
|
||||
throw new LogicException('Unexpected dry-run changes.');
|
||||
}
|
||||
|
||||
$result = ['checked_at' => gmdate('c'), 'result' => 'passed', 'environment' => 'http://127.0.0.1:8005', 'scope' => 'prepare_two_nullable_operational_geography_columns', 'migration' => $migration, 'migration_sha256' => hash_file('sha256', $root.'/'.$migration), 'backup' => $dump, 'backup_sha256' => hash_file('sha256', $dump), 'restored_tables_checked' => count($tables), 'protected_tables_checked' => count($protected), 'existing_warehouse_and_equipment_columns_preserved' => true, 'new_references_all_null' => true, 'pilot_migration_applied' => false, 'pilot_configuration_changed' => false, 'mfa_enabled' => false, 'restore_database_removed' => false];
|
||||
} finally {
|
||||
if ($db->transactionLevel() > 0) {
|
||||
$db->rollBack();
|
||||
}
|
||||
DB::purge('receipt_restore');
|
||||
if ($created) {
|
||||
$admin->statement('DROP DATABASE "'.$restore.'" WITH (FORCE)');
|
||||
}
|
||||
}
|
||||
$result['restore_database_removed'] = true;
|
||||
file_put_contents($root.'/docs/proofs/operational-geography-schema-preparation.json', json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES).PHP_EOL);
|
||||
echo 'Operational geography schema prepared only in restored copy; pilot unchanged.'.PHP_EOL;
|
||||
@@ -0,0 +1,23 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import assert from 'node:assert/strict';
|
||||
const source = await fs.readFile('scripts/supplier-evaluation-check.mjs', 'utf8');
|
||||
const common = source.slice(source.indexOf('const base ='), source.indexOf('const author ='));
|
||||
await new Function('fs', 'assert', 'return (async()=>{' + common + `
|
||||
const records=[];
|
||||
for(const role of ['admin','warehouse','procurement']) {
|
||||
const viewer=client(); await viewer.enter(role);
|
||||
assert.equal((await viewer.request('/contexto','POST',{area:'procurement',option:'procurement.internal'})).status,302);
|
||||
const index=await viewer.request('/recepcoes'); assert.equal(index.status,200);
|
||||
let checked=0;
|
||||
for(const receipt of index.page.props.receipts.data.slice(0,2)) {
|
||||
const page=await viewer.request('/recepcoes/'+receipt.id+'/imprimir','GET',undefined,false);
|
||||
assert.equal(page.status,200);
|
||||
assert.ok(page.text.includes('Referências documentais'));
|
||||
assert.ok(page.text.includes('Confirmação original'));
|
||||
checked++;
|
||||
}
|
||||
records.push({role,printedReceiptsChecked:checked});
|
||||
}
|
||||
await fs.writeFile('docs/proofs/receipt-print-http.json',JSON.stringify({checked_at:new Date().toISOString(),environment:base,result:'passed',records,business_commands:false,visual_review:false},null,2));
|
||||
console.log('Reprodução imprimível consultada; sem movimentos ou decisões.');
|
||||
` + '})()')(fs, assert);
|
||||
@@ -0,0 +1,24 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import assert from 'node:assert/strict';
|
||||
const source = await fs.readFile('scripts/supplier-evaluation-check.mjs', 'utf8');
|
||||
const common = source.slice(source.indexOf('const base ='), source.indexOf('const author ='));
|
||||
await new Function('fs', 'assert', 'return (async()=>{' + common + `
|
||||
const viewer=client(); await viewer.enter('admin');
|
||||
assert.equal((await viewer.request('/contexto','POST',{area:'people',option:'people.internal'})).status,302);
|
||||
const index=await viewer.request('/pessoas'); assert.equal(index.status,200);
|
||||
let dossiersChecked=0; let availabilityRows=0;
|
||||
for(const process of index.page.props.processes.data.slice(0,2)) {
|
||||
const dossier=await viewer.request('/pessoas/'+process.id+'/dossier.json','GET',undefined,false);
|
||||
assert.equal(dossier.status,200);
|
||||
assert.equal(dossier.page.schema,'sgc.people.process-dossier.v1');
|
||||
assert.equal(dossier.page.effect,'preparation_only');
|
||||
const page=await viewer.request('/pessoas/'+process.id); assert.equal(page.status,200);
|
||||
assert.deepEqual(dossier.page.candidates,page.page.props.candidates);
|
||||
availabilityRows+=dossier.page.candidates.filter(row=>row.received_availability).length;
|
||||
assert.equal(dossier.page.process.tenant_id,undefined);
|
||||
assert.equal(dossier.page.process.creation_hash,undefined);
|
||||
dossiersChecked++;
|
||||
}
|
||||
await fs.writeFile('docs/proofs/received-availability-http.json',JSON.stringify({checked_at:new Date().toISOString(),environment:base,result:'passed',dossiersChecked,availabilityRows,business_commands:false,visual_review:false,coverage:'Existing visible processes only; positive received-availability cases exercised in disposable tests.'},null,2));
|
||||
console.log('People page/dossier availability projection checked for existing visible sources only.');
|
||||
` + '})()')(fs, assert);
|
||||
@@ -0,0 +1,25 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import assert from 'node:assert/strict';
|
||||
const source = await fs.readFile('scripts/supplier-evaluation-check.mjs', 'utf8');
|
||||
const common = source.slice(source.indexOf('const base ='), source.indexOf('const author ='));
|
||||
await new Function('fs', 'assert', 'return (async()=>{' + common + `
|
||||
const records=[];
|
||||
for(const role of ['admin','warehouse']) {
|
||||
const viewer=client(); await viewer.enter(role);
|
||||
assert.equal((await viewer.request('/contexto','POST',{area:'procurement',option:'procurement.internal'})).status,302);
|
||||
const index=await viewer.request('/stock/requisicoes'); assert.equal(index.status,200);
|
||||
let documentsChecked=0,requestsChecked=0;
|
||||
for(const request of index.page.props.records.data.slice(0,3)) {
|
||||
const detail=await viewer.request('/stock/requisicoes/'+request.id); assert.equal(detail.status,200);
|
||||
requestsChecked++;
|
||||
for(const delivery of detail.page.props.progress.events.filter(e=>e.action==='deliver').slice(0,2)) {
|
||||
const print=await viewer.request('/stock/requisicoes/'+request.id+'/entregas/'+delivery.id+'/imprimir','GET',undefined,false);
|
||||
assert.equal(print.status,200); assert.ok(print.text.includes('Documento de saída interna'));
|
||||
assert.ok(print.text.includes('Sem factura')); documentsChecked++;
|
||||
}
|
||||
}
|
||||
records.push({role,requestsChecked,documentsChecked});
|
||||
}
|
||||
await fs.writeFile('docs/proofs/warehouse-delivery-print-http.json',JSON.stringify({checked_at:new Date().toISOString(),environment:base,result:'passed',records,business_commands:false,visual_review:false,coverage:'Existing records only; complete delivery scenarios exercised in disposable databases.'},null,2));
|
||||
console.log('Consultas de armazém preservadas; sem criar entregas no piloto.');
|
||||
` + '})()')(fs, assert);
|
||||
@@ -58,12 +58,30 @@ $noticeConsultation = in_array('--notice-consultation', $argv ?? [], true);
|
||||
$agreementSources = in_array('--agreement-sources', $argv ?? [], true);
|
||||
$mfaFoundation = in_array('--mfa-foundation', $argv ?? [], true);
|
||||
$mfaManagement = in_array('--mfa-management', $argv ?? [], true);
|
||||
$mfaEvaluation = in_array('--mfa-evaluation', $argv ?? [], true);
|
||||
$peopleDossier = in_array('--people-dossier', $argv ?? [], true);
|
||||
$auditMetadata = in_array('--audit-metadata', $argv ?? [], true);
|
||||
$equipmentIntegration = in_array('--equipment-integration', $argv ?? [], true);
|
||||
$equipmentWarehouse = in_array('--equipment-warehouse', $argv ?? [], true);
|
||||
$receivedAvailability = in_array('--received-availability', $argv ?? [], true);
|
||||
$candidateTerritory = in_array('--candidate-territory', $argv ?? [], true);
|
||||
$candidateAdditionalGeography = in_array('--candidate-additional-geography', $argv ?? [], true);
|
||||
$candidateGeography = in_array('--candidate-geography', $argv ?? [], true);
|
||||
$operationalGeography = in_array('--operational-geography', $argv ?? [], true);
|
||||
$geography = in_array('--geography', $argv ?? [], true);
|
||||
$issuedCancellation = in_array('--issued-cancellation', $argv ?? [], true);
|
||||
$requisitionAudit = in_array('--requisition-audit', $argv ?? [], true);
|
||||
$auditPagination = in_array('--audit-pagination', $argv ?? [], true);
|
||||
$neededAttention = in_array('--needed-attention', $argv ?? [], true);
|
||||
$approvedAttention = in_array('--approved-attention', $argv ?? [], true);
|
||||
$replenishmentHealth = in_array('--replenishment-health', $argv ?? [], true);
|
||||
$receiptBlock = in_array('--receipt-block', $argv ?? [], true);
|
||||
$receiptPrint = in_array('--receipt-print', $argv ?? [], true);
|
||||
$receiptHealth = in_array('--receipt-health', $argv ?? [], true);
|
||||
$operationalHealth = in_array('--operational-health', $argv ?? [], true);
|
||||
$warehouseDeliveryPrint = in_array('--warehouse-delivery-print', $argv ?? [], true);
|
||||
$warehouseRequestPrint = in_array('--warehouse-request-print', $argv ?? [], true);
|
||||
$warehouseRequestExport = in_array('--warehouse-request-export', $argv ?? [], true);
|
||||
$inbox = in_array('--inbox', $argv ?? [], true) || $transport;
|
||||
$warehouse = in_array('--warehouse', $argv ?? [], true) || $pilot || $workdesk || $readiness;
|
||||
try {
|
||||
@@ -188,6 +206,39 @@ try {
|
||||
if ($mfaManagement) {
|
||||
$tests = ['tests/Feature/Settings/TwoFactorManagementTest.php'];
|
||||
}
|
||||
if ($mfaEvaluation) {
|
||||
$tests = ['tests/Feature/Auth/MfaEvaluationBoundaryTest.php'];
|
||||
}
|
||||
if ($peopleDossier) {
|
||||
$tests = ['tests/Feature/PeopleHiringTest.php'];
|
||||
}
|
||||
if ($auditMetadata) {
|
||||
$tests = ['tests/Feature/AuditMetadataBlockTest.php'];
|
||||
}
|
||||
if ($equipmentIntegration || $equipmentWarehouse) {
|
||||
$tests = ['tests/Feature/EquipmentSparePartsTest.php'];
|
||||
}
|
||||
if ($receivedAvailability) {
|
||||
$tests = ['tests/Feature/CandidateSubmissionsTest.php'];
|
||||
}
|
||||
if ($candidateTerritory) {
|
||||
$tests = ['tests/Feature/CandidateSubmissionsTest.php'];
|
||||
}
|
||||
if ($candidateAdditionalGeography) {
|
||||
$tests = ['tests/Feature/CandidateGeographyTest.php', 'tests/Feature/CandidateSubmissionsTest.php'];
|
||||
}
|
||||
if ($candidateGeography) {
|
||||
$tests = ['tests/Feature/CandidateGeographyTest.php', 'tests/Feature/CandidateSubmissionsTest.php'];
|
||||
}
|
||||
if ($operationalGeography) {
|
||||
$tests = ['tests/Feature/OperationalGeographyTest.php'];
|
||||
}
|
||||
if ($geography) {
|
||||
$tests = ['tests/Feature/AdministrativeGeographyTest.php', 'tests/Feature/AdministrativeGeographyReviewTest.php'];
|
||||
}
|
||||
if ($issuedCancellation) {
|
||||
$tests = ['tests/Feature/IssuedOrderCancellationTest.php'];
|
||||
}
|
||||
if ($requisitionAudit || $auditPagination) {
|
||||
$tests = ['tests/Feature/InternalRequisitionCandidateTest.php'];
|
||||
}
|
||||
@@ -200,7 +251,33 @@ try {
|
||||
if ($receiptBlock) {
|
||||
$tests = ['tests/Feature/ReceiptDocumentBlockTest.php'];
|
||||
}
|
||||
if ($receiptPrint) {
|
||||
$tests = ['tests/Feature/ReceiptPrintBlockTest.php'];
|
||||
}
|
||||
if ($receiptHealth) {
|
||||
$tests = ['tests/Feature/ReceiptDocumentHealthTest.php'];
|
||||
}
|
||||
if ($operationalHealth) {
|
||||
$tests = ['tests/Feature/OperationalHealthTest.php'];
|
||||
}
|
||||
if ($warehouseDeliveryPrint || $warehouseRequestPrint || $warehouseRequestExport) {
|
||||
$tests = ['tests/Feature/WarehouseFulfilmentTest.php'];
|
||||
}
|
||||
$filter = match (true) {
|
||||
$receivedAvailability => ['--filter=received availability projection'],
|
||||
$candidateTerritory => ['--filter=candidate territory'],
|
||||
$issuedCancellation => ['--filter=issued cancellation'],
|
||||
$equipmentWarehouse => ['--filter=equipment warehouse requests|reuses the stock operation'],
|
||||
$equipmentIntegration => ['--filter=equipment integration block'],
|
||||
$auditMetadata => ['--filter=audit metadata block'],
|
||||
$peopleDossier => ['--filter=people dossier block'],
|
||||
$mfaEvaluation => ['--filter=mfa evaluation boundary'],
|
||||
$operationalHealth => ['--filter=operational health'],
|
||||
$receiptHealth => ['--filter=receipt document health'],
|
||||
$warehouseRequestExport => ['--filter=warehouse request export'],
|
||||
$warehouseRequestPrint => ['--filter=warehouse request print'],
|
||||
$warehouseDeliveryPrint => ['--filter=warehouse delivery print block'],
|
||||
$receiptPrint => ['--filter=receipt print block'],
|
||||
$receiptBlock => ['--filter=receipt document block'],
|
||||
$replenishmentHealth => ['--filter=replenishment health'],
|
||||
$approvedAttention => ['--filter=approved attention reports'],
|
||||
@@ -238,13 +315,17 @@ try {
|
||||
$awardTasks => ['--filter=task inbox'],
|
||||
default => [],
|
||||
};
|
||||
$process = new Process([PHP_BINARY, 'vendor/bin/pest', '--compact', ...$tests, ...$filter], dirname(__DIR__), $environment, timeout: $financeIntegration || $awardTasks || $quotationTasks || $organizationPreview || $pilotAccessProposal || $formalTasks || $supplyTasks || $historyTasks || $suggestionTasks || $alternativeTasks || $supplierTasks || $dispositionTasks || $inventoryTasks || $lotTasks || $improvementTasks || $qualityTasks || $coordinationTasks || $lotQualityTasks || $lotPagination || $supplyPagination || $correctionPagination || $reservationSources || $historySources || $policySources || $correctionSources || $supplyLineSources || $noticeConsultation ? 300 : 120);
|
||||
$process = new Process([PHP_BINARY, 'vendor/bin/pest', '--compact', ...$tests, ...$filter], dirname(__DIR__), $environment, timeout: $equipmentWarehouse || $operationalHealth || $warehouseRequestExport || $receivedAvailability || $candidateTerritory || $candidateAdditionalGeography || $candidateGeography || $operationalGeography || $geography || $financeIntegration || $awardTasks || $quotationTasks || $organizationPreview || $pilotAccessProposal || $formalTasks || $supplyTasks || $historyTasks || $suggestionTasks || $alternativeTasks || $supplierTasks || $dispositionTasks || $inventoryTasks || $lotTasks || $improvementTasks || $qualityTasks || $coordinationTasks || $lotQualityTasks || $lotPagination || $supplyPagination || $correctionPagination || $reservationSources || $historySources || $policySources || $correctionSources || $supplyLineSources || $noticeConsultation || $peopleDossier ? 300 : 120);
|
||||
$process->run();
|
||||
echo $process->getOutput();
|
||||
if (! $process->isSuccessful()) {
|
||||
throw new RuntimeException('Prova PostgreSQL de contexto não aprovada.');
|
||||
}
|
||||
$races = [];
|
||||
if ($issuedCancellation) {
|
||||
require __DIR__.'/../tests/Support/order-cancellation-pg-races.php';
|
||||
$races = orderCancellationRaces($environment, $config, $database);
|
||||
}
|
||||
if ($inbox) {
|
||||
require __DIR__.'/../tests/Support/inbox-pg-races.php';
|
||||
$races = inboxPgRaces($environment, $config, $database);
|
||||
@@ -367,6 +448,24 @@ try {
|
||||
DB::purge('warehouse_proof_read');
|
||||
}
|
||||
$proofName = match (true) {
|
||||
$receivedAvailability => 'received-availability-pg',
|
||||
$candidateTerritory => 'candidate-territory-pg',
|
||||
$candidateAdditionalGeography => 'candidate-additional-geography-pg',
|
||||
$candidateGeography => 'candidate-geography-pg',
|
||||
$operationalGeography => 'operational-geography-pg',
|
||||
$geography => 'administrative-geography-review-pg',
|
||||
$issuedCancellation => 'issued-order-cancellation-pg',
|
||||
$equipmentWarehouse => 'equipment-warehouse-pg',
|
||||
$equipmentIntegration => 'equipment-integration-pg',
|
||||
$auditMetadata => 'audit-metadata-pg',
|
||||
$peopleDossier => 'people-dossier-pg',
|
||||
$mfaEvaluation => 'mfa-evaluation-boundary-pg',
|
||||
$operationalHealth => 'operational-health-pg',
|
||||
$receiptHealth => 'receipt-document-health-pg',
|
||||
$warehouseRequestExport => 'warehouse-request-export-pg',
|
||||
$warehouseRequestPrint => 'warehouse-request-print-pg',
|
||||
$warehouseDeliveryPrint => 'warehouse-delivery-print-pg',
|
||||
$receiptPrint => 'receipt-print-pg',
|
||||
$receiptBlock => 'receipt-block-pg',
|
||||
$replenishmentHealth => 'replenishment-health-pg',
|
||||
$approvedAttention => 'approved-attention-pg',
|
||||
|
||||
Reference in New Issue
Block a user