verify-live-S1-v2-input.mjs 4.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. // Read-only verification of the recovered material and the next S1 report input.
  2. import fs from 'node:fs';
  3. import crypto from 'node:crypto';
  4. import assert from 'node:assert/strict';
  5. import {invoke,classRows} from '../server/scripts/platform-client.mjs';
  6. const file='work/first-live-S1-v2-template.json';
  7. const task=JSON.parse(fs.readFileSync(file,'utf8'));
  8. const sessionId='btrFqkz0yw',previousArtifactId='cOm6c8gg7z';
  9. const productId='-KbX0Agka-',sourceId='McdwhD7KQq',categoryId='Mc9FJpryn4';
  10. const evidenceIds=['Cjt9C6V-sF','PkIMIDs38u','za6VI7Xdjh'];
  11. const requestId='KSF-真实资料-20261102-01:S1:save:v2';
  12. const hash=x=>crypto.createHash('sha256').update(x).digest('hex');
  13. const pointer=(className,objectId)=>({__type:'Pointer',className,objectId});
  14. const get=async(className,objectId)=>{
  15. const r=await invoke('ksfGetRecord',{className,objectId});
  16. assert.equal(r.http,200);assert.equal(r.body?.result?.ok,true);
  17. return r.body.result.data.record;
  18. };
  19. try{
  20. assert.equal(task.sessionId,null);assert.equal(task.version,2);assert.equal(task.stageCode,'S1');
  21. assert.equal(task.taskKey,'KSF-真实资料-20261102-01:S1');
  22. assert.deepEqual(task.sourceIds,['BgUB6dnxN1','SFiKA0D7n9',sourceId]);
  23. assert.equal(task.pendingQuestions.length,6);assert.equal(task.opportunity,undefined);assert.equal(task.projectId,undefined);
  24. const [session,previous,product,category,origins,evidence,history,operations,products]=await Promise.all([
  25. get('KfSession',sessionId),get('KfArtifact',previousArtifactId),get('KfProduct',productId),get('KfCategory',categoryId),
  26. Promise.all(task.sourceIds.map(id=>get('KfSource',id))),Promise.all(evidenceIds.map(id=>get('KfEvidence',id))),
  27. classRows('KfArtifact',{workshopKey:'KSF-真实资料-20261102-01'}),
  28. classRows('KsfOperation',{operation:'workshop-task',requestId}),
  29. classRows('KfProduct',{sourceRef:pointer('KfSource',sourceId)})
  30. ]);
  31. assert.equal(session.dataMode,'live');assert.equal(previous.version,1);assert.equal(previous.reportSeriesKey,task.taskKey);
  32. assert.equal(previous.sessionRef?.objectId,sessionId);assert.equal(previous.dashboardState,'workshop');
  33. assert.equal(session.meta.attribution.agentKey,previous.meta.attribution.agentKey);
  34. assert.equal(session.meta.attribution.participantKey,previous.meta.attribution.participantKey);
  35. assert.equal(history.filter(a=>a.reportSeriesKey===task.taskKey&&a.version===2).length,0,'V2_ALREADY_EXISTS');
  36. assert.equal(operations.length,0,'REQUEST_ALREADY_EXISTS');
  37. assert.equal(category.code,'KSF-RESEARCH-INSTANT-MIXED-NOODLES-V1');
  38. assert.equal(category.updatedAt,'2026-11-02T07:12:09.338Z');
  39. assert.equal(product.categoryRef?.objectId,categoryId);assert.equal(product.sourceRef?.objectId,sourceId);
  40. assert.equal(product.productLevel,'product_line');assert.equal(product.isOwnBrand,true);assert.equal(product.dataMode,'live');
  41. assert.equal(products.length,1);assert.equal(products[0].objectId,productId);
  42. let quoteCount=0;
  43. for(const entry of Object.values(task.report.evidence)){
  44. const origin=origins.find(o=>o.objectId===entry.sourceId);assert.ok(origin);assert.equal(origin.dataMode,'live');
  45. assert.equal(hash(origin.rawText),origin.contentHash);
  46. for(const q of entry.quotes){assert.ok(origin.rawText.includes(q.text));quoteCount++;}
  47. }
  48. assert.equal(quoteCount,5);
  49. const ksf=origins.find(o=>o.objectId===sourceId);
  50. const reportQuotes=task.report.evidence['ksf-brand-statement'].quotes;
  51. evidence.forEach((e,i)=>{
  52. assert.equal(e.objectId,evidenceIds[i]);assert.equal(e.sourceRef?.objectId,sourceId);assert.equal(e.productRef?.objectId,productId);
  53. assert.equal(e.evidenceKind,'official_brand_statement');assert.equal(e.dataMode,'live');
  54. assert.equal(e.quote,reportQuotes[i].text);
  55. assert.equal(Array.from(ksf.rawText).slice(e.locator.startCharacter,e.locator.endCharacter).join(''),e.quote);
  56. });
  57. const result={ok:true,operation:'live-S1-v2-input-readonly-verified',at:new Date().toISOString(),readOnly:true,businessWrites:0,
  58. sessionId,previousArtifactId,taskKey:task.taskKey,nextVersion:2,requestId,requestHistoryMatches:operations.length,
  59. productId,categoryId,sourceIds:task.sourceIds,evidenceIds,linkedProductCount:products.length,reportQuoteCount:quoteCount,
  60. previousVersionPreserved:true,privateAttributionMatched:true,templateSha256:hash(fs.readFileSync(file)),
  61. targetSaved:false,browserVerified:false};
  62. fs.writeFileSync('work/first-live-S1-v2-input-check.json',JSON.stringify(result,null,2)+'\n');
  63. console.log(JSON.stringify(result,null,2));
  64. }catch{
  65. console.error(JSON.stringify({ok:false,error:'LIVE_S1_V2_INPUT_CHECK_STOPPED',readOnly:true,businessWrites:0}));
  66. process.exitCode=1;
  67. }