66 lines
8.9 KiB
JavaScript
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}));
|