| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- // Independent read-only acceptance of the completed S4 classroom chain.
- import fs from 'node:fs';
- import crypto from 'node:crypto';
- import assert from 'node:assert/strict';
- import {invoke,classRows} from '../server/scripts/platform-client.mjs';
- import {loadPublicBoard} from '../src/services/workshop-data.js';
- const output='work/live-S4-readback-20261103.json';
- assert.equal(fs.existsSync(output),false,'Preserve previous acceptance');
- const base='KSF-真实资料-20261102-01',sessionId='btrFqkz0yw';
- const ids={candidateArtifactId:'V2z4ox7Ehx',candidateSourceId:'CFjbZlMWp5',opportunityId:'QE9hjtCj8E',projectId:'S_CikxZeBs',
- baselineArtifactId:'XF4h-Z7l5o',baselineSourceId:'H_eFL1eT-d',artifactId:'U3_W7WDliP',outputSourceId:'y-KcU9Ijc0'};
- const ptr=(className,objectId)=>({__type:'Pointer',className,objectId});
- const sha=x=>crypto.createHash('sha256').update(x).digest('hex');
- const checks=[];
- let step='fixed template fingerprint';
- const check=(name,fn)=>{step=name;fn();checks.push({name,passed:true});};
- const read=async(className,objectId)=>{
- const r=await invoke('ksfGetRecord',{className,objectId});assert.equal(r.http,200);assert.equal(r.body?.result?.ok,true);
- const row=r.body.result.data.record;assert.equal(row?.objectId,objectId);return row;
- };
- const readGroup=async tasks=>{
- const all=await Promise.allSettled(tasks);assert.deepEqual(all.flatMap((r,i)=>r.status==='rejected'?[i]:[]),[],'Read failure indexes');
- return all.map(r=>r.value);
- };
- const dir='work/S4-classroom-next-20261103/';
- const templateNames=['01-candidate-save.template.json','03-project-S3-save.template.json','05-project-S4-save.template.json'];
- const templates=templateNames.map(n=>JSON.parse(fs.readFileSync(dir+n,'utf8')));
- const template=templates[2];
- const requestIds=[...fs.readFileSync('F:/文档/工作坊当前资料/47-发给王刚001-S4课堂项目与桌面评估.txt','utf8').matchAll(/requestId=(KSF-[^\s。;]+)/gu)].map(m=>m[1]);
- try{
- check('fixed S4 template and scope',()=>{
- assert.equal(sha(fs.readFileSync(dir+templateNames[2])),'fd4d5b19f9dc104ae7e31a0aebd56e578c24280ca1ba515bd583d8b070a57f7b');
- assert.equal(requestIds.length,12);assert.equal(template.sessionId,null);assert.equal(template.projectId,null);
- assert.equal(template.version,1);assert.equal(template.stageCode,'S4');assert.equal(template.artifactType,'assessment_report');
- assert.equal(template.taskKey,base+':S4');assert.equal([...template.rawText].length,1862);assert.equal(template.pendingQuestions.length,8);
- });
- step='read chain, status, history and write audit';
- const [session,candidate,candidateSource,opp,project,baseline,baselineSource,artifact,source,statusResponse,operations,history,opps,projects,
- s1,s2,s3v1,s3v2]=await readGroup([
- read('KfSession',sessionId),read('KfArtifact',ids.candidateArtifactId),read('KfSource',ids.candidateSourceId),read('KfOpportunity',ids.opportunityId),
- read('KfProject',ids.projectId),read('KfArtifact',ids.baselineArtifactId),read('KfSource',ids.baselineSourceId),
- read('KfArtifact',ids.artifactId),read('KfSource',ids.outputSourceId),invoke('ksfGetWorkshopProject',{sessionId,projectId:ids.projectId}),
- classRows('KsfOperation',{requestId:{$in:requestIds}}),classRows('KfArtifact',{workshopKey:base}),
- classRows('KfOpportunity',{sessionRef:ptr('KfSession',sessionId)}),classRows('KfProject',{sessionRef:ptr('KfSession',sessionId)}),
- read('KfArtifact','G8IrkkDG7X'),read('KfArtifact','b7NOby5ys0'),read('KfArtifact','oiNxX7o27j'),read('KfArtifact','ZW1FB5zeHr')
- ]);
- assert.equal(statusResponse.http,200);assert.equal(statusResponse.body?.result?.ok,true);const status=statusResponse.body.result.data;
- for(const [i,row,body,pid]of [[0,candidate,candidateSource,null],[1,baseline,baselineSource,ids.projectId],[2,artifact,source,ids.projectId]]){
- const t=templates[i];
- check(templateNames[i]+' saved fields and body equal fixed template',()=>{
- for(const key of ['title','rawText','version','stageCode','artifactType'])assert.deepEqual(row[key],t[key]);
- assert.equal(row.reportSeriesKey,t.taskKey);assert.equal(row.reportType,'workshop_auxiliary');assert.equal(row.artifactSubtype,'classroom_task');
- assert.equal(row.schemaVersion,'ksf.v0.7');assert.equal(row.recordState,'active');assert.equal(row.dataMode,'live');
- assert.equal(row.dashboardState,'workshop');assert.equal(row.ownerSession,sessionId);assert.deepEqual(row.sessionRef,ptr('KfSession',sessionId));
- assert.equal(row.projectId??null,pid);assert.deepEqual(row.projectRef??null,pid?ptr('KfProject',pid):null);
- assert.deepEqual(row.sourceRef,ptr('KfSource',body.objectId));assert.equal(body.rawText,t.rawText);assert.equal(body.dataMode,'live');
- assert.deepEqual(row.meta.workshop.sourceIds,t.sourceIds);assert.deepEqual(row.meta.workshop.pendingQuestions,t.pendingQuestions);
- for(const key of ['chapters','evidence','method','sources','subtitle'])assert.deepEqual(row.report[key],t.report[key]);
- assert.deepEqual(row.report.executive,['课堂过程成果,未经员工正式确认;待验证项不作为市场结论。',...t.report.executive]);
- assert.equal(row.report.title,t.title);assert.ok(Number.isFinite(Date.parse(row.report.generatedAt)));
- assert.equal(row.evidenceCount,4);assert.deepEqual(row.tags,['拌面','课堂过程成果']);
- assert.deepEqual(row.meta.attribution,session.meta.attribution);assert.equal(row.agentId,session.meta.attribution.agentKey);assert.equal(row.agentName,'王刚001');
- assert.equal(row.dashboardPublishedAt??null,null);assert.equal(row.contentConfirmedAt??null,null);assert.equal(row.meta.workshop.formalConfirmation,false);
- });
- }
- check('S4 three visible tables and eight pending rows',()=>{
- const tables=artifact.report.chapters.flatMap(c=>c.sections).filter(s=>s.kind==='table');
- assert.deepEqual(tables.map(t=>[t.title,t.data.cols.length,t.data.rows.length]),[['主引文',3,4],['桌面评估',4,5],['待补问题',2,8]]);
- assert.deepEqual(tables[2].data.rows,template.pendingQuestions.map((q,i)=>[String(i+1),q]));
- assert.deepEqual(artifact.report.chapters.map(c=>c.sections.length),[3,2,3,2]);
- });
- const evidenceIds={'consumer-cq-03':'_9mUml0wmh','consumer-cq-04':'2bFGP7axMo','public-p1':'xHGV_mRWcG','public-p2':'CeAEIwymTa'};
- step='read source and evidence bindings';
- const entries=Object.entries(template.report.evidence);
- const evidenceRows=await readGroup(entries.flatMap(([key,e])=>[read('KfSource',e.sourceId),read('KfEvidence',evidenceIds[key])]));
- for(const [index,[key,e]]of entries.entries())check(key+' exact quote, source and evidence identity',()=>{
- const origin=evidenceRows[index*2],evidence=evidenceRows[index*2+1];
- assert.equal(origin.dataMode,'live');assert.equal(origin.sourceKind,key==='public-p1'?'ecommerce':'social');assert.equal(origin.platform,key==='public-p1'?'suning':'smzdm');
- assert.deepEqual(evidence.sourceRef,ptr('KfSource',e.sourceId));assert.equal(evidence.productRef??null,null);assert.equal(evidence.quote,e.quotes[0].text);
- assert.equal(evidence.evidenceKind,key==='public-p1'?'retailer_listing_observation':'public_user_experience_self_report');
- assert.equal(evidence.verificationState,'archived_text_and_screenshot_checked');assert.equal(evidence.quoteForm,'verbatim_excerpt');
- assert.ok(origin.rawText.includes(e.quotes[0].text));assert.ok(template.rawText.includes(e.quotes[0].text));assert.equal(e.quotes.length,1);
- });
- check('single classroom opportunity and project retain hypothesis scope',()=>{
- assert.deepEqual(opps.map(x=>x.objectId),[ids.opportunityId]);assert.deepEqual(projects.map(x=>x.objectId),[ids.projectId]);
- assert.equal(opp.claimKind,'hypothesis');assert.equal(opp.status,'approved');assert.equal(opp.version,1);assert.equal(opp.opportunitySeriesKey,base+':portion-fit');
- assert.equal(opp.linkedProject,ids.projectId);assert.deepEqual(opp.linkedArtifacts,[ids.candidateArtifactId]);
- for(const key of ['title','demandSummary','targetAudience','usageScenario','fusionDefinition'])assert.equal(opp[key],templates[0].opportunity[key]);
- assert.equal(project.stageCode,'S4');assert.equal(project.progressState,'in_progress');assert.equal(project.status,'workshop');assert.equal(project.dataMode,'live');
- assert.equal(project.ownerSession,sessionId);assert.deepEqual(project.sessionRef,ptr('KfSession',sessionId));assert.deepEqual(project.opportunityRef,ptr('KfOpportunity',ids.opportunityId));
- assert.equal(project.meta.projectKey,base+':portion-fit-project');assert.equal(project.artifactCount,2);assert.equal(session.stageCode,'S4');
- });
- check('project status contains two artifacts, four ordered events and no concept',()=>{
- assert.equal(status.project.objectId,ids.projectId);assert.equal(status.project.stageCode,'S4');assert.equal(status.project.progressState,'in_progress');
- assert.deepEqual(status.artifacts.map(x=>x.objectId).sort(),[ids.baselineArtifactId,ids.artifactId].sort());
- assert.deepEqual(status.events.map(x=>x.eventType),['project_created','task_submitted','stage_changed','task_submitted']);
- assert.deepEqual(status.events.map(x=>x.sequence),[1,2,3,4]);assert.equal(status.events[0].objectId,'GGDTcg5YTy');assert.equal(status.events[2].objectId,'hOFghbcMsY');
- assert.equal(status.events[1].artifactRef.objectId,ids.baselineArtifactId);assert.equal(status.events[3].artifactRef.objectId,ids.artifactId);
- assert.equal(status.concept,null);assert.deepEqual(status.formulaItems,[]);
- });
- check('five committed writes, one per business key including recovered transition',()=>{
- const writes=[requestIds[0],requestIds[3],requestIds[5],requestIds[7],requestIds[9]];
- assert.equal(operations.length,5);assert.deepEqual(operations.map(x=>x.requestId).sort(),writes.sort());
- for(const [key,id]of [[requestIds[0],ids.candidateArtifactId],[requestIds[5],ids.baselineArtifactId],[requestIds[9],ids.artifactId]]){
- const receipt=operations.find(x=>x.requestId===key).receipt;assert.equal(receipt.artifactId,id);assert.equal(receipt.boardVisible,true);assert.equal(receipt.employeeConfirmed,false);assert.equal(receipt.formalReportPublished,false);
- }
- const created=operations.find(x=>x.requestId===requestIds[3]).receipt;assert.equal(created.projectId,ids.projectId);assert.equal(created.enterpriseApproval,false);
- const moved=operations.find(x=>x.requestId===requestIds[7]).receipt;assert.equal(moved.projectId,ids.projectId);assert.equal(moved.stageCode,'S4');assert.equal(moved.enterpriseApproval,false);
- });
- check('one S4 version, eight workshop reports and no S5 report',()=>{
- assert.equal(history.length,8);assert.deepEqual(history.filter(x=>x.reportSeriesKey===template.taskKey).map(x=>[x.objectId,x.version]),[[ids.artifactId,1]]);
- assert.equal(history.some(x=>x.stageCode==='S5'),false);
- });
- check('previous S1, S2 and both S3 reports still match their templates',()=>{
- for(const [row,file]of [[s1,'first-live-S1-v2-template.json'],[s2,'first-live-S2-template.json'],[s3v1,'first-live-S3-template.json'],[s3v2,'first-live-S3-v2-template.json']]){
- const t=JSON.parse(fs.readFileSync('work/'+file,'utf8'));
- for(const k of ['title','rawText','version','stageCode'])assert.deepEqual(row[k],t[k]);
- assert.deepEqual(row.report.chapters,t.report.chapters);assert.deepEqual(row.report.evidence,t.report.evidence);assert.deepEqual(row.meta.workshop.sourceIds,t.sourceIds);
- assert.deepEqual(row.meta.attribution,session.meta.attribution);
- }
- });
- step='anonymous public board projection';
- const board=await loadPublicBoard({url:'https://kangshifu.fmode.cn/api/functions',appId:'FKieRbIoGt_main'});
- check('anonymous board includes three new reports, project and selected opportunity',()=>{
- for(const [row,t]of [[candidate,templates[0]],[baseline,templates[1]],[artifact,template]]){
- const publicRow=board.artifact.find(x=>x.objectId===row.objectId);assert.ok(publicRow);assert.deepEqual(publicRow.report.chapters,t.report.chapters);assert.deepEqual(publicRow.report.evidence,t.report.evidence);
- }
- const p=board.project.find(x=>x.objectId===ids.projectId);assert.ok(p);assert.equal(p.stageCode,'S4');assert.equal(p.progressState,'in_progress');
- assert.ok(board.opportunity.some(x=>x.objectId===ids.opportunityId));
- for(const value of [session.meta.attribution.participantAccount?.accountId,session.meta.attribution.agentAccount?.accountId,session.meta.attribution.conversationKey].filter(Boolean))assert.equal(JSON.stringify(board).includes(value),false);
- });
- const result={ok:true,operation:'live-S4-independent-readback',at:new Date().toISOString(),readOnly:true,businessWrites:0,checksPassed:checks.length,checks,
- sessionId,...ids,reportUrl:'https://kangshifu.fmode.cn/report/'+ids.artifactId,version:1,stageCode:'S4',progressState:'in_progress',dataMode:'live',dashboardState:'workshop',
- reportSeriesKey:template.taskKey,sourceIds:template.sourceIds,evidenceIds,evidenceCount:4,pendingQuestionCount:8,rawTextCodepoints:[...template.rawText].length,
- reportTables:artifact.report.chapters.flatMap(c=>c.sections).filter(s=>s.kind==='table').map(s=>({title:s.title,columns:s.data.cols.length,rows:s.data.rows.length})),
- projectArtifactIds:status.artifacts.map(a=>a.objectId),projectEvents:status.events.map(e=>({eventId:e.objectId,eventType:e.eventType,sequence:e.sequence,artifactId:e.artifactRef?.objectId??null})),
- committedWriteRequests:operations.map(o=>({requestId:o.requestId,operation:o.operation})),committedWriteCount:operations.length,
- opportunityCount:opps.length,projectCount:projects.length,workshopArtifactCount:history.length,formalReportPublished:false,employeeConfirmed:false,enterpriseApproval:false,
- conceptPresent:false,templateMatched:true,oldReportsPreserved:true,publicBoardProjectionVerified:true,browserVerified:false,
- invalidStep08PreservedBasis:'Operator report of old USAGE logs. One committed transition now independently verified; historical failure is not erased.',
- remoteFilesAndExitcodesBasis:'Operator report only; remote filesystem and CLI exit codes not independently read',templateSha256:sha(fs.readFileSync(dir+templateNames[2]))};
- fs.writeFileSync(output,JSON.stringify(result,null,2)+'\n',{flag:'wx'});
- console.log(JSON.stringify({ok:true,checksPassed:checks.length,artifactId:ids.artifactId,projectId:ids.projectId,stageCode:'S4',progressState:'in_progress',projectArtifactCount:status.artifacts.length,projectEventCount:status.events.length,committedWriteCount:operations.length,businessWrites:0},null,2));
- }catch(error){
- const result={ok:false,operation:'live-S4-independent-readback-stopped',at:new Date().toISOString(),step,
- checkLine:error.stack?.match(/verify-live-S4-readback\.mjs:(\d+):/)?.[1]??null,checksPassed:checks.length,readOnly:true,businessWrites:0};
- fs.writeFileSync(output,JSON.stringify(result,null,2)+'\n',{flag:'wx'});console.error(JSON.stringify(result,null,2));process.exitCode=1;
- }
|