// Only the two new public board action functions; no changes to existing functions, schemas or ACLs. import fs from 'node:fs'; import path from 'node:path'; import crypto from 'node:crypto'; import { ROOT, request } from './platform-client.mjs'; const sha = (s) => crypto.createHash('sha256').update(s).digest('hex'); const apply = process.argv.includes('--apply'); const manifest = JSON.parse(fs.readFileSync(path.join(ROOT, 'dist/manifest.json'))); const names = ['ksfSetPublicOpportunityStatus', 'ksfRatePublicOpportunity']; const output = path.join(ROOT, 'evidence/board-actions-' + Date.now() + '.json'); const journal = { ok: false, readOnly: !apply, at: new Date().toISOString(), businessWrites: 0, schemaChanges: 0, permissionChanges: 0, functions: [] }; try { const schemas = await request('GET', '/schemas'); for (const name of names) { const entry = manifest.functions.find((f) => f.name === name); if (!entry || entry.path !== '/' + name) throw Error('BAD_MANIFEST'); const code = fs.readFileSync(path.join(ROOT, entry.file), 'utf8'); if (sha(code) !== entry.sha256) throw Error('HASH_MISMATCH'); new Function(code); const query = new URLSearchParams({ where: JSON.stringify({ $or: [{ name }, { path: '/' + name }] }), limit: '3' }); const matches = (await request('GET', '/classes/Function?' + query)).results; if (matches.length > 1 || matches.some((f) => f.name !== name || f.path !== '/' + name || f.code !== code)) throw Error('UNKNOWN_FUNCTION_VERSION'); journal.functions.push({ name, path: '/' + name, sha256: sha(code), objectId: matches[0]?.objectId || null, action: matches.length ? 'unchanged' : 'create' }); } if (apply) for (const entry of journal.functions) { if (entry.action === 'unchanged') continue; const def = manifest.functions.find((f) => f.name === entry.name), code = fs.readFileSync(path.join(ROOT, def.file), 'utf8'); const created = await request('POST', '/classes/Function', { name: entry.name, path: entry.path, code, ACL: {} }); entry.objectId = created.objectId; fs.writeFileSync(output, JSON.stringify(journal, null, 2), { mode: 0o600 }); const read = await request('GET', '/classes/Function/' + entry.objectId); if (read.code !== code || read.path !== entry.path || read.name !== entry.name) throw Error('READBACK_MISMATCH'); entry.verified = true; } if (JSON.stringify((await request('GET', '/schemas')).results) !== JSON.stringify(schemas.results)) throw Error('SCHEMA_OR_PERMISSION_CHANGE'); journal.ok = true; journal.schemaAndPermissionsPreserved = true; fs.writeFileSync(output, JSON.stringify(journal, null, 2), { mode: 0o600 }); console.log(JSON.stringify(journal)); } catch (error) { journal.error = { code: /^[A-Z_]+$/.test(error.message) ? error.message : 'DEPLOY_STOPPED' }; fs.writeFileSync(output, JSON.stringify(journal, null, 2), { mode: 0o600 }); console.error(JSON.stringify(journal)); process.exitCode = 1; }