verify-dedup-acceptance-readback.mjs 4.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. // Read-only verification of the completed sample run; never replays either import.
  2. import fs from 'node:fs';
  3. import assert from 'node:assert/strict';
  4. import crypto from 'node:crypto';
  5. import {request,classRows} from '../server/scripts/platform-client.mjs';
  6. import {TASK_KEY,dedupTask} from './workshop-dedup-acceptance.mjs';
  7. const sourceId='V6AS9Qf10d',materialId='SGOF-c5vTo';
  8. const rounds=[
  9. {round:'batch-a',jobId:'2MDXH_CzC5',operation:'created',created:1,unchanged:0,externalKey:TASK_KEY+':material:a',invocationId:'d23c55cc-bea2-4fb6-99a7-df53aca5d8e1',entryInvocationId:'fc6eb96b-6bd7-4aab-96f9-e54cd139d742'},
  10. {round:'batch-b',jobId:'L68OPSHuXW',operation:'unchanged',created:0,unchanged:1,externalKey:TASK_KEY+':material:b',invocationId:'4f984682-6fa7-4696-b29b-b7eb04026676',entryInvocationId:'df418c3a-fbf0-44c1-9b0f-b030b38e01b3'}
  11. ];
  12. const pointer=(className,objectId)=>({__type:'Pointer',className,objectId});
  13. const get=(className,id)=>request('GET','/classes/'+className+'/'+encodeURIComponent(id));
  14. try{
  15. const task=dedupTask('a'),contentHash=crypto.createHash('sha256').update(task.source.rawText).digest('hex');
  16. const [source,material,sources,materials,jobs]=await Promise.all([
  17. get('KfSource',sourceId),get('KfMaterial',materialId),
  18. classRows('KfSource',{contentHash}),classRows('KfMaterial',{name:task.records[0].data.name}),
  19. classRows('KfImportJob',{jobKey:{$regex:TASK_KEY}})
  20. ]);
  21. assert.equal(source.objectId,sourceId);assert.equal(material.objectId,materialId);
  22. for(const [k,v]of Object.entries(task.source))assert.deepEqual(source[k],v);
  23. assert.equal(source.contentHash,contentHash);
  24. for(const [k,v]of Object.entries(task.records[0].data))assert.deepEqual(material[k],v);
  25. assert.deepEqual(material.sourceRef,pointer('KfSource',sourceId));
  26. assert.equal(sources.length,1);assert.equal(sources[0].objectId,sourceId);
  27. assert.equal(materials.length,1);assert.equal(materials[0].objectId,materialId);
  28. assert.equal(material.externalKey,rounds[0].externalKey,'reuse preserves original business key');
  29. assert.equal(jobs.length,2);assert.deepEqual(jobs.map(j=>j.objectId).sort(),rounds.map(r=>r.jobId).sort());
  30. const result=[];
  31. for(const round of rounds){
  32. const job=jobs.find(j=>j.objectId===round.jobId),items=await classRows('KfImportItem',{jobRef:pointer('KfImportJob',round.jobId)});
  33. assert.equal(job.status,'succeeded');assert.equal(job.expectedCount,1);assert.equal(job.successCount,1);assert.equal(job.failureCount,0);
  34. assert.ok(job.jobKey.endsWith(':'+TASK_KEY+':'+round.round+':item:material'));
  35. assert.deepEqual(job.inputSourceRef,pointer('KfSource',sourceId));
  36. const counts=JSON.parse(job.summary).counts;
  37. assert.equal(counts.created,round.created);assert.equal(counts.unchanged,round.unchanged);
  38. assert.equal(counts.updated,0);assert.equal(counts.failed,0);
  39. assert.equal(items.length,1);const item=items[0];
  40. assert.equal(item.itemKey,'material');assert.equal(item.targetClass,'KfMaterial');assert.equal(item.targetId,materialId);
  41. assert.equal(item.status,'succeeded');assert.equal(item.operation,round.operation);assert.equal(item.businessKey,round.externalKey);
  42. result.push({round:round.round,jobId:round.jobId,sourceId,materialId,created:counts.created,unchanged:counts.unchanged,operation:item.operation});
  43. }
  44. assert.deepEqual(jobs[0].producer.agent,jobs[1].producer.agent);
  45. assert.equal(jobs[0].producer.agent.key,'agent-account:7c9f85363ca4c5a529a5b363308b065998924e2394e846145a5c62c904506d2e');
  46. const receipt={ok:true,operation:'sample-material-dedup-independently-verified',at:new Date().toISOString(),readOnly:true,businessWrites:0,taskKey:TASK_KEY,sourceId,materialId,rounds:result,exactSourceMatches:sources.length,exactMaterialMatches:materials.length,importJobCount:jobs.length,importItemCount:2,originalMaterialBusinessKeyPreserved:true,sameAgentAttribution:true,dataMode:'sample',actualPlatformCollectionPerformed:false,formalReportPublished:false,invocationEvidence:{basis:'user-forwarded-tool-receipts; IDs not independently authenticated by database',rounds:rounds.map(({round,invocationId,entryInvocationId})=>({round,invocationId,entryInvocationId}))}};
  47. fs.writeFileSync('work/dedup-acceptance-readback-20261102.json',JSON.stringify(receipt,null,2));
  48. console.log(JSON.stringify(receipt,null,2));
  49. }catch{
  50. console.error(JSON.stringify({ok:false,error:'DEDUP_READBACK_FAILED',message:'只读核验未通过;未输出原始异常、账号或凭据。'}));
  51. process.exitCode=1;
  52. }