// Independent S5 acceptance. Production reads only; never invokes business writes. 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'; // The initial strict audit is retained separately. Receipt inspection established // a real save-request deviation, which must stay explicit in final acceptance. const output='work/live-S5-readback-20261103-02.json';assert.equal(fs.existsSync(output),false); const dir='work/S5-classroom-next-20261103/',base='KSF-真实资料-20261102-01'; const runbook=JSON.parse(fs.readFileSync(dir+'runbook.json','utf8')); const template=JSON.parse(fs.readFileSync(dir+'03-project-S5-save.template.json','utf8')); const conceptTemplate=JSON.parse(fs.readFileSync(dir+'02-concept-save.template.json','utf8')); const ids={sessionId:runbook.sessionId,projectId:runbook.projectId,opportunityId:runbook.opportunityId, conceptId:'QO0uo8nisk',artifactId:'tLkz2MAoIw',outputSourceId:'5zsPtYFL1u',baselineArtifactId:'XF4h-Z7l5o',s4ArtifactId:'U3_W7WDliP'}; const actualSaveRequestId=base+':portion-fit-project:S5:save:v1'; const ptr=(className,objectId)=>({__type:'Pointer',className,objectId}),sha=x=>crypto.createHash('sha256').update(x).digest('hex'); const checks=[];let step='fixed template'; 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 group=async tasks=>{const rs=await Promise.allSettled(tasks);assert.deepEqual(rs.flatMap((r,i)=>r.status==='rejected'?[i]:[]),[],'Read failure indexes');return rs.map(r=>r.value);}; try{ check('unchanged S5 templates, four sources and thirteen questions',()=>{ assert.equal(sha(fs.readFileSync(dir+'03-project-S5-save.template.json')),'f8dc7d8a9f0b2a998f90453add9ca02f34a60cfc52be3bc592deab87d49ada8c'); assert.equal(sha(fs.readFileSync(dir+'02-concept-save.template.json')),'5c182b5c5733218577bca330df90b85b91d69ad1b6b1e5494bab5b06e2238e4d'); assert.equal([...template.rawText].length,3655);assert.equal(template.sourceIds.length,4);assert.equal(template.pendingQuestions.length,13); assert.deepEqual(conceptTemplate.concept.pendingQuestions,template.pendingQuestions); }); step='read S5 records, current project and committed audits'; const [artifact,source,concept,session,project,opp,statusResponse,operations,history,concepts,formulaItems,projects,opps, s4,baseline,s1,s2,s3v1,s3v2]=await group([ read('KfArtifact',ids.artifactId),read('KfSource',ids.outputSourceId),read('KfConcept',ids.conceptId),read('KfSession',ids.sessionId),read('KfProject',ids.projectId),read('KfOpportunity',ids.opportunityId), invoke('ksfGetWorkshopProject',{sessionId:ids.sessionId,projectId:ids.projectId}), classRows('KsfOperation',{requestId:{$in:[...runbook.steps.map(s=>s.requestId),actualSaveRequestId]}}),classRows('KfArtifact',{workshopKey:base}), classRows('KfConcept',{projectRef:ptr('KfProject',ids.projectId)}),classRows('KfFormulaItem',{conceptRef:ptr('KfConcept',ids.conceptId)}), classRows('KfProject',{sessionRef:ptr('KfSession',ids.sessionId)}),classRows('KfOpportunity',{sessionRef:ptr('KfSession',ids.sessionId)}), read('KfArtifact',ids.s4ArtifactId),read('KfArtifact',ids.baselineArtifactId),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; check('S5 artifact fields, complete report body and generated source match template',()=>{ for(const k of ['rawText','title','version','stageCode','artifactType'])assert.deepEqual(artifact[k],template[k]); assert.equal(artifact.reportSeriesKey,template.taskKey);assert.equal(artifact.reportType,'workshop_auxiliary');assert.equal(artifact.artifactSubtype,'classroom_task'); assert.equal(artifact.schemaVersion,'ksf.v0.7');assert.equal(artifact.recordState,'active');assert.equal(artifact.dataMode,'live');assert.equal(artifact.dashboardState,'workshop'); assert.equal(artifact.ownerSession,ids.sessionId);assert.equal(artifact.projectId,ids.projectId);assert.deepEqual(artifact.sessionRef,ptr('KfSession',ids.sessionId));assert.deepEqual(artifact.projectRef,ptr('KfProject',ids.projectId)); assert.deepEqual(artifact.sourceRef,ptr('KfSource',ids.outputSourceId));assert.equal(source.rawText,template.rawText);assert.equal(source.dataMode,'live'); assert.deepEqual(artifact.tags,['拌面','课堂过程成果']);assert.equal(artifact.evidenceCount,4);assert.equal(artifact.evidenceLevel,'supported'); assert.deepEqual(artifact.meta.workshop.sourceIds,template.sourceIds);assert.deepEqual(artifact.meta.workshop.pendingQuestions,template.pendingQuestions); for(const k of ['chapters','evidence','method','sources','subtitle'])assert.deepEqual(artifact.report[k],template.report[k]); assert.deepEqual(artifact.report.executive,['课堂过程成果,未经员工正式确认;待验证项不作为市场结论。',...template.report.executive]); assert.equal(artifact.report.title,template.title);assert.ok(Number.isFinite(Date.parse(artifact.report.generatedAt))); assert.equal(artifact.contentConfirmedAt??null,null);assert.equal(artifact.dashboardPublishedAt??null,null);assert.equal(artifact.meta.workshop.formalConfirmation,false); assert.equal(artifact.taskKey??null,null);assert.equal(artifact.conceptRef??null,null);assert.equal(artifact.previousVersionRef??null,null); }); check('concept v1 fields and unknown specification match, with no formula or fake process',()=>{ assert.equal(concept.version,1);assert.equal(concept.dataMode,'live');assert.equal(concept.schemaVersion,'ksf.v0.7');assert.equal(concept.recordState,'active');assert.equal(concept.reviewState,'pending'); assert.deepEqual(concept.projectRef,ptr('KfProject',ids.projectId));assert.equal(concept.externalKey,'workshop:concept:'+ids.projectId+':1');assert.equal(concept.previousVersionRef??null,null); for(const k of ['name','targetAudience','usageScenario','specification','valueProposition','flavor','sellingPoints','productionSteps'])assert.deepEqual(concept[k],conceptTemplate.concept[k]); assert.deepEqual(concept.productionSteps,[]);assert.deepEqual(formulaItems,[]);assert.deepEqual(status.formulaItems,[]); assert.deepEqual(project.currentConceptRef,ptr('KfConcept',ids.conceptId));assert.equal(status.concept.objectId,ids.conceptId);assert.deepEqual(status.concept.specification,concept.specification); assert.deepEqual(project.meta.pendingQuestions,template.pendingQuestions);assert.deepEqual(status.project.meta.pendingQuestions,template.pendingQuestions); assert.equal(concept.pendingQuestions??null,null);assert.equal(concept.proposalArtifactRef??null,null); }); const sections=artifact.report.chapters.flatMap(c=>c.sections),tables=sections.filter(s=>s.kind==='table'); check('five chapters, fifteen sections, five exact tables and thirteen visible pending rows',()=>{ assert.equal(artifact.report.chapters.length,5);assert.deepEqual(artifact.report.chapters.map(c=>c.sections.length),[4,4,2,2,3]);assert.equal(sections.length,15); assert.deepEqual(tables.map(t=>[t.title,t.data.cols.length,t.data.rows.length]),[['设计对照',4,4],['设计、观察与未知',3,8],['主引文',3,4],['继续、调整与暂停',4,4],['待补问题',3,13]]); const pending=tables.find(t=>t.title==='待补问题');assert.deepEqual(pending.data.rows.map(r=>r[1]),template.pendingQuestions); assert.deepEqual(pending.data.rows.map(r=>r[2]),[...Array(8).fill('原S4问题'),'承原1、4','承原4、6','承原7','承原8','承原3、6']); assert.deepEqual(template.pendingQuestions.slice(0,8),s4.meta.workshop.pendingQuestions); }); const evidenceIds={'consumer-cq-03':'_9mUml0wmh','consumer-cq-04':'2bFGP7axMo','public-p1':'xHGV_mRWcG','public-p2':'CeAEIwymTa'}; step='exact evidence and source binding reads';const entries=Object.entries(template.report.evidence); const evRows=await group(entries.flatMap(([k,e])=>[read('KfSource',e.sourceId),read('KfEvidence',evidenceIds[k])])); for(const [i,[k,e]]of entries.entries())check(k+' exact verbatim source quote and product remains unbound',()=>{ const origin=evRows[i*2],ev=evRows[i*2+1];assert.equal(origin.dataMode,'live');assert.equal(ev.dataMode,'live');assert.equal(ev.quote,e.quotes[0].text);assert.ok(origin.rawText.includes(ev.quote));assert.ok(template.rawText.includes(ev.quote)); assert.deepEqual(ev.sourceRef,ptr('KfSource',e.sourceId));assert.equal(ev.productRef??null,null);assert.equal(ev.quoteForm,'verbatim_excerpt');assert.equal(ev.verificationState,'archived_text_and_screenshot_checked'); assert.equal(ev.evidenceKind,k==='public-p1'?'retailer_listing_observation':'public_user_experience_self_report');assert.equal(origin.platform,k==='public-p1'?'suning':'smzdm'); }); check('one classroom project and hypothesis opportunity; current session stage S5',()=>{ assert.deepEqual(projects.map(p=>p.objectId),[ids.projectId]);assert.deepEqual(opps.map(p=>p.objectId),[ids.opportunityId]);assert.deepEqual(concepts.map(c=>c.objectId),[ids.conceptId]); assert.equal(project.stageCode,'S5');assert.equal(project.progressState,'in_progress');assert.equal(project.status,'workshop');assert.equal(project.dataMode,'live');assert.equal(project.artifactCount,3); assert.equal(project.ownerSession,ids.sessionId);assert.deepEqual(project.opportunityRef,ptr('KfOpportunity',ids.opportunityId));assert.equal(project.meta.projectKey,base+':portion-fit-project'); assert.equal(opp.claimKind,'hypothesis');assert.equal(opp.status,'approved');assert.equal(opp.linkedProject,ids.projectId);assert.equal(opp.version,1); assert.equal(session.stageCode,'S5');assert.equal(session.dataMode,'live');assert.equal(session.workshopKey,base);assert.deepEqual(artifact.meta.attribution,session.meta.attribution); assert.equal(artifact.agentId,session.meta.attribution.agentKey);assert.equal(artifact.agentName,'王刚001'); }); check('exact three project reports and seven sequential events with no completed event',()=>{ assert.equal(status.project.objectId,ids.projectId);assert.equal(status.project.stageCode,'S5');assert.equal(status.project.progressState,'in_progress'); assert.deepEqual(status.artifacts.map(a=>a.objectId).sort(),[ids.baselineArtifactId,ids.s4ArtifactId,ids.artifactId].sort()); assert.deepEqual(status.events.map(e=>e.sequence),[1,2,3,4,5,6,7]);assert.deepEqual(status.events.map(e=>e.eventType),['project_created','task_submitted','stage_changed','task_submitted','stage_changed','concept_saved','task_submitted']); assert.deepEqual(status.events.map(e=>e.objectId),['GGDTcg5YTy','RkkTTE_xeb','hOFghbcMsY','VlGFu_jv4b','0ya0a03U9G','qKnPnVkZA8','iUs3JwZ9Kf']); assert.equal(status.events[6].artifactRef.objectId,ids.artifactId);assert.equal(status.events[4].toStage,'S5'); }); check('three actual committed writes; S5 report save key deviates from runbook and original key remains unused',()=>{ const expected=[runbook.steps[1].requestId,runbook.steps[3].requestId,actualSaveRequestId];assert.equal(operations.length,3);assert.deepEqual(operations.map(o=>o.requestId).sort(),expected.sort()); assert.notEqual(actualSaveRequestId,runbook.steps[5].requestId);assert.equal(operations.some(o=>o.requestId===runbook.steps[5].requestId),false); const move=operations.find(o=>o.requestId===runbook.steps[1].requestId).receipt;assert.equal(move.projectId,ids.projectId);assert.equal(move.stageCode,'S5');assert.equal(move.eventId,'0ya0a03U9G');assert.equal(move.enterpriseApproval,false); const c=operations.find(o=>o.requestId===runbook.steps[3].requestId).receipt;assert.equal(c.conceptId,ids.conceptId);assert.equal(c.version,1);assert.deepEqual(c.pendingQuestions,template.pendingQuestions);assert.deepEqual(c.formulaItemIds,[]);assert.equal(c.enterpriseApproval,false); const a=operations.find(o=>o.requestId===actualSaveRequestId).receipt;assert.equal(a.artifactId,ids.artifactId);assert.equal(a.sourceId,ids.outputSourceId);assert.equal(a.opportunityId,null);assert.equal(a.projectId,ids.projectId);assert.equal(a.boardVisible,true);assert.equal(a.formalReportPublished,false);assert.equal(a.employeeConfirmed,false); }); check('single S5 version and nine reports across the existing workshop',()=>{ assert.equal(history.length,9);assert.deepEqual(history.filter(a=>a.reportSeriesKey===template.taskKey).map(a=>[a.objectId,a.version]),[[ids.artifactId,1]]); }); check('all previous S1-S4 content and evidence retained with their original question sets',()=>{ for(const [row,file]of [[baseline,'S4-classroom-next-20261103/03-project-S3-save.template.json'],[s4,'S4-classroom-next-20261103/05-project-S4-save.template.json'],[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.pendingQuestions,t.pendingQuestions);assert.deepEqual(row.meta.workshop.sourceIds,t.sourceIds); } }); step='public anonymous board projection';const board=await loadPublicBoard({url:'https://kangshifu.fmode.cn/api/functions',appId:'FKieRbIoGt_main'}); check('public S5 report and project stage/concept pointer are consistent without private identity',()=>{ const a=board.artifact.find(a=>a.objectId===ids.artifactId),p=board.project.find(p=>p.objectId===ids.projectId);assert.ok(a&&p); assert.deepEqual(a.report.chapters,template.report.chapters);assert.deepEqual(a.report.evidence,template.report.evidence);assert.equal(a.dashboardState,'workshop'); assert.equal(p.stageCode,'S5');assert.equal(p.progressState,'in_progress');assert.deepEqual(p.currentConceptRef,ptr('KfConcept',ids.conceptId)); for(const v of [session.meta.attribution.participantAccount?.accountId,session.meta.attribution.agentAccount?.accountId,session.meta.attribution.conversationKey].filter(Boolean))assert.equal(JSON.stringify(board).includes(v),false); }); const result={ok:true,at:new Date().toISOString(),operation:'live-S5-independent-readback',readOnly:true,businessWrites:0,checksPassed:checks.length,checks,...ids, reportUrl:'https://kangshifu.fmode.cn/report/'+ids.artifactId,stageCode:'S5',progressState:'in_progress',version:1,conceptVersion:1,reportSeriesKey:template.taskKey, templateMatched:true,conceptMatched:true,rawTextCodepoints:[...template.rawText].length,chapterCount:artifact.report.chapters.length,sectionCount:sections.length,tableCount:tables.length, reportTables:tables.map(t=>({title:t.title,columns:t.data.cols.length,rows:t.data.rows.length})),sourceIds:template.sourceIds,evidenceIds,evidenceCount:4,pendingQuestionCount:13, 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})), committedWriteCount:operations.length,committedWriteRequests:operations.map(o=>({requestId:o.requestId,operation:o.operation})),expectedReadCommandCount:5, conceptCount:concepts.length,formulaItemCount:formulaItems.length,workshopArtifactCount:history.length,formalReportPublished:false,employeeConfirmed:false,enterpriseApproval:false, oldReportsPreserved:true,publicBoardProjectionVerified:true,browserVerified:false, fieldLocations:{pendingQuestions:'KfProject.meta.pendingQuestions, KfArtifact.meta.workshop.pendingQuestions, concept save receipt',taskKey:'Stored as KfArtifact.reportSeriesKey',formulaItems:'Project-status top-level array; KfFormulaItem rows, not a KfProject property'}, corrections:{businessWriteCommands:3,readCommands:5,chapters:5,sections:15,tables:5,pendingTableThirdColumn:'First eight 原S4问题; final five 承原... references'}, executionConformsToFixedRequestIds:false,requestIdDeviation:{step:6,expected:runbook.steps[5].requestId,actual:actualSaveRequestId,expectedKeyCommitted:false, artifactId:ids.artifactId,sourceId:ids.outputSourceId,resolution:'Retain actual receipt and IDs; never resave under the unused expected key. No claim that every fixed request ID was followed.',cause:'Not established; remote input/CLI logs not obtained'}, initialStrictAuditRetained:'work/live-S5-readback-20261103.json', remoteFilesExitcodesAndInvocationIdsBasis:'Operator report, remote filesystem not independently read. CLI command counts follow runbook and committed write audit; not a claim about total database row mutations or hidden reads.'}; fs.writeFileSync(output,JSON.stringify(result,null,2)+'\n',{flag:'wx'}); console.log(JSON.stringify({ok:true,checksPassed:checks.length,artifactId:ids.artifactId,conceptId:ids.conceptId,projectStage:'S5',progressState:'in_progress',projectReports:3,projectEvents:7,committedWriteCommands:3,sections:15,tables:5,businessWrites:0},null,2)); }catch(error){ const result={ok:false,at:new Date().toISOString(),operation:'S5-independent-readback-stopped',step,checksPassed:checks.length, checkLine:error.stack?.match(/verify-live-S5-readback\.mjs:(\d+):/)?.[1]??null,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; }