Files
SGF/scripts/summarize-workshop-stock.mjs
T
2026-11-02 20:29:34 +02:00

56 lines
7.7 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
import {spawnSync} from 'node:child_process';
const read=p=>JSON.parse(fs.readFileSync(p,'utf8').replace(/^\uFEFF/,''));
const hash=p=>crypto.createHash('sha256').update(fs.readFileSync(p)).digest('hex');
const s=read('C:/laragon/data/sgf-tempo/run/modular-regression-current.json');
if(s.port!==55439||!/^[a-f0-9]{12}$/.test(s.id))throw Error('Wrong QA host');
const attrs=text=>Object.fromEntries([...text.matchAll(/([\w]+)="([^"]*)"/g)].map(m=>[m[1],m[2]]));
function junit(file,selected=null){
const xml=fs.readFileSync(file,'utf8'),a=attrs(xml.match(/<testsuite\b[^>]*>/)?.[0]??'');
const cases=[...xml.matchAll(/<testcase\b[^>]*\/>|<testcase\b[^>]*>[\s\S]*?<\/testcase>/g)].map(m=>({text:m[0],...attrs(m[0].match(/^<testcase\b[^>]*>/)[0])}));
const picked=cases.filter(c=>!selected||selected.includes(c.name));
if(!picked.length||(selected&&picked.length!==selected.length)||picked.some(c=>/<(failure|error|skipped)\b/.test(c.text)))throw Error('Selected proof not passing: '+file);
if(!selected&&(Number(a.errors)!==0||Number(a.failures)!==0))throw Error('Suite not passing');
return {file:path.relative(s.root,file).replaceAll('\\','/'),sha256:hash(file),tests:picked.length,assertions:picked.reduce((n,c)=>n+Number(c.assertions),0),cases:picked.map(c=>c.name),selection:!!selected,whole_run_tests:Number(a.tests),whole_run_failures:Number(a.failures),whole_run_errors:Number(a.errors)};
}
const base=s.root;
const reused=junit(path.join(base,'workshop-stock-1793632450233.xml'),['test_individual_impact_independence_exact_issue_and_replay','test_client_unverified_fraction_and_unknown_delivery_do_not_debit','test_stale_book_mapping_permission_scope_and_failure_recovery','test_concurrent_confirmation_has_one_claim_and_movement']);
const corrected=junit(path.join(base,'workshop-stock-1793632791460.xml'));
const isolated=junit(path.join(base,'workshop-stock-1793633025634.xml'));
const native=junit(path.join(base,'private/stock-receipt-54f39ed20d16/issue-affected-junit.xml'));
const lots=junit(path.join(base,'private/stock-receipt-0119bb873c56/issue-lots-junit.xml'));
const navigation=junit(path.join(base,'junit-focused.xml'));
if(navigation.tests!==8||navigation.assertions!==90)throw Error('Unexpected navigation proof');
const artifact='../compras/packages/inventory-issue/releases/0.1.0',manifest=read(artifact+'/manifest.json');
if(hash(artifact+'/manifest.json')!=='3c24d9e9fb99bfbb274077495c3a42ddb7eccab06b5c6a956cd739b2bc9b56f0')throw Error('Artifact changed');
for(const [f,sha] of Object.entries(manifest.files))if(hash(artifact+'/'+f)!==sha||hash('packages/inventory-issue/'+f)!==sha)throw Error('Artifact/source mismatch');
for(const tag of ['54f39ed20d16','0119bb873c56']){
const tested=path.join(base,'private/stock-receipt-'+tag+'/app/packages/inventory-issue/releases/0.1.0');
if(hash(tested+'/manifest.json')!==hash(artifact+'/manifest.json'))throw Error('Tested artifact differs');
for(const [f,sha] of Object.entries(manifest.files))if(hash(tested+'/'+f)!==sha)throw Error('Tested writer differs');
if(hash(path.join(base,'private/stock-receipt-'+tag+'/app/app/Services/StockIssueMovement.php'))!==hash('../compras/app/Services/StockIssueMovement.php'))throw Error('Tested native adapter differs');
}
const prior=read('docs/proofs/module-association-http-lock-summary-20261102.json');
for(const p of [prior.proof,...prior.previous_proofs])if(hash(p.path)!==p.sha256)throw Error('Previous association proof changed');
const load=read('docs/proofs/stock-read-load-20261102.json');
if(hash('packages/inventory-read/src/InventoryReader.php')!==load.source_sha256)throw Error('Reader changed since load test');
const post=spawnSync('C:/laragon/tools/sgf-tempo/php-8.4.27/php.exe',['scripts/workshop-stock-state.php'],{encoding:'utf8',windowsHide:true});
if(post.status!==0)throw Error('QA postcheck failed: '+post.stderr);
const source=['app/Modules/Workshop/WorkshopStock.php','app/Modules/Workshop/Http/WorkshopStockController.php','resources/js/WorkshopStockWorkspace.jsx','resources/js/WorkshopMenuWorkspace.jsx','config/workshop_stock.php','tests/Feature/WorkshopStockTest.php','tests/Support/workshop-stock-worker.php','../compras/app/Services/StockIssueMovement.php','../compras/app/Services/StockWorkflow.php'];
const tests={reused_passing_cases:reused,corrected_source:corrected,scope_and_command:isolated,native_issues:native,native_lots:lots,navigation};
const report={date:'2026-11-02',increment:'WORKSHOP-STOCK-ISSUE-SIM-v1',status:'local_synthetic_implementation',tests,totals:{tests:Object.values(tests).reduce((n,t)=>n+t.tests,0),assertions:Object.values(tests).reduce((n,t)=>n+t.assertions,0)},
proof_selection:'Four passing cases reused from an intermediate five-case run with one fixture-date failure. Only the corrected case and the additional scope case were rerun; the failed case is excluded from reused results.',
scenario_asserted:{initial_physical_milli:'10000',consumption_milli:'2000',final_physical_milli:'8000',movements_added:1,claims_added:1,same_book_observed_by_stock:true,preparer_cannot_confirm:true,replay_original_movement:true,stale_book_map_rights_context_refused:true,unknown_previous_issue_refused:true,client_delivered_unverified_fraction_insufficient_refused:true,failure_after_claim_insert_atomic_and_recovered:true,other_company_sources_and_proposals_not_visible:true,other_company_prepare_and_confirm_refused:true,wrong_module_context_refused:true,changed_idempotency_body_refused:true},
concurrency_asserted:{processes:2,lock:'company row held by first transaction',pg_blocking_pids_observed:true,second_after_release:'200 replay same movement',one_movement_and_claim:true},
correction_asserted:{old_proposal_after_source_correction:'409',verified_v2_after_new_review_physical_milli:'7000',verified_v3_after_consumption:'409 without second effect'},
package:{...manifest,manifest_sha256:hash(artifact+'/manifest.json'),source_and_artifact_equal:true},source_sha256:Object.fromEntries(source.map(p=>[p,hash(p)])),
qa_postcheck:JSON.parse(post.stdout),static_ui:{proof:'docs/proofs/workshop-stock-ui-20261102.json',sha256:hash('docs/proofs/workshop-stock-ui-20261102.json'),...read('docs/proofs/workshop-stock-ui-20261102.json')},
build:{tool:'pinned Node 24.22.1 / Vite 8.3.5',passed:true,manifest_sha256:hash('public/build/manifest.json'),visual_review:false},
reused_evidence_sha256:Object.fromEntries(['docs/proofs/stock-receipt-link-20261102.json','docs/proofs/stock-read-load-20261102.json','docs/proofs/stock-read-composition-20261102.json'].map(p=>[p,hash(p)])),
preservation:{writes_confined_to_new_synthetic_fixtures:true,regression_not_reset:true,six_protected_records_untouched:true,blocked_cache_untouched:true,demos_untouched:true,previous_association_proofs_unchanged:true,real_grants_or_data_changed:false,purchasing_candidate_activation:false,publication:false,checkpoint:false},
limits:['Quantity-only new eligible workshop origins; lots/reservations/spare parts refused.','Reconciliation of existing deliveries, partial allocation, returns and compensation not implemented.','Initial stock in workshop fixture is synthetic; previous native receipt proof is separate, not an installed single end-to-end ERP scenario.','Conference tables are QA fixture only; operational installer and real rights not applied.','Source list paginated 25, latest 25 proposals only.','PostgreSQL tested; new write-load capacity and other drivers unvalidated.'],visual_review_pending:true,deployment_capacity_validated:false};
fs.writeFileSync('docs/proofs/workshop-stock-20261102.json',JSON.stringify(report,null,2)+'\n');
console.log(JSON.stringify({totals:report.totals,postcheck:report.qa_postcheck,artifact_sha256:report.package.manifest_sha256,visual_review_pending:true,deployment_capacity_validated:false},null,2));