// Read-only production verification. Uses ksfGetRecord and Parse GETs; no import/save calls. 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'; const taskKey='KSF-消费者原话-20261102-01'; const directory='work/consumer-quote-inputs-20261102/'; const originals='work/consumer-research-codex-20261102/delivery/'; const archiveFile='work/consumer-quotes-codex-20261102.zip'; const output='work/consumer-quotes-readback-20261102-complete.json'; const imports=[ {key:'cq-01',sourceId:'2mzBadmvws',evidenceId:'FO47R0yR8D',jobId:'Li-h4TsoS2'}, {key:'cq-02',sourceId:'Pi04eTwTQ3',evidenceId:'rD8XZgV1Xo',jobId:'KGKMHZl8Mm'}, {key:'cq-03',sourceId:'m1OQx9aynA',evidenceId:'_9mUml0wmh',jobId:'plOFt3HvXJ'}, {key:'cq-04',sourceId:'QdeMcyTTjX',evidenceId:'2bFGP7axMo',jobId:'MVmsZHRZbj'}, ]; const sha=value=>crypto.createHash('sha256').update(value).digest('hex'); const pointer=(className,objectId)=>({__type:'Pointer',className,objectId}); // Parse REST serializes Date fields as envelopes; ksfGetRecord returns ISO strings. // Compare the same date value while retaining pointers and every other field. const normalizeDates=value=>{ if(Array.isArray(value))return value.map(normalizeDates); if(value&&typeof value==='object'){ if(value.__type==='Date'&&typeof value.iso==='string')return value.iso; return Object.fromEntries(Object.entries(value).map(([key,item])=>[key,normalizeDates(item)])); } return value; }; const read=async(className,objectId)=>{ const response=await invoke('ksfGetRecord',{className,objectId}); assert.equal(response.http,200);assert.equal(response.body?.result?.ok,true); const row=response.body.result.data.record;assert.ok(row);assert.equal(row.objectId,objectId); return row; }; let check='local fixed inputs',cq04Verified=false; const reports=[]; assert.equal(fs.existsSync(output),false,'Retain existing verification'); try{ const receipt=JSON.parse(fs.readFileSync('work/consumer-quote-import-package-receipt-20261102.json','utf8')); const archive=fs.readFileSync(archiveFile);assert.equal(archive.length,1287263);assert.equal(sha(archive),receipt.originalArchiveSha256); const tasks=new Map(imports.map(ids=>{ const bytes=fs.readFileSync(directory+ids.key+'.input.json');assert.equal(sha(bytes),receipt.inputHashes[ids.key+'.input.json']); return[ids.key,JSON.parse(bytes)]; })); check='cq-04 precise evidence read'; const cq04=await read('KfEvidence',imports[3].evidenceId),last=tasks.get('cq-04'); for(const[field,value]of Object.entries(last.records[0].data))assert.deepEqual(cq04[field]??null,value,'cq-04 evidence '+field); assert.deepEqual(cq04.sourceRef,pointer('KfSource',imports[3].sourceId)); assert.equal(cq04.productRef??null,null);assert.equal(cq04.recordState,'active'); cq04Verified=true; console.log(JSON.stringify({step:'cq-04 precise read',ok:true,evidenceId:cq04.objectId,allFixedFieldsMatched:true})); check='read source, evidence, job and item groups'; const sourcePointers=imports.map(x=>pointer('KfSource',x.sourceId)); const jobPointers=imports.map(x=>pointer('KfImportJob',x.jobId)); const results=await Promise.allSettled([ classRows('KfSource',{sourceUrl:{$in:[...tasks.values()].map(x=>x.source.sourceUrl)}}), classRows('KfEvidence',{sourceRef:{$in:sourcePointers}}), classRows('KfImportJob',{inputSourceRef:{$in:sourcePointers}}), classRows('KfImportItem',{jobRef:{$in:jobPointers}}), classRows('KfProduct',{sourceRef:{$in:sourcePointers}}), classRows('KfArtifact',{$or:[{sourceRef:{$in:sourcePointers}},{workshopKey:'KSF-真实资料-20261102-01',stageCode:'S2'}]}), read('KfArtifact','G8IrkkDG7X'), ]); const failures=results.map((r,index)=>r.status==='rejected'?index:null).filter(x=>x!==null); assert.deepEqual(failures,[],'Read group failed; indexes only, no raw exception or credentials'); const [sources,evidences,jobs,items,products,newArtifacts,s1]=results.map(r=>normalizeDates(r.value)); assert.equal(sources.length,4);assert.equal(evidences.length,4);assert.equal(jobs.length,4);assert.equal(items.length,4); assert.equal(products.length,0);assert.equal(newArtifacts.length,0); for(const ids of imports){ check=ids.key+' fixed fields, reference, history and archive'; const input=tasks.get(ids.key),row=input.records[0]; const source=sources.find(x=>x.objectId===ids.sourceId),evidence=evidences.find(x=>x.objectId===ids.evidenceId); const job=jobs.find(x=>x.objectId===ids.jobId),item=items.find(x=>x.jobRef?.objectId===ids.jobId); assert.ok(source);assert.ok(evidence);assert.ok(job);assert.ok(item); for(const[field,value]of Object.entries(input.source))assert.deepEqual(source[field]??null,value,ids.key+' source '+field); for(const[field,value]of Object.entries(row.data))assert.deepEqual(evidence[field]??null,value,ids.key+' evidence '+field); assert.equal(source.contentHash,sha(input.source.rawText));assert.equal(source.recordState,'active');assert.equal(evidence.recordState,'active'); assert.equal(source.meta.requestId,taskKey+':'+ids.key+':import:v1:source'); assert.deepEqual(evidence.sourceRef,pointer('KfSource',ids.sourceId)); assert.equal(evidence.productRef??null,null);assert.equal(evidence.artifactRef??null,null);assert.equal(evidence.publishedAt??null,null); assert.equal(evidence.locator.publicationTimezone,'unknown');assert.equal(evidence.locator.archiveSha256,sha(archive)); assert.equal([...source.rawText].slice(evidence.locator.startCharacter,evidence.locator.endCharacter).join(''),evidence.quote); for(const field of ['textOrigin','responseOriginal','screenshot','articleDom']){ const file=evidence.locator[field];assert.equal(sha(fs.readFileSync(originals+file.file)),file.sha256,ids.key+' '+field); } assert.equal(job.status,'succeeded');assert.equal(job.expectedCount,1);assert.equal(job.successCount,1);assert.equal(job.failureCount,0); assert.deepEqual(job.inputSourceRef,pointer('KfSource',ids.sourceId)); assert.equal(job.producer.agent.name,'王刚001');assert.equal(job.producer.agent.key,s1.meta.attribution.agentKey); assert.equal(item.targetClass,'KfEvidence');assert.equal(item.targetId,ids.evidenceId); assert.equal(item.itemKey,row.itemKey);assert.equal(item.businessKey,row.externalKey);assert.equal(item.status,'succeeded');assert.equal(item.operation,'created'); assert.equal(sources.filter(x=>x.sourceUrl===source.sourceUrl).length,1); assert.equal(evidences.filter(x=>x.sourceRef?.objectId===ids.sourceId).length,1); assert.equal(jobs.filter(x=>x.inputSourceRef?.objectId===ids.sourceId).length,1); reports.push({...ids,sourceOperation:'created',created:1,unchanged:0,sourceMatchesFixedInput:true,evidenceMatchesFixedInput:true, quoteAndUnicodeLocatorMatched:true,originalArchiveAndFilesVerified:true,actualCollectionTimePreserved:true, publicationTimeAndTimezonePreserved:true,productRef:null,importJobSucceeded:true,sameAgent:true,duplicateSourceCount:1}); } check='original S1 preserved'; const template=JSON.parse(fs.readFileSync('work/first-live-S1-v2-template.json','utf8')); for(const field of ['title','rawText','version','stageCode'])assert.deepEqual(s1[field],template[field]); assert.equal(s1.dataMode,'live');assert.equal(s1.dashboardState,'workshop');assert.equal(s1.ownerSession,'btrFqkz0yw'); assert.deepEqual(s1.report.chapters,template.report.chapters);assert.deepEqual(s1.report.evidence,template.report.evidence); assert.deepEqual(s1.meta.workshop.sourceIds,template.sourceIds); const result={ok:true,operation:'consumer-quotes-independently-verified',at:new Date().toISOString(),taskKey,readOnly:true,businessWrites:0, dataMode:'live',sources:4,evidence:4,imports:reports,cq04EvidenceIndependentlyVerified:true, originalReadbackErrorRetained:true,originalReadbackRequestResent:false,originalReadbackFailureCauseVerified:false, newProductsFromTheseSources:0,s2Saved:false,originalS1Preserved:true,originalS1ArtifactId:'G8IrkkDG7X', archive:{sha256:sha(archive),bytes:archive.length,localOriginalsVerified:true,remoteLocationBasis:'operations receipt'}, dateComparison:'Parse Date envelope normalized to ISO value; first verification attempt retained separately.', purchaseVerified:false,realIdentityVerified:false,marketDemandValidated:false,formalReportPublished:false}; fs.writeFileSync(output,JSON.stringify(result,null,2)+'\n',{flag:'wx'});console.log(JSON.stringify(result,null,2)); }catch(error){ const result={ok:false,operation:'consumer-quotes-independent-readback-stopped',check,cq04EvidenceIndependentlyVerified:cq04Verified, importsVerified:reports.length,errorCode:'CONSUMER_QUOTES_READBACK_FAILED',checkLine:error.stack?.match(/verify-consumer-quotes-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; }