| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169 |
- // 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;i<code.length;i++){
- const ch=code[i];
- if(quoted){if(escaped)escaped=false;else if(ch==='\\')escaped=true;else if(ch==='"')quoted=false;continue;}
- if(ch==='"')quoted=true;
- else if(ch==='{')depth++;
- else if(ch==='}'&&--depth===0)return JSON.parse(code.slice(at,i+1));
- }
- throw Error('Embedded data missing');
- }
- const modules=new Map();
- let data;
- for(const name of ['ksfSaveWorkshopTask','ksfCreateProjectFromOpportunity','ksfTransitionProject','ksfGetWorkshopProject']){
- const code=fs.readFileSync('work/S4-deployed-'+name+'-20261103.js','utf8');
- const expected=audit.deployedCodeChecks.find(x=>x.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;
- }
|