| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889 |
- // Read-only final acceptance of the existing classroom completion.
- 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';
- // Preserve the first audit, which used an unverified operator timestamp.
- const output='work/live-S5-completion-readback-20261103-02.json';assert.equal(fs.existsSync(output),false);
- const base='KSF-真实资料-20261102-01',dir='work/S5-classroom-completion-20261103/';
- const rb=JSON.parse(fs.readFileSync(dir+'runbook.json','utf8'));
- const task=JSON.parse(fs.readFileSync(dir+'01-project-complete.template.json','utf8'));
- const s5=JSON.parse(fs.readFileSync('work/S5-classroom-next-20261103/03-project-S5-save.template.json','utf8'));
- const ct=JSON.parse(fs.readFileSync('work/S5-classroom-next-20261103/02-concept-save.template.json','utf8'));
- const prior=JSON.parse(fs.readFileSync('work/live-S5-readback-20261103-02.json','utf8'));
- const expectedEventId='MV6JyC6Jcx';
- const ptr=(className,objectId)=>({__type:'Pointer',className,objectId}),sha=x=>crypto.createHash('sha256').update(x).digest('hex');
- let step='fixed template',readCalls=0;const checks=[];
- const check=(name,fn)=>{step=name;fn();checks.push({name,passed:true});};
- const fetchRead=async(name,p)=>{readCalls++;const r=await invoke(name,p);assert.equal(r.http,200);assert.equal(r.body?.result?.ok,true);return r.body.result.data;};
- const read=async(className,objectId)=>{const r=await fetchRead('ksfGetRecord',{className,objectId});assert.equal(r.record?.objectId,objectId);return r.record;};
- const rows=async(name,where)=>{readCalls++;const r=await classRows(name,where);assert.ok(r.length<1000);return r;};
- 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('completion template and prior acceptance remain unchanged',()=>{
- assert.equal(sha(fs.readFileSync(dir+'01-project-complete.template.json')),rb.templateSha256);assert.equal(rb.templateSha256,'a74dc259ef795eae404f8da834780a5b6b2f2d19c4bf97c9eb047f521e6a46b2');
- assert.equal(task.toStage,'S5');assert.equal(task.state,'completed');assert.equal(task.sessionId,null);assert.equal(task.projectId,null);assert.equal(prior.ok,true);assert.equal(prior.projectId,rb.projectId);
- });
- step='production reads';
- const [status,session,completionEvent,artifact,source,concept,opportunity,ops,history,concepts,projects,opps]=await group([
- fetchRead('ksfGetWorkshopProject',{sessionId:rb.sessionId,projectId:rb.projectId}),read('KfSession',rb.sessionId),read('KfStageEvent',expectedEventId),
- read('KfArtifact',rb.s5ArtifactId),read('KfSource',rb.s5OutputSourceId),read('KfConcept',rb.conceptId),read('KfOpportunity',rb.opportunityId),
- rows('KsfOperation',{requestId:{$in:[...rb.steps.map(s=>s.requestId),rb.retainedS5SaveRequestIdDeviation.expected,rb.retainedS5SaveRequestIdDeviation.actual]}}),
- rows('KfArtifact',{workshopKey:base}),rows('KfConcept',{projectRef:ptr('KfProject',rb.projectId)}),rows('KfProject',{sessionRef:ptr('KfSession',rb.sessionId)}),rows('KfOpportunity',{sessionRef:ptr('KfSession',rb.sessionId)})
- ]);
- check('same classroom project is S5 completed with workshop status and owner unchanged',()=>{
- const p=status.project;assert.equal(p.objectId,rb.projectId);assert.equal(p.stageCode,'S5');assert.equal(p.progressState,'completed');assert.equal(p.status,'workshop');assert.equal(p.dataMode,'live');
- assert.equal(p.ownerSession,rb.sessionId);assert.deepEqual(p.sessionRef,ptr('KfSession',rb.sessionId));assert.deepEqual(p.opportunityRef,ptr('KfOpportunity',rb.opportunityId));
- assert.equal(session.stageCode,'S5');assert.equal(session.step,'completed');assert.equal(session.dataMode,'live');assert.equal(session.workshopKey,base);assert.deepEqual(p.meta.attribution,session.meta.attribution);
- assert.deepEqual(projects.map(x=>x.objectId),[rb.projectId]);assert.deepEqual(opps.map(x=>x.objectId),[rb.opportunityId]);assert.equal(opportunity.linkedProject,rb.projectId);assert.equal(opportunity.claimKind,'hypothesis');assert.equal(opportunity.status,'approved');
- });
- check('three original reports and one original concept remain with thirteen exact questions',()=>{
- assert.equal(status.project.artifactCount,3);assert.deepEqual(status.artifacts.map(x=>x.objectId).sort(),rb.projectArtifactIds.toSorted());assert.deepEqual(concepts.map(x=>x.objectId),[rb.conceptId]);
- assert.deepEqual(status.project.currentConceptRef,ptr('KfConcept',rb.conceptId));assert.equal(status.concept.objectId,rb.conceptId);assert.equal(status.concept.version,1);assert.deepEqual(status.project.meta.pendingQuestions,rb.pendingQuestions);assert.equal(rb.pendingQuestions.length,13);
- });
- check('eighth event completed matches returned ID, original seven IDs, note and pointers',()=>{
- assert.equal(status.events.length,8);assert.deepEqual(status.events.map(e=>e.sequence),[1,2,3,4,5,6,7,8]);assert.deepEqual(status.events.slice(0,7).map(e=>e.objectId),rb.existingEventIds);
- assert.deepEqual(status.events.slice(0,7).map(e=>e.eventType),prior.projectEvents.map(e=>e.eventType));const last=status.events[7];assert.equal(last.objectId,expectedEventId);assert.deepEqual(last,completionEvent);
- assert.equal(last.eventType,'completed');assert.equal(last.sequence,8);assert.equal(last.fromStage,'S5');assert.equal(last.toStage,'S5');assert.deepEqual(last.projectRef,ptr('KfProject',rb.projectId));assert.deepEqual(last.sessionRef,ptr('KfSession',rb.sessionId));assert.equal(last.note,task.note);assert.equal(last.artifactRef??null,null);
- assert.equal(last.externalKey,'workshop:event:'+rb.projectId+':8');
- });
- check('one completion write audit at exact prescribed request ID and no duplicate commit',()=>{
- const completed=ops.filter(o=>o.requestId===rb.steps[1].requestId);assert.equal(completed.length,1);const op=completed[0];assert.equal(op.operation,'workshop-transition');
- const receipt=op.receipt;assert.equal(receipt.projectId,rb.projectId);assert.equal(receipt.eventId,expectedEventId);assert.equal(receipt.stageCode,'S5');assert.equal(receipt.progressState,'completed');assert.equal(receipt.enterpriseApproval,false);
- assert.equal(ops.length,2);const actual=ops.find(o=>o.requestId===rb.retainedS5SaveRequestIdDeviation.actual);assert.equal(actual.receipt.artifactId,rb.s5ArtifactId);assert.equal(actual.receipt.sourceId,rb.s5OutputSourceId);
- assert.equal(ops.some(o=>o.requestId===rb.retainedS5SaveRequestIdDeviation.expected),false);
- });
- check('S5 report, generated source, five tables and thirteen questions fully unchanged',()=>{
- for(const k of ['title','rawText','version','stageCode','artifactType'])assert.deepEqual(artifact[k],s5[k]);assert.equal(source.rawText,s5.rawText);assert.equal(Array.from(source.rawText).length,3655);
- for(const k of ['chapters','evidence','method','sources','subtitle'])assert.deepEqual(artifact.report[k],s5.report[k]);assert.deepEqual(artifact.report.executive,['课堂过程成果,未经员工正式确认;待验证项不作为市场结论。',...s5.report.executive]);
- assert.deepEqual(artifact.meta.workshop.pendingQuestions,rb.pendingQuestions);assert.deepEqual(artifact.meta.workshop.sourceIds,s5.sourceIds);assert.equal(artifact.evidenceCount,4);assert.equal(artifact.reportSeriesKey,s5.taskKey);assert.ok(Number.isFinite(Date.parse(artifact.report.generatedAt)));assert.equal(artifact.dashboardState,'workshop');
- assert.equal(artifact.contentConfirmedAt??null,null);assert.equal(artifact.dashboardPublishedAt??null,null);assert.equal(artifact.meta.workshop.formalConfirmation,false);const a=ops.find(o=>o.requestId===rb.retainedS5SaveRequestIdDeviation.actual).receipt;assert.equal(a.formalReportPublished,false);assert.equal(a.employeeConfirmed,false);assert.equal(a.boardVisible,true);
- });
- check('concept v1 stays pending, unknown specifications and empty formula/process intact',()=>{
- assert.equal(concept.version,1);assert.equal(concept.reviewState,'pending');assert.equal(concept.recordState,'active');assert.equal(concept.dataMode,'live');assert.deepEqual(concept.projectRef,ptr('KfProject',rb.projectId));
- for(const k of ['name','targetAudience','usageScenario','specification','valueProposition','flavor','sellingPoints','productionSteps'])assert.deepEqual(concept[k],ct.concept[k]);assert.deepEqual(status.formulaItems,[]);assert.deepEqual(concept.productionSteps,[]);assert.equal(concept.pendingQuestions??null,null);
- });
- check('nine workshop reports and previous S1-S4 complete bodies retain original versions',()=>{
- assert.equal(history.length,9);assert.deepEqual(history.filter(x=>x.reportSeriesKey===s5.taskKey).map(x=>[x.objectId,x.version]),[[rb.s5ArtifactId,1]]);
- for(const [id,file]of [['XF4h-Z7l5o','S4-classroom-next-20261103/03-project-S3-save.template.json'],['U3_W7WDliP','S4-classroom-next-20261103/05-project-S4-save.template.json'],['G8IrkkDG7X','first-live-S1-v2-template.json'],['b7NOby5ys0','first-live-S2-template.json'],['oiNxX7o27j','first-live-S3-template.json'],['ZW1FB5zeHr','first-live-S3-v2-template.json']]){
- const a=history.find(x=>x.objectId===id),t=JSON.parse(fs.readFileSync('work/'+file,'utf8'));assert.ok(a);for(const k of ['title','rawText','version','stageCode'])assert.deepEqual(a[k],t[k]);assert.deepEqual(a.report.chapters,t.report.chapters);assert.deepEqual(a.report.evidence,t.report.evidence);assert.deepEqual(a.meta.workshop.pendingQuestions,t.pendingQuestions);
- }
- });
- step='anonymous public board projection';readCalls++;const board=await loadPublicBoard({url:'https://kangshifu.fmode.cn/api/functions',appId:'FKieRbIoGt_main'});
- check('anonymous public board has S5 completed project with unchanged concept and reports',()=>{
- const p=board.project.find(p=>p.objectId===rb.projectId);assert.ok(p);assert.equal(p.stageCode,'S5');assert.equal(p.progressState,'completed');assert.deepEqual(p.currentConceptRef,ptr('KfConcept',rb.conceptId));assert.deepEqual(board.artifact.filter(a=>a.projectId===rb.projectId||a.projectRef?.objectId===rb.projectId).map(a=>a.objectId).sort(),rb.projectArtifactIds.toSorted());
- 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 reportOfReadDeviation={step:7,expected:base+':S5:read:v1',actual:base+':portion-fit-project:S5:read:v1',basis:'Operator report of remote receipt inspection; remote receipts not independently obtained by Codex',independentlyVerified:false,cause:'Not established; shared prefix pattern is observed in reported receipts, not an explanation',resolution:'Record supplement; do not rerun read or save to replace the historical receipts.'};
- const r={ok:true,at:new Date().toISOString(),operation:'live-S5-classroom-completion-independent-readback',readOnly:true,businessWrites:0,readCalls,checksPassed:checks.length,checks,
- sessionId:rb.sessionId,projectId:rb.projectId,opportunityId:rb.opportunityId,conceptId:rb.conceptId,artifactId:rb.s5ArtifactId,outputSourceId:rb.s5OutputSourceId,eventId:expectedEventId,
- stageCode:'S5',progressState:'completed',status:'workshop',dataMode:'live',projectArtifactIds:rb.projectArtifactIds,conceptVersion:1,pendingQuestionCount:13,projectEventCount:8,
- projectEvents:status.events.map(e=>({eventId:e.objectId,eventType:e.eventType,sequence:e.sequence})),completionRequestId:rb.steps[1].requestId,committedCompletionAuditCount:1,completionExecutionWriteKeyMatches:true,
- formalReportPublished:false,employeeConfirmed:false,enterpriseApproval:false,oldReportsPreserved:true,browserVerified:false,reportGeneratedAt:artifact.report.generatedAt,
- initialAuditRetained:'work/live-S5-completion-readback-20261103.json',
- timestampCheckCorrection:{initialBasis:'Unverified earlier operator report of generatedAt',initialExpected:'2026-11-03T04:36:12.012Z',actual:artifact.report.generatedAt,claim:'Actual timestamp is valid; no claim it equals the unverified report or an uncaptured prior value. Full report body compared against fixed template.'},
- historicalS5RequestIdDeviations:[{...rb.retainedS5SaveRequestIdDeviation,basis:'Independently verified successful write audit',independentlyVerified:true},reportOfReadDeviation],
- notes:['Historical S5 report text describing in_progress remains an unchanged report of the earlier submission; live project now completed.','project-status pointer is project.currentConceptRef; top-level concept is the concept record.','dashboardPublishedAt is a persisted timestamp, currently null; formalReportPublished/employeeConfirmed booleans checked from successful save receipt.', 'Remote current-message identity, CLI invocation IDs, exit codes and file permissions remain operator-reported; no independent remote filesystem access.']};
- fs.writeFileSync(output,JSON.stringify(r,null,2)+'\n',{flag:'wx'});console.log(JSON.stringify({ok:true,checksPassed:checks.length,readCalls,projectId:rb.projectId,progressState:'completed',eventId:expectedEventId,businessWrites:0}));
- }catch(error){const r={ok:false,at:new Date().toISOString(),step,checksPassed:checks.length,readCalls,businessWrites:0,errorCode:'S5_COMPLETION_ACCEPTANCE_STOPPED',checkLine:error.stack?.match(/verify-live-S5-completion-readback\.mjs:(\d+):/)?.[1]??null};fs.writeFileSync(output,JSON.stringify(r,null,2)+'\n',{flag:'wx'});console.error(JSON.stringify(r));process.exitCode=1;}
|