| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119 |
- // 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;
- }
|