Files
SGF/scripts/prepare-sgc-8085.mjs
2026-11-11 09:00:17 +02:00

66 lines
8.9 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
import assert from 'node:assert/strict';
// Prepare/check only. Deliberately has no install, activate, migrate, restart or grant action.
const command=process.argv[2]??'check';assert(['check','prepare'].includes(command));
const repo=process.cwd(),run=JSON.parse(fs.readFileSync('C:/laragon/data/sgf-tempo/run/modular-regression-current.json'));
assert.equal(run.id,'2576427d1939');assert.equal(run.port,55439);
const root=path.join(run.root,'private/receipt-composition-cc50a276c10c'),state=JSON.parse(fs.readFileSync(path.join(root,'state.json'))),server=JSON.parse(fs.readFileSync(path.join(root,'demo-server.json')));
assert.equal(state.tag,'cc50a276c10c');assert.equal(server.port,8085);assert.equal(state.singleInstallation,true);
const hash=p=>crypto.createHash('sha256').update(fs.readFileSync(p)).digest('hex'),digest=v=>crypto.createHash('sha256').update(JSON.stringify(v)).digest('hex'),normalise=p=>fs.readFileSync(p,'utf8').replaceAll('\r\n','\n');
const prior=JSON.parse(fs.readFileSync('docs/proofs/purchasing-completion-final-20261104-r2.json'));
for(const source of prior.sources)assert.equal(hash(path.join(state.app,source.file)),source.sha256,'Protected8085 '+source.file);
const endpoint=JSON.parse(fs.readFileSync('docs/proofs/sgc-invoice-endpoint-20261110.json'));
assert(endpoint.passed&&endpoint.checks===78);for(const source of endpoint.sources)assert.equal(hash(source.file),source.sha256,'Accepted endpoint '+source.file);
const files=['app/Http/Middleware/AuthenticateSgcInvoice.php','app/Http/Requests/Sgc/StoreInvoiceRequest.php','app/Http/Controllers/Api/V1/SgcInvoiceIngressController.php','app/Modules/Purchasing/SgcInvoiceIngress.php','config/sgc_invoice.php','routes/sgc.php','bootstrap/app.php','app/Shared/Context/ModuleNavigation.php'];
const additions={
'bootstrap/app.php':" api: __DIR__.'/../routes/sgc.php',\n",
'app/Shared/Context/ModuleNavigation.php':" if (preg_match('~^api/v1/integration/sgc/invoices(?:/orders/[1-9][0-9]*)?$~D', $path)) return 'purchasing';\n"
};
const entries=files.map(file=>{const target=path.join(state.app,file),exists=fs.existsSync(target);if(additions[file])assert.equal(normalise(file).replace(additions[file],''),normalise(target),'Only additive routing patch allowed');else assert(!exists,'Unexpected endpoint installation: '+file);return {file,before_sha256:exists?hash(target):null,after_sha256:hash(file)};});
assert(/'enabled'\s*=>\s*false/.test(normalise('config/sgc_invoice.php'))&&/'principals'\s*=>\s*\[\]/.test(normalise('config/sgc_invoice.php')));
const sgcRoot='C:/laragon/www/compras',sgcFiles=['app/Services/SgfInvoiceGateway.php','config/sgf_invoice.php','app/Services/SgfInvoiceDispatches.php','app/Http/Controllers/SgfInvoiceDispatchController.php',
'database/migrations/2026_11_10_000001_create_sgf_invoice_dispatches.php',...['layout','document-form','index','evidence','show'].map(x=>'resources/views/sgf-invoices/'+x+'.blade.php'),
'app/Services/InvoiceWorkflow.php','app/Http/Controllers/InvoiceController.php','routes/web.php','resources/js/pages/invoices/index.tsx'];
const clientFiles=[{source:'packages/sgc-invoice-client/src/InvoiceClient.php',target:'client/InvoiceClient.php'},...sgcFiles.map(file=>({source:path.join(sgcRoot,file),target:'sgc/'+file}))].map(x=>({...x,sha256:hash(x.source)}));
assert(/'enabled'\s*=>\s*false/.test(normalise(path.join(sgcRoot,'config/sgf_invoice.php'))));
assert(/'workspace_enabled'\s*=>\s*false/.test(normalise(path.join(sgcRoot,'config/sgf_invoice.php'))));
const plan={schema:1,installation:'cc50a276c10c',port:8085,endpoint_files:entries,client_files:clientFiles.map(({target,sha256})=>({target,sha256})),
source_endpoint_proof_sha256:hash('docs/proofs/sgc-invoice-endpoint-20261110.json'),
modes:{prepared_only:true,install:false,activate:false,publish:false,new_database:false,new_demo:false},
endpoint_configuration:{enabled:false,principals:[]},sgc_configuration:{enabled:false,workspace_enabled:false,connections:[],client_package:null,client_package_sha256:null},
source_schema:{migration:'2026_11_10_000001_create_sgf_invoice_dispatches',pilot_applied:false,source_regression_book_only:'b4093c6f6707'},
sql_privileges_to_review:{approved:false,role:null,database:null,dispatches:['SELECT','INSERT','UPDATE'],events:['SELECT','INSERT'],sequences:['USAGE','SELECT'],public_grants:false},
source_existing_files_require_manual_merge:['app/Services/InvoiceWorkflow.php','app/Http/Controllers/InvoiceController.php','routes/web.php','resources/js/pages/invoices/index.tsx'],
review_required:['Approve installation of the implemented SGC draft workspace/schema and disabled code; no blanket migration or existing-invoice forwarding',
'Explicit source tenant and SGF tenant/company/unit mapping; reviewed order and line IDs',
'Named QA principal with existing company.view/unit.view/purchasing_invoices.view/prepare rights, auth/context versions',
'Private credential provisioning, separately approved disabled-code installation and subsequent activation decision',
'End-to-end SGC transport to this same8085 installation after approval; no deployment claim from kernel/fake tests']};
const id=digest(plan).slice(0,20),stage=path.join(root,'prepared-sgc-invoice',id),protectedFiles=['state.json','binding.json','demo-server.json'];
const before=protectedFiles.map(file=>({file,sha256:hash(path.join(root,file))}));
if(command==='prepare'){
if(fs.existsSync(stage)){assert(fs.existsSync(path.join(stage,'manifest.json')),'Incomplete preparation preserved; inspect manually');assert.deepEqual(JSON.parse(fs.readFileSync(path.join(stage,'manifest.json'))),plan);}
else{
fs.mkdirSync(stage,{recursive:true});for(const entry of entries){const out=path.join(stage,'8085',entry.file);fs.mkdirSync(path.dirname(out),{recursive:true});fs.copyFileSync(entry.file,out);if(entry.before_sha256){const backup=path.join(stage,'before',entry.file);fs.mkdirSync(path.dirname(backup),{recursive:true});fs.copyFileSync(path.join(state.app,entry.file),backup);}}
for(const entry of clientFiles){const out=path.join(stage,entry.target);fs.mkdirSync(path.dirname(out),{recursive:true});fs.copyFileSync(entry.source,out);}
fs.writeFileSync(path.join(stage,'mapping.template.json'),JSON.stringify({selected:false,id:null,version:null,authority:'sgf_only',source_scope:{tenant_id:null},target_scope:{tenant_id:null,company_id:null,unit_id:null},order_id:null,lines:{}},null,2)+'\n');
fs.writeFileSync(path.join(stage,'principal.template.json'),JSON.stringify({selected:false,enabled:false,client_id:null,token_sha256:null,user_id:null,auth_version:null,tenant_id:null,company_id:null,unit_id:null,context_version:null},null,2)+'\n');
fs.writeFileSync(path.join(stage,'source-mapping.template.json'),JSON.stringify({approved:false,label:null,approved_by:null,approved_at:null,source_supplier_id:null,mapping:{id:null,version:null,authority:'sgf_only',source_scope:{tenant_id:null},target_scope:{tenant_id:null,company_id:null,unit_id:null},order_id:null,lines:{}}},null,2)+'\n');
fs.writeFileSync(path.join(stage,'manifest.json'),JSON.stringify(plan,null,2)+'\n');
}
}
assert(fs.existsSync(path.join(stage,'manifest.json')),'Preparation must exist before check');
assert.deepEqual(JSON.parse(fs.readFileSync(path.join(stage,'manifest.json'))),plan);
for(const entry of entries)assert.equal(hash(path.join(stage,'8085',entry.file)),entry.after_sha256);
for(const entry of clientFiles)assert.equal(hash(path.join(stage,entry.target)),entry.sha256);
assert.deepEqual(JSON.parse(fs.readFileSync(path.join(stage,'mapping.template.json'))),{selected:false,id:null,version:null,authority:'sgf_only',source_scope:{tenant_id:null},target_scope:{tenant_id:null,company_id:null,unit_id:null},order_id:null,lines:{}});
assert.deepEqual(JSON.parse(fs.readFileSync(path.join(stage,'principal.template.json'))),{selected:false,enabled:false,client_id:null,token_sha256:null,user_id:null,auth_version:null,tenant_id:null,company_id:null,unit_id:null,context_version:null});
assert.deepEqual(JSON.parse(fs.readFileSync(path.join(stage,'source-mapping.template.json'))),{approved:false,label:null,approved_by:null,approved_at:null,source_supplier_id:null,mapping:{id:null,version:null,authority:'sgf_only',source_scope:{tenant_id:null},target_scope:{tenant_id:null,company_id:null,unit_id:null},order_id:null,lines:{}}});
for(const source of prior.sources)assert.equal(hash(path.join(state.app,source.file)),source.sha256);
for(const source of before)assert.equal(hash(path.join(root,source.file)),source.sha256);
for(const entry of entries)assert.equal(fs.existsSync(path.join(state.app,entry.file))?hash(path.join(state.app,entry.file)):null,entry.before_sha256);
console.log(JSON.stringify({passed:true,command,preparation_id:id,endpoint_files:entries.length,client_files:clientFiles.length,target_code_changed:false,target_state_changed:false,credentials_created:false,mappings_assigned:false,installed:false,activated:false,new_demo:false}));