// Executes the captured production handler definitions against local in-memory // storage only. No platform-client import and no network or production writes. import fs from 'node:fs'; import vm from 'node:vm'; import crypto from 'node:crypto'; import assert from 'node:assert/strict'; import {runWorkshop} from '../skills/ksf-workshop-runtime/scripts/ksf-workshop.mjs'; const output='work/S4-classroom-isolated-simulation-20261103-02.json'; assert.equal(fs.existsSync(output),false,'Preserve prior simulation'); const base='work/S4-classroom-next-20261103/'; const audit=JSON.parse(fs.readFileSync('work/S4-classroom-contract-check-20261103-03.json','utf8')); assert.equal(audit.ok,true); const sha=value=>crypto.createHash('sha256').update(value).digest('hex'); const copy=structuredClone; const proof=[],calls=[],checks=[]; const okay=(name,fn)=>{fn();checks.push({name,passed:true});}; function readEmbeddedData(code,name){ const marker='runDatabaseFunction('+JSON.stringify(name)+',request,response,'; const at=code.lastIndexOf(marker)+marker.length;assert.ok(at>=marker.length); let depth=0,quoted=false,escaped=false; for(let i=at;ix.name===name);assert.equal(sha(code),expected.sha256); const runtime=readEmbeddedData(code,name); if(data)assert.deepEqual(runtime,data);else data=runtime; const handlers=vm.runInNewContext(code+'\n;({createApplication,attachWorkshopHandlers});',{ Buffer,structuredClone,URL,URLSearchParams,TextEncoder,TextDecoder },{timeout:5000}); modules.set(name,handlers);proof.push({name,sha256:sha(code)}); } const actor={id:'isolated-service-only',kind:'service',scopes:['ingest:write','data:read','source:write'],projectIds:['*']}; const attribution={agentKey:'agent-registration:11111111-1111-4111-8111-111111111111',agentName:'隔离验证助手',agentKeyOrigin:'runtime_config', participantKey:'isolated-participant',participantName:'隔离验证参与者',channel:'wecom',agentRuntimePlatform:'hermes',agentChannel:'wecom'}; const agentProfile={key:attribution.agentKey,name:attribution.agentName,identityBasis:'runtime_config',role:'workshop',runtimePlatform:'hermes',channel:'wecom'}; const db=new Map(),copyRow=value=>value==null?null:copy(value); let sequence=0; const rows=name=>{if(!db.has(name))db.set(name,[]);return db.get(name);}; const equal=(a,b)=>JSON.stringify(a??null)===JSON.stringify(b??null); const store={ async transaction(fn){const before=copy(db);try{return await fn(this);}catch(error){db.clear();for(const [k,v]of before)db.set(k,v);throw error;}}, async lock(){}, async get(name,id){return copyRow(rows(name).find(x=>x.objectId===id));}, async list(name,{where={},afterId,limit=1000}={}){return copy(rows(name).filter(row=>Object.entries(where).every(([k,v])=>equal(row[k],v))).filter(row=>!afterId||row.objectId>afterId).sort((a,b)=>a.objectId.localeCompare(b.objectId)).slice(0,limit));}, async create(name,input){for(const key of Object.keys(input))assert.ok(data.tables[name][key],name+'.'+key); const row={...copy(input),objectId:'sim'+String(++sequence).padStart(7,'0'),createdAt:new Date().toISOString(),updatedAt:new Date().toISOString()};rows(name).push(row);return copy(row);}, async update(name,id,input){for(const key of Object.keys(input))assert.ok(data.tables[name][key],name+'.'+key); const row=rows(name).find(x=>x.objectId===id);assert.ok(row);Object.assign(row,copy(input),{updatedAt:new Date().toISOString()});return copy(row);} }; // Seed a synthetic actor/session and accepted original source text only in RAM. // The null ID fields of the delivered templates are the only mutated inputs. rows('KfSession').push({objectId:'simSession01',dataMode:'live',workshopKey:'KSF-真实资料-20261102-01',stageCode:'S3',studentName:attribution.participantName,groupId:'',meta:{ownerId:actor.id,attribution}}); const publicRows=JSON.parse(fs.readFileSync('work/S4-public-record-inspection-20261103.json','utf8')).records; for(const row of publicRows.filter(x=>x.className==='KfSource'))rows('KfSource').push({...copy(row.record),meta:{ownerId:actor.id}}); const previous=JSON.parse(fs.readFileSync('work/first-live-S3-v2-template.json','utf8')); const originalIds={'consumer-cq-03':'cq-03','consumer-cq-04':'cq-04'}; for(const [key,id]of Object.entries(originalIds)){ const input=JSON.parse(fs.readFileSync('work/consumer-quote-inputs-20261102/'+id+'.input.json','utf8')); rows('KfSource').push({...copy(input.source),objectId:previous.report.evidence[key].sourceId,meta:{ownerId:actor.id}}); } const beforeSourceRows=copy(rows('KfSource')); const apps=new Map(); for(const [name,impl]of modules){ const app=impl.createApplication({store,crypto,Parse:{},data,settings:{signingSecret:'isolated-simulation-only'},files:{}}); impl.attachWorkshopHandlers(app,{store,data});apps.set(name,app); } const general=apps.get('ksfSaveWorkshopTask'); async function call(name,params){ const app=apps.get(name)||general; const result=await app.dispatch(name,copy(params),actor); return copy(result.data); } async function execute(command,input,requestId){ const result=await runWorkshop(command,copy(input),{requestId,agentProfile,call}); calls.push({command,requestId,ok:true});return result; } const getTemplate=name=>JSON.parse(fs.readFileSync(base+name,'utf8')); const bound=(name,ids)=>{const input=getTemplate(name),result={...copy(input),...ids};for(const key of Object.keys(ids))assert.equal(input[key],null);return result;}; const prefix='KSF-真实资料-20261102-01',sessionId='simSession01'; function verifyReport(row,template,projectId=null){ assert.equal(row.reportType,'workshop_auxiliary');assert.equal(row.artifactSubtype,'classroom_task');assert.equal(row.artifactType,template.artifactType); for(const field of ['rawText','version','stageCode','reportSeriesKey'])assert.equal(row[field],field==='reportSeriesKey'?template.taskKey:template[field]); assert.deepEqual(row.meta.workshop.sourceIds,template.sourceIds);assert.deepEqual(row.meta.workshop.pendingQuestions,template.pendingQuestions); assert.equal(row.evidenceCount,4);assert.equal(row.ownerSession,sessionId);assert.equal(row.projectId??null,projectId); for(const field of ['chapters','evidence','method','sources','subtitle'])assert.deepEqual(row.report[field],template.report[field]); assert.deepEqual(row.report.executive.slice(1),template.report.executive);assert.equal(row.report.executive[0],'课堂过程成果,未经员工正式确认;待验证项不作为市场结论。'); } let step='candidate save/read'; try{ const candidate=bound('01-candidate-save.template.json',{sessionId}); const c=await execute('workshop-save',candidate,prefix+':S3-份量候选:save:v1'); const artifact=(await execute('workshop-read',{className:'KfArtifact',objectId:c.artifactId},prefix+':S3-份量候选:read:v1')).record; okay('candidate report stored as workshop_auxiliary/classroom_task with opportunity_card artifactType',()=>verifyReport(artifact,candidate)); const opp=(await execute('workshop-read',{className:'KfOpportunity',objectId:c.opportunityId},prefix+':portion-fit:read:v1')).record; okay('opportunity handler supplies hypothesis/candidate/version/series and report link',()=>{ assert.equal(opp.claimKind,'hypothesis');assert.equal(opp.status,'candidate');assert.equal(opp.version,1); assert.equal(opp.opportunitySeriesKey,candidate.opportunity.key);assert.deepEqual(opp.linkedArtifacts,[c.artifactId]); }); step='project create/status'; const p=await execute('project-create',bound('02-project-create.template.json',{sessionId,opportunityId:c.opportunityId}),prefix+':portion-fit-project:create:v1'); const projectId=p.projectId,statusInput={sessionId,projectId}; const created=await execute('project-status',statusInput,prefix+':portion-fit-project:read-created:v1'); okay('projectKey stored in meta, stage S3, no project artifacts, no enterprise approval',()=>{ assert.equal(created.project.meta.projectKey,getTemplate('02-project-create.template.json').projectKey); assert.equal(created.project.stageCode,'S3');assert.equal(created.artifacts.length,0);assert.equal(p.enterpriseApproval,false); }); okay('classroom project changes opportunity to approved with linked project only',()=>{ const saved=rows('KfOpportunity').find(x=>x.objectId===c.opportunityId);assert.equal(saved.status,'approved');assert.equal(saved.linkedProject,projectId); }); // Negative branch is local-only and uses a simulation-specific request ID. step='negative control: refuse premature transition'; let premature; try{await call('ksfTransitionProject',{sessionId,projectId,toStage:'S4',state:'in_progress',note:'negative control only',requestId:'isolated-premature-S4'});}catch(error){premature=error.code;} okay('S3 project artifact is necessary before entering S4',()=>assert.equal(premature,'STAGE_REQUIREMENTS_NOT_MET')); step='project S3 baseline/save/read'; const s3input=bound('03-project-S3-save.template.json',{sessionId,projectId}); const s3=await execute('workshop-save',s3input,prefix+':S3-项目基线:save:v1'); const s3row=(await execute('workshop-read',{className:'KfArtifact',objectId:s3.artifactId},prefix+':S3-项目基线:read:v1')).record; okay('project S3 baseline keeps four quotes/eight questions and actual project reference',()=>verifyReport(s3row,s3input,projectId)); step='transition/status'; const moved=await execute('project-progress',bound('04-project-S4-progress.template.json',{sessionId,projectId}),prefix+':portion-fit-project:to-S4:v1'); const underway=await execute('project-status',statusInput,prefix+':portion-fit-project:read-S4:v1'); okay('project transition returns S4/in_progress and preserves S3 baseline',()=>{ assert.equal(moved.stageCode,'S4');assert.equal(moved.progressState,'in_progress');assert.equal(moved.enterpriseApproval,false); assert.equal(underway.project.stageCode,'S4');assert.equal(underway.artifacts.length,1); }); step='project S4 assessment/save/read'; const s4input=bound('05-project-S4-save.template.json',{sessionId,projectId}); const s4=await execute('workshop-save',s4input,prefix+':S4:save:v1'); const s4row=(await execute('workshop-read',{className:'KfArtifact',objectId:s4.artifactId},prefix+':S4:read:v1')).record; okay('S4 assessment_report persists as artifactType alongside workshop_auxiliary reportType',()=>verifyReport(s4row,s4input,projectId)); const final=await execute('project-status',statusInput,prefix+':portion-fit-project:read-final:v1'); okay('final project remains S4/in_progress with S3/S4 reports and no concept/formula',()=>{ assert.equal(final.project.stageCode,'S4');assert.equal(final.project.progressState,'in_progress');assert.equal(final.artifacts.length,2); assert.equal(final.concept,null);assert.equal(final.formulaItems.length,0);assert.equal(final.events.length,4); }); okay('12 ordered commands use each delivered request ID exactly once',()=>{ assert.equal(calls.length,12);assert.deepEqual(calls.map(x=>x.requestId),audit.checkedRequestIds); }); okay('only three reports/sources, one candidate/project and five write audit records created in RAM',()=>{ assert.equal(rows('KfArtifact').length,3);assert.equal(rows('KfOpportunity').length,1);assert.equal(rows('KfProject').length,1);assert.equal(rows('KsfOperation').length,5); assert.equal(rows('KfSource').length-beforeSourceRows.length,3); assert.equal(rows('KfProduct').length,0);assert.equal(rows('KfCategory').length,0);assert.equal(rows('KfConcept').length,0); assert.deepEqual(rows('KfSource').filter(x=>beforeSourceRows.some(y=>x.objectId===y.objectId)),beforeSourceRows); }); const result={ok:true,at:new Date().toISOString(),scope:'Local RAM simulation using captured deployed command source and runtime schema; synthetic execution identity/session', productionCalls:0,productionWrites:0,browserVerified:false,templatesModified:false,checksPassed:checks.length,checks,calls, deployedCode:proof,simulatedOnly:true,negativeControl:'Premature S4 transition rejected before project S3 baseline', threeReportedDifferencesResolved:true,expectedFinal:{stageCode:'S4',progressState:'in_progress',reports:3,projectReports:2,opportunities:1,projects:1}, limitations:['Does not exercise real platform database transaction/transport or current-message identity integration','Production writes/readbacks remain necessary; stop on uncertainty']}; fs.writeFileSync(output,JSON.stringify(result,null,2)+'\n',{flag:'wx'}); console.log(JSON.stringify({ok:true,checksPassed:checks.length,orderedCommandCount:calls.length,productionCalls:0,productionWrites:0,simulatedOnly:true,negativeControl:result.negativeControl,expectedFinal:result.expectedFinal},null,2)); }catch(error){ const result={ok:false,step,errorCode:'S4_CLASSROOM_SIMULATION_STOPPED',failureCode:error.code??null,checkLine:error.stack?.match(/simulate-S4-classroom-next\.mjs:(\d+):/)?.[1]??null, commandsCompleted:calls.length,checksPassed:checks.length,productionCalls:0,productionWrites:0,simulatedOnly:true}; fs.writeFileSync(output,JSON.stringify(result,null,2)+'\n',{flag:'wx'});console.error(JSON.stringify(result,null,2));process.exitCode=1; }