verify-live-S1-v2-readback.mjs 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. // Read-only acceptance of the existing v2. Never calls start, save or import.
  2. import fs from 'node:fs';
  3. import assert from 'node:assert/strict';
  4. import crypto from 'node:crypto';
  5. import {invoke,classRows} from '../server/scripts/platform-client.mjs';
  6. import {loadPublicBoard} from '../src/services/workshop-data.js';
  7. const sessionId='btrFqkz0yw',artifactId='G8IrkkDG7X',previousArtifactId='cOm6c8gg7z',outputSourceId='SwVWicwjev';
  8. const template=JSON.parse(fs.readFileSync('work/first-live-S1-v2-template.json','utf8'));
  9. const oldTemplate=JSON.parse(fs.readFileSync('server/test/fixtures/first-live-S1-20261102.json','utf8'));
  10. const sha=x=>crypto.createHash('sha256').update(x).digest('hex');
  11. const read=async(className,objectId)=>{
  12. const r=await invoke('ksfGetRecord',{className,objectId});
  13. assert.equal(r.http,200);assert.equal(r.body?.result?.ok,true);return r.body.result.data.record;
  14. };
  15. try{
  16. const [session,artifact,previous,output,origins,history]=await Promise.all([
  17. read('KfSession',sessionId),read('KfArtifact',artifactId),read('KfArtifact',previousArtifactId),read('KfSource',outputSourceId),
  18. Promise.all(template.sourceIds.map(id=>read('KfSource',id))),
  19. classRows('KfArtifact',{workshopKey:'KSF-真实资料-20261102-01'})
  20. ]);
  21. assert.equal(session.dataMode,'live');assert.equal(artifact.sessionRef?.objectId,sessionId);assert.equal(artifact.ownerSession,sessionId);
  22. assert.equal(artifact.sourceRef?.objectId,outputSourceId);
  23. for(const field of ['title','rawText','version','stageCode','artifactType'])assert.deepEqual(artifact[field],template[field],field);
  24. assert.equal(artifact.dataMode,'live');assert.equal(artifact.dashboardState,'workshop');
  25. assert.equal(artifact.report.title,template.title);assert.ok(Number.isFinite(Date.parse(artifact.report.generatedAt)));
  26. assert.equal(artifact.report.executive[0],'课堂过程成果,未经员工正式确认;待验证项不作为市场结论。');
  27. assert.deepEqual(artifact.report.executive.slice(1),template.report.executive);
  28. for(const field of ['chapters','evidence'])assert.deepEqual(artifact.report[field],template.report[field],field);
  29. assert.deepEqual(artifact.meta.workshop.sourceIds,template.sourceIds);
  30. assert.deepEqual(artifact.meta.workshop.pendingQuestions,template.pendingQuestions);
  31. assert.equal(artifact.meta.workshop.formalConfirmation,false);
  32. assert.equal(artifact.contentConfirmedAt??null,null);assert.equal(artifact.dashboardPublishedAt??null,null);
  33. assert.equal(artifact.projectRef??null,null);
  34. assert.equal(output.rawText,template.rawText);assert.equal(output.contentHash,sha(template.rawText));assert.equal(output.dataMode,'live');
  35. let quotes=0;
  36. for(const entry of Object.values(artifact.report.evidence)){
  37. const origin=origins.find(o=>o.objectId===entry.sourceId);assert.ok(origin);assert.equal(origin.dataMode,'live');
  38. for(const q of entry.quotes){assert.ok(origin.rawText.includes(q.text));quotes++;}
  39. }
  40. assert.equal(quotes,5);assert.equal(artifact.sources.length,5);assert.equal(artifact.evidenceCount,5);
  41. for(const field of ['title','rawText','version','stageCode'])assert.deepEqual(previous[field],oldTemplate[field]);
  42. assert.equal(previous.reportSeriesKey,artifact.reportSeriesKey);assert.equal(artifact.reportSeriesKey,template.taskKey);
  43. assert.deepEqual(previous.meta.attribution,artifact.meta.attribution);assert.deepEqual(session.meta.attribution,artifact.meta.attribution);
  44. const participantAccountKey='participant-account:f83edd27dda7e0989a0d313c158314043fd129efb86b8f6a07db8140033c5708';
  45. const expected='participant:'+sha(JSON.stringify([session.workshopKey,participantAccountKey]));
  46. assert.equal(session.meta.attribution.participantKey,expected);
  47. const versions=history.filter(a=>a.reportSeriesKey===template.taskKey).sort((a,b)=>a.version-b.version);
  48. assert.deepEqual(versions.map(a=>[a.objectId,a.version]),[[previousArtifactId,1],[artifactId,2]]);
  49. const raw=await loadPublicBoard({url:'https://kangshifu.fmode.cn/api/functions',appId:'FKieRbIoGt_main'});
  50. for(const [id,version]of [[previousArtifactId,1],[artifactId,2]]){
  51. assert.ok(raw.artifact.some(a=>a.objectId===id&&a.version===version&&a.dataMode==='live'));
  52. }
  53. for(const privateValue of [session.meta.attribution.participantAccount?.accountId,session.meta.attribution.agentAccount?.accountId,session.meta.attribution.conversationKey].filter(Boolean)){
  54. assert.equal(JSON.stringify(raw).includes(privateValue),false);
  55. }
  56. const result={ok:true,operation:'live-S1-v2-independently-verified',at:new Date().toISOString(),readOnly:true,businessWrites:0,
  57. sessionId,artifactId,previousArtifactId,outputSourceId,reportUrl:'https://kangshifu.fmode.cn/report/'+artifactId,
  58. version:2,stageCode:'S1',dataMode:'live',dashboardState:'workshop',reportSeriesKey:artifact.reportSeriesKey,
  59. versions:versions.map(a=>({artifactId:a.objectId,version:a.version})),previousVersionRef:artifact.previousVersionRef?.objectId??null,
  60. versionIdentification:'reportSeriesKey + workshop attribution + version',derivedParticipantMatched:true,participantKeyPrefix:'participant',
  61. privateAttributionMatched:true,previousVersionPreserved:true,templateMatched:true,sourceIds:template.sourceIds,
  62. reportQuoteCount:quotes,pendingQuestionCount:template.pendingQuestions.length,publicBoardDataVerified:true,
  63. formalReportPublished:false,employeeConfirmed:false,browserVerified:false};
  64. fs.writeFileSync('work/live-S1-v2-readback-20261102.json',JSON.stringify(result,null,2)+'\n');console.log(JSON.stringify(result,null,2));
  65. }catch(e){
  66. console.error(JSON.stringify({ok:false,error:'LIVE_S1_V2_READBACK_STOPPED',checkLine:e.stack?.match(/verify-live-S1-v2-readback\.mjs:(\d+):/)?.[1],readOnly:true,businessWrites:0}));
  67. process.exitCode=1;
  68. }