import fs from 'node:fs'; import path from 'node:path'; import crypto from 'node:crypto'; import {ROOT,request,classRows} from './platform-client.mjs'; const names=['ksfGetPublicBoard','ksfStartWorkshopSession','ksfSaveWorkshopTask','ksfCreateProjectFromOpportunity','ksfTransitionProject','ksfSaveWorkshopConcept','ksfGetWorkshopProject','ksfGetIngestContract']; const manifest=JSON.parse(fs.readFileSync(path.join(ROOT,'dist/manifest.json'))); const dry=!process.argv.includes('--apply');fs.mkdirSync(path.join(ROOT,'evidence'),{recursive:true}); const existing=await classRows('Function');const schemas=await request('GET','/schemas'); const journal={at:new Date().toISOString(),dryRun:dry,functions:[],schemaChanges:0,permissionChanges:0,credentialsChanged:false}; // Do not overwrite unrelated changes: existing entrypoints must still be the audited v0.1 source. for(const name of names){ const item=manifest.functions.find(x=>x.name===name);if(!item)throw Error('构建缺少目标函数'); const code=fs.readFileSync(path.join(ROOT,item.file),'utf8');if(crypto.createHash('sha256').update(code).digest('hex')!==item.sha256)throw Error('构建指纹不匹配'); const matches=existing.filter(x=>x.name===name||x.path===item.path);if(matches.length>1||matches.some(x=>x.path!==item.path||x.name!==name))throw Error('函数名称/路径冲突'); const row=matches[0]; if(row&&row.code!==code&&!row.code.includes("release: 'ksf.cloud.v0.1'")&&!row.code.includes('ksf.cloud.v0.1'))throw Error('发现未知函数版本,停止覆盖'); journal.functions.push({name,path:item.path,sha256:item.sha256,objectId:row?.objectId||null,action:row?(row.code===code?'unchanged':'update'):'create'}); } fs.writeFileSync(path.join(ROOT,'evidence/workshop-deploy-plan.json'),JSON.stringify(journal,null,2)); if(!dry){ const backupPath=path.join(ROOT,'evidence/workshop-functions-before-'+Date.now()+'.json'); fs.writeFileSync(backupPath,JSON.stringify(existing.filter(x=>names.includes(x.name)),null,2)); for(const item of journal.functions){ if(item.action==='unchanged')continue; const entry=manifest.functions.find(x=>x.name===item.name),code=fs.readFileSync(path.join(ROOT,entry.file),'utf8'); if(item.objectId)await request('PUT','/classes/Function/'+item.objectId,{code}); else item.objectId=(await request('POST','/classes/Function',{name:item.name,path:item.path,code,ACL:{}})).objectId; const read=await request('GET','/classes/Function/'+item.objectId);if(read.code!==code)throw Error('函数回读不一致'); item.verified=true;fs.writeFileSync(path.join(ROOT,'evidence/workshop-deployment.json'),JSON.stringify(journal,null,2)); } const after=await request('GET','/schemas'); for(const before of schemas.results){const now=after.results.find(x=>x.className===before.className);if(JSON.stringify(before.fields)!==JSON.stringify(now.fields)||JSON.stringify(before.classLevelPermissions)!==JSON.stringify(now.classLevelPermissions))throw Error('表结构/权限意外变化');} journal.finishedAt=new Date().toISOString();journal.schemaAndPermissionsPreserved=true; fs.writeFileSync(path.join(ROOT,'evidence/workshop-deployment.json'),JSON.stringify(journal,null,2)); } console.log(JSON.stringify(journal,null,2));