// Captured deployed code, synthetic actor/session and RAM only. No network. 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/S5-classroom-isolated-simulation-20261103.json';assert.equal(fs.existsSync(output),false); const folder='work/S5-classroom-next-20261103/'; const audit=JSON.parse(fs.readFileSync('work/S5-classroom-live-preflight-20261103.json','utf8'));assert.equal(audit.ok,true); const runbook=JSON.parse(fs.readFileSync(folder+'runbook.json','utf8')); const tasks=Object.fromEntries(['01-project-S5-progress.template.json','02-concept-save.template.json','03-project-S5-save.template.json'].map(n=>[n,JSON.parse(fs.readFileSync(folder+n,'utf8'))])); const sha=x=>crypto.createHash('sha256').update(x).digest('hex'); const copy=structuredClone; const checks=[],calls=[],proof=[];let step='load captured definitions'; const check=(name,fn)=>{step=name;fn();checks.push({name,passed:true});}; function embedded(code,name){ const marker='runDatabaseFunction('+JSON.stringify(name)+',request,response,',at=code.lastIndexOf(marker)+marker.length;assert.ok(at>=marker.length); let depth=0,quoted=false,escaped=false; for(let i=at;i{if(!db.has(n))db.set(n,[]);return db.get(n);};let sequence=0; const equal=(a,b)=>JSON.stringify(a??null)===JSON.stringify(b??null),ptr=(className,objectId)=>({__type:'Pointer',className,objectId}); const store={ async transaction(fn){const before=copy(db);try{return await fn(this);}catch(e){db.clear();for(const [k,v]of before)db.set(k,v);throw e;}}, async lock(){},async get(name,id){return copy(rows(name).find(x=>x.objectId===id)??null);}, async list(name,{where={},afterId,limit=1000}={}){return copy(rows(name).filter(r=>Object.entries(where).every(([k,v])=>equal(r[k],v))).filter(r=>!afterId||r.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 r={...copy(input),objectId:'simS5'+String(++sequence).padStart(5,'0'),createdAt:new Date().toISOString(),updatedAt:new Date().toISOString()};rows(name).push(r);return copy(r);}, async update(name,id,input){for(const key of Object.keys(input))assert.ok(data.tables[name][key],name+'.'+key);const r=rows(name).find(x=>x.objectId===id);assert.ok(r);Object.assign(r,copy(input),{updatedAt:new Date().toISOString()});return copy(r);} }; const sessionId='simS5Session',projectId='simS5Project',baselineId='simS3Baseline',s4Id='simS4Assessment'; rows('KfSession').push({objectId:sessionId,dataMode:'live',workshopKey:'KSF-真实资料-20261102-01',stageCode:'S4',studentName:attribution.participantName,groupId:'',meta:{ownerId:actor.id,attribution}}); rows('KfProject').push({objectId:projectId,dataMode:'live',workshopKey:'KSF-真实资料-20261102-01',stageCode:'S4',stage:'S4 机会验证评估',status:'workshop',progressState:'in_progress', ownerSession:sessionId,sessionRef:ptr('KfSession',sessionId),artifactCount:2,groupId:'',meta:{ownerId:actor.id,participantKey:attribution.participantKey,pendingQuestions:[]}}); rows('KfArtifact').push({objectId:baselineId,stageCode:'S3',projectRef:ptr('KfProject',projectId)},{objectId:s4Id,stageCode:'S4',projectRef:ptr('KfProject',projectId)}); for(const [i,type]of ['project_created','task_submitted','stage_changed','task_submitted'].entries())rows('KfStageEvent').push({objectId:'simOldEvent'+i,eventType:type,sequence:i+1,projectRef:ptr('KfProject',projectId)}); const publicRows=JSON.parse(fs.readFileSync('work/S4-public-record-inspection-20261103.json','utf8')).records; for(const r of publicRows.filter(r=>r.className==='KfSource'))rows('KfSource').push({...copy(r.record),meta:{ownerId:actor.id}}); const prior=JSON.parse(fs.readFileSync('work/first-live-S3-v2-template.json','utf8')); for(const [key,file]of [['consumer-cq-03','cq-03'],['consumer-cq-04','cq-04']]){ const input=JSON.parse(fs.readFileSync('work/consumer-quote-inputs-20261102/'+file+'.input.json','utf8')); rows('KfSource').push({...copy(input.source),objectId:prior.report.evidence[key].sourceId,meta:{ownerId:actor.id}}); } const originalSources=copy(rows('KfSource')),originalArtifacts=copy(rows('KfArtifact')); const apps=new Map(); for(const [name,impl]of modules){const app=impl.createApplication({store,crypto,Parse:{},data,settings:{signingSecret:'isolated-s5-only'},files:{}});impl.attachWorkshopHandlers(app,{store,data});apps.set(name,app);} const call=async(name,params)=>copy((await (apps.get(name)||apps.get('ksfSaveWorkshopTask')).dispatch(name,copy(params),actor)).data); const execute=async(i,input)=>{const s=runbook.steps[i],r=await runWorkshop(s.command,copy(input),{requestId:s.requestId,agentProfile,call});calls.push({step:s.step,command:s.command,requestId:s.requestId,ok:true});return r;}; const bind=name=>{const t=tasks[name];assert.equal(t.sessionId,null);assert.equal(t.projectId,null);return {...copy(t),sessionId,projectId};}; const statusInput={sessionId,projectId}; try{ step='negative: no S4 artifact'; rows('KfArtifact').splice(1,1);let missing; try{await call('ksfTransitionProject',{...statusInput,requestId:'isolated-s5-missing-baseline',toStage:'S5',state:'in_progress',note:'local negative control'});}catch(e){missing=e.code;} check('missing project S4 report prevents transition with no side effects',()=>{ assert.equal(missing,'STAGE_REQUIREMENTS_NOT_MET');assert.equal(rows('KfProject')[0].stageCode,'S4');assert.equal(rows('KfStageEvent').length,4);assert.equal(rows('KsfOperation').length,0); }); rows('KfArtifact').push(copy(originalArtifacts[1])); const before=await execute(0,statusInput); check('initial S4 status matches precondition',()=>{assert.equal(before.project.stageCode,'S4');assert.equal(before.artifacts.length,2);assert.equal(before.events.length,4);assert.equal(before.concept,null);}); const move=await execute(1,bind('01-project-S5-progress.template.json')); const moved=await execute(2,statusInput); check('S5 in progress transition adds only one stage event',()=>{ assert.equal(move.stageCode,'S5');assert.equal(move.progressState,'in_progress');assert.equal(move.enterpriseApproval,false); assert.equal(moved.project.stageCode,'S5');assert.equal(moved.events.length,5);assert.equal(moved.artifacts.length,2);assert.equal(moved.concept,null); }); step='negative: reject skipped initial concept version';let skipped; try{await call('ksfSaveWorkshopConcept',{...bind('02-concept-save.template.json'),version:2,requestId:'isolated-s5-skipped-version'});}catch(e){skipped=e.code;} check('first concept must be v1; rejected version creates no partial concept',()=>{assert.equal(skipped,'VERSION_CONFLICT');assert.equal(rows('KfConcept').length,0);assert.equal(rows('KfStageEvent').length,5);}); const c=await execute(3,bind('02-concept-save.template.json')); const conceptStatus=await execute(4,statusInput),t=tasks['02-concept-save.template.json']; check('concept v1 preserves explicit unknowns, empty arrays and 13 project questions',()=>{ assert.equal(c.version,1);assert.equal(c.enterpriseApproval,false);assert.deepEqual(c.formulaItemIds,[]);assert.deepEqual(c.pendingQuestions,t.concept.pendingQuestions); assert.equal(conceptStatus.concept.objectId,c.conceptId);assert.equal(conceptStatus.concept.version,1);assert.equal(conceptStatus.concept.dataMode,'live'); assert.deepEqual(conceptStatus.concept.projectRef,ptr('KfProject',projectId));assert.deepEqual(conceptStatus.project.currentConceptRef,ptr('KfConcept',c.conceptId)); for(const k of ['name','targetAudience','usageScenario','specification','valueProposition','flavor','sellingPoints','productionSteps'])assert.deepEqual(conceptStatus.concept[k],t.concept[k]); assert.deepEqual(conceptStatus.project.meta.pendingQuestions,t.concept.pendingQuestions);assert.equal('pendingQuestions' in conceptStatus.concept,false); assert.deepEqual(conceptStatus.formulaItems,[]);assert.equal(conceptStatus.events.length,6);assert.equal(conceptStatus.events[5].eventType,'concept_saved'); }); const reportInput=bind('03-project-S5-save.template.json'),saved=await execute(5,reportInput); const record=(await execute(6,{className:'KfArtifact',objectId:saved.artifactId})).record; check('S5 report body, four quotes, five tables and pending questions persist',()=>{ assert.equal(saved.artifactId,record.objectId);assert.equal(saved.projectId,projectId);assert.equal(saved.opportunityId,null);assert.equal(saved.boardVisible,true); assert.equal(saved.formalReportPublished,false);assert.equal(saved.employeeConfirmed,false); assert.equal(record.artifactType,'concept');assert.equal(record.reportType,'workshop_auxiliary');assert.equal(record.artifactSubtype,'classroom_task'); assert.equal(record.rawText,reportInput.rawText);assert.equal(record.reportSeriesKey,reportInput.taskKey);assert.equal(record.stageCode,'S5');assert.equal(record.version,1);assert.equal(record.evidenceCount,4); assert.deepEqual(record.projectRef,ptr('KfProject',projectId));assert.equal(record.projectId,projectId);assert.deepEqual(record.meta.workshop.sourceIds,reportInput.sourceIds); assert.deepEqual(record.meta.workshop.pendingQuestions,reportInput.pendingQuestions); for(const k of ['chapters','evidence','method','sources','subtitle'])assert.deepEqual(record.report[k],reportInput.report[k]); assert.deepEqual(record.report.executive.slice(1),reportInput.report.executive);assert.equal(record.report.executive[0],'课堂过程成果,未经员工正式确认;待验证项不作为市场结论。'); assert.equal('conceptRef' in record,false);assert.equal(rows('KfSource').find(s=>s.objectId===saved.sourceId).rawText,reportInput.rawText); }); const final=await execute(7,statusInput); check('final S5 in progress, three reports, one concept and seven ordered events',()=>{ assert.equal(final.project.stageCode,'S5');assert.equal(final.project.progressState,'in_progress');assert.equal(final.project.artifactCount,3); assert.equal(final.artifacts.length,3);assert.equal(final.concept.objectId,c.conceptId);assert.equal(final.concept.version,1);assert.equal(final.formulaItems.length,0); assert.deepEqual(final.project.meta.pendingQuestions,reportInput.pendingQuestions); assert.deepEqual(final.events.map(e=>e.eventType),['project_created','task_submitted','stage_changed','task_submitted','stage_changed','concept_saved','task_submitted']); assert.deepEqual(final.events.map(e=>e.sequence),[1,2,3,4,5,6,7]); }); check('eight commands, three writes and no duplicate entities or old record changes',()=>{ assert.equal(calls.length,8);assert.deepEqual(calls.map(c=>c.requestId),runbook.steps.map(s=>s.requestId)); assert.equal(rows('KsfOperation').length,3);assert.equal(rows('KfConcept').length,1);assert.equal(rows('KfProject').length,1); assert.equal(rows('KfSource').length,originalSources.length+1);assert.equal(rows('KfArtifact').length,3); assert.deepEqual(rows('KfArtifact').filter(a=>originalArtifacts.some(o=>o.objectId===a.objectId)),originalArtifacts); assert.deepEqual(rows('KfSource').filter(s=>originalSources.some(o=>o.objectId===s.objectId)),originalSources); for(const cls of ['KfFormulaItem','KfProduct','KfSku','KfMaterial','KfProcess','KfOpportunity'])assert.equal(rows(cls).length,0); }); const result={ok:true,at:new Date().toISOString(),scope:'Local RAM only, current captured deployed handler code/runtime schema, synthetic identity/session/project and accepted source text', simulatedOnly:true,productionCalls:0,productionWrites:0,checksPassed:checks.length,checks,calls,deployedCode:proof,templatesModified:false,browserVerified:false, negativeControls:['S5 refused without project S4 artifact','Initial concept version 2 refused without partial write'],expectedFinal:runbook.expectedFinal, limits:['No current-message identity or real platform database transport/transaction test.','Production readbacks remain required; stop rules apply.']}; fs.writeFileSync(output,JSON.stringify(result,null,2)+'\n',{flag:'wx'}); console.log(JSON.stringify({ok:true,checksPassed:checks.length,orderedCommandCount:calls.length,negativeControls:2,productionCalls:0,productionWrites:0,expectedFinal:runbook.expectedFinal},null,2)); }catch(error){ const result={ok:false,at:new Date().toISOString(),step,errorCode:'S5_SIMULATION_STOPPED',failureCode:error.code??null, checkLine:error.stack?.match(/simulate-S5-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; }