| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 |
- // 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<code.length;i++){
- const c=code[i];if(quoted){if(escaped)escaped=false;else if(c==='\\')escaped=true;else if(c==='"')quoted=false;continue;}
- if(c==='"')quoted=true;else if(c==='{')depth++;else if(c==='}'&&--depth===0)return JSON.parse(code.slice(at,i+1));
- }
- throw Error('Embedded schema missing');
- }
- const modules=new Map();let data;
- for(const snapshot of audit.deployedCodeChecks){
- const code=fs.readFileSync(snapshot.file,'utf8');assert.equal(sha(code),snapshot.sha256);
- const runtime=embedded(code,snapshot.name);if(data)assert.deepEqual(runtime,data);else data=runtime;
- const impl=vm.runInNewContext(code+'\n;({createApplication,attachWorkshopHandlers});',{Buffer,structuredClone,URL,URLSearchParams,TextEncoder,TextDecoder},{timeout:5000});
- modules.set(snapshot.name,impl);proof.push({name:snapshot.name,sha256:snapshot.sha256});
- }
- const actor={id:'isolated-s5-service',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-s5-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(),rows=n=>{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;
- }
|