const fs = require('node:fs') const crypto = require('node:crypto') const plan = JSON.parse(fs.readFileSync('/tmp/material-hotfix-plan.json', 'utf8')) const stage = process.argv[2] if (!['before', 'after'].includes(stage) || plan.version !== 1 || plan.files.length !== 7) throw Error('Invalid hotfix plan') const digest = bytes => crypto.createHash('sha256').update(bytes).digest('hex') const normalized = bytes => Buffer.from(bytes.toString('utf8').replace(/\r\n/g, '\n')) const allowed = new Set([ '.next/server/chunks/29.js', '.next/server/app/api/external/download/route.js', '.next/server/app/api/external/product-assets/route.js', 'app/api/external/download/route.ts', 'app/api/external/product-assets/route.ts', 'lib/task-detail-asset-edit.ts', 'lib/task-detail-change-approval.ts', ]) for (const file of plan.files) { if (!allowed.delete(file.path)) throw Error('Duplicate or unapproved hotfix path') const bytes = fs.readFileSync('/app/' + file.path) if (digest(file.normalizeLf ? normalized(bytes) : bytes) !== file[stage]) throw Error('Image file hash mismatch: ' + file.path) } if (allowed.size || digest(normalized(fs.readFileSync('/app/prisma/schema.prisma'))) !== plan.schemaHash) { throw Error('Existing production schema changed') } if (stage === 'before') { if (fs.readFileSync('/app/DEPLOYMENT_COMMIT', 'utf8').trim() !== plan.baseCommit) throw Error('Production base commit mismatch') } else { const commit = process.env.VCS_REF if (!/^[0-9a-f]{40}$/.test(commit || '') || /^0+$/.test(commit)) throw Error('Exact release recipe commit required') fs.writeFileSync('/app/DEPLOYMENT_COMMIT', commit + '\n') fs.chmodSync('/app/DEPLOYMENT_COMMIT', 0o444) fs.writeFileSync('/app/MATERIAL_IMAGE_HOTFIX.json', JSON.stringify({ ...plan, releaseRecipeCommit: commit }) + '\n') fs.chmodSync('/app/MATERIAL_IMAGE_HOTFIX.json', 0o444) for (const file of plan.files) fs.chmodSync('/app/' + file.path, 0o444) } console.log('Verified bounded material image hotfix: ' + stage)