| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- // Independently verify archived public material and existing import history. Read only.
- import fs from 'node:fs';
- import path from 'node:path';
- import crypto from 'node:crypto';
- import assert from 'node:assert/strict';
- import {invoke,classRows} from '../server/scripts/platform-client.mjs';
- const output='work/S4-public-readback-20261103.json';
- assert.equal(fs.existsSync(output),false,'Preserve prior acceptance');
- const directory=path.resolve('work/S4-public-materials-20261103');
- const zip='F:/文档/工作坊当前资料/ksf-S4-public-materials-20261103.zip';
- const taskKey='KSF-S4公开资料-20261103-01';
- const sessionId='btrFqkz0yw',workshopKey='KSF-真实资料-20261102-01';
- const archiveSha='88776415ab6e80e9a5b1220bf5836f5022d4c0a7814cc251128e052b0cb9009f';
- const sha=value=>crypto.createHash('sha256').update(value).digest('hex');
- const ptr=(className,objectId)=>({__type:'Pointer',className,objectId});
- const normalize=value=>Array.isArray(value)?value.map(normalize):value&&typeof value==='object'?
- value.__type==='Date'?value.iso:Object.fromEntries(Object.entries(value).map(([k,v])=>[k,normalize(v)])):value;
- const read=async(className,objectId)=>{
- const r=await invoke('ksfGetRecord',{className,objectId});
- assert.equal(r.http,200);assert.equal(r.body?.result?.ok,true);
- const row=r.body.result.data.record;assert.equal(row?.objectId,objectId);return row;
- };
- const imports=[
- {key:'p1',sourceId:'PjORcvaRHn',evidenceId:'xHGV_mRWcG',name:'suning-photo-117',lines:[5,31],length:188,
- start:122,end:136,quote:'净含量\n585克\n包装\n袋装',sourceKind:'ecommerce',platform:'suning',
- url:'https://m.suning.com/bigimages/12447641634.html',externalKey:taskKey+':p1:listing:v1',itemKey:'s4-p1-listing',
- evidenceKind:'retailer_listing_observation',parentObjectId:'12447641634',authorLabel:'苏宁易购商品页(平台标示,无个人作者)',
- rawSha:'26f9865d7061009a002f2fbb8d5c9bd21f18cc5097444781b0de3ddd08470a5c',publishedAtReported:null},
- {key:'p2',sourceId:'5S_Y4IgT5q',evidenceId:'CeAEIwymTa',name:'post-label-review',lines:[12,49],length:766,
- start:327,end:356,quote:'包装上写的煮面6-7分钟,实测写少了,煮了10分钟面才软。',sourceKind:'social',platform:'smzdm',
- url:'https://post.smzdm.com/p/aeqxm74k/',externalKey:taskKey+':p2:experience:v1',itemKey:'s4-p2-experience',
- evidenceKind:'public_user_experience_self_report',parentObjectId:'aeqxm74k',authorLabel:'没事儿聊聊呗',
- rawSha:'2a5e1057b91f313ea6fb9ee747f6fcc2f7b11dca55342672950cb51e4a6ada62',publishedAtReported:'2023-12-18 21:20:15'}
- ];
- let step='original package and four exact reads';
- const checks=[],reports=[];
- const check=(name,fn)=>{fn();checks.push({name,passed:true});};
- try{
- const archive=fs.readFileSync(zip);assert.equal(archive.length,6100444);assert.equal(sha(archive),archiveSha);
- const inspection=JSON.parse(fs.readFileSync('work/S4-public-record-inspection-20261103.json','utf8'));
- assert.equal(inspection.ok,true);assert.equal(inspection.records.length,4);
- const index=JSON.parse(fs.readFileSync(path.join(directory,'SOURCE-INDEX.json'),'utf8'));
- for(const ids of imports){
- step=ids.key+' source and evidence';
- const source=inspection.records.find(x=>x.className==='KfSource'&&x.objectId===ids.sourceId).record;
- const evidence=inspection.records.find(x=>x.className==='KfEvidence'&&x.objectId===ids.evidenceId).record;
- const origin=index.sources.find(x=>x.id===ids.name);
- const raw=fs.readFileSync(path.join(directory,'browser',ids.name+'.text.txt'),'utf8').split(/\r?\n/).slice(ids.lines[0]-1,ids.lines[1]).join('\n');
- check(ids.key+' source body and archive extraction',()=>{
- assert.equal(source.rawText,raw);assert.equal([...raw].length,ids.length);assert.equal(sha(raw),ids.rawSha);assert.equal(source.contentHash,ids.rawSha);
- assert.equal(source.sourceUrl,ids.url);assert.equal(source.title,origin.title);assert.equal(source.sourceKind,ids.sourceKind);
- assert.equal(source.platform,ids.platform);assert.equal(source.collectedAt,origin.observedAt);
- });
- check(ids.key+' literal evidence fields and source pointer',()=>{
- for(const row of [source,evidence]){
- assert.equal(row.dataMode,'live');assert.equal(row.reviewState,'pending');assert.equal(row.recordState,'active');assert.equal(row.schemaVersion,'ksf.v0.7');
- }
- assert.equal(evidence.externalKey,ids.externalKey);assert.equal(evidence.platform,ids.platform);assert.equal(evidence.quote,ids.quote);
- assert.equal(evidence.evidenceKind,ids.evidenceKind);assert.equal(evidence.quoteForm,'verbatim_excerpt');
- assert.equal(evidence.verificationState,'archived_text_and_screenshot_checked');
- assert.equal(evidence.authorLabel,ids.authorLabel);assert.equal(evidence.parentObjectId,ids.parentObjectId);
- assert.equal(evidence.collectedAt,origin.observedAt);assert.equal(evidence.publishedAt??null,null);
- assert.equal(evidence.productRef??null,null);assert.equal(evidence.artifactRef??null,null);
- assert.deepEqual(evidence.sourceRef,ptr('KfSource',ids.sourceId));
- });
- const locator=evidence.locator;
- check(ids.key+' locator and date distinctions',()=>{
- assert.equal(locator.url,ids.url);assert.equal(locator.startCharacter,ids.start);assert.equal(locator.endCharacter,ids.end);
- assert.equal(locator.unit,'Unicode code point');assert.equal(locator.startBase,0);assert.equal(locator.endExclusive,true);
- assert.equal([...raw].slice(ids.start,ids.end).join(''),ids.quote);assert.equal(locator.archiveSha256,archiveSha);
- assert.equal(locator.archiveFile,path.basename(zip));assert.equal(locator.authenticated,false);assert.equal(locator.purchaseVerified,false);
- assert.equal(locator.browserReadAt,origin.observedAt);assert.equal(locator.httpCollectedAt,origin.startedAt);
- assert.equal(locator.publishedAtReported,ids.publishedAtReported);assert.equal(locator.publicationTimezone,'unknown');
- assert.equal(locator.collectionPerformedBy,'Codex');
- });
- const fileResults=[];
- for(const file of [locator.textOrigin,locator.responseOriginal,locator.articleDom,locator.screenshot,...locator.imageReadings]){
- const target=path.resolve(directory,file.file);
- assert.ok(target.startsWith(directory+path.sep),'Archive paths stay inside packet');
- assert.equal(sha(fs.readFileSync(target)),file.sha256,file.file);
- fileResults.push({file:file.file,sha256:file.sha256,verified:true});
- }
- checks.push({name:ids.key+' five locator-linked original file hashes',passed:true});
- for(const image of locator.imageReadings){
- const original=index.images.find(x=>x.file===image.file);assert.ok(original);assert.equal(original.sha256,image.sha256);
- }
- reports.push({key:ids.key,sourceId:ids.sourceId,evidenceId:ids.evidenceId,externalKey:ids.externalKey,
- sourceBodyMatchesArchivedExtraction:true,sourceTextSha256:sha(raw),quoteSliceVerified:true,
- evidenceKind:evidence.evidenceKind,verificationState:evidence.verificationState,quoteForm:evidence.quoteForm,
- collectedAt:evidence.collectedAt,publishedAt:null,publishedAtReported:locator.publishedAtReported,publicationTimezone:'unknown',
- authorLabel:evidence.authorLabel,parentObjectId:evidence.parentObjectId,sourceRefMatched:true,productRef:null,
- locatorOriginalFiles:fileResults,inputFileByteHashBasis:'Remote operator receipt; complete input JSON not obtained by Codex'});
- }
- step='current session, import jobs/items, duplicate and stage history';
- const sources=imports.map(x=>ptr('KfSource',x.sourceId));
- const settled=await Promise.allSettled([
- read('KfSession',sessionId),
- classRows('KfSource',{sourceUrl:{$in:imports.map(x=>x.url)}}),
- classRows('KfEvidence',{$or:[{sourceRef:{$in:sources}},{externalKey:{$in:imports.map(x=>x.externalKey)}}]}),
- classRows('KfImportJob',{inputSourceRef:{$in:sources}}),
- classRows('KfImportItem',{businessKey:{$in:imports.map(x=>x.externalKey)}}),
- classRows('KfProduct',{sourceRef:{$in:sources}}),
- classRows('KfArtifact',{workshopKey}),
- classRows('KfOpportunity',{sessionRef:ptr('KfSession',sessionId)}),
- classRows('KfProject',{sessionRef:ptr('KfSession',sessionId)}),
- ]);
- const failures=settled.flatMap((r,i)=>r.status==='rejected'?[i]:[]);assert.deepEqual(failures,[],'Read group failures (indexes only)');
- const [session,sourceHistory,evidenceHistory,jobs,items,products,artifacts,opportunities,projects]=settled.map(r=>normalize(r.value));
- check('same live S3 session and no extra business objects',()=>{
- assert.equal(session.dataMode,'live');assert.equal(session.stageCode,'S3');assert.equal(session.workshopKey,workshopKey);
- assert.equal(products.length,0);assert.equal(opportunities.length,0);assert.equal(projects.length,0);
- assert.equal(artifacts.filter(x=>x.stageCode==='S4').length,0);assert.equal(artifacts.length,5);
- });
- for(const ids of imports){
- step=ids.key+' unique import history';
- check(ids.key+' one source/evidence/job/item and created operation',()=>{
- assert.deepEqual(sourceHistory.filter(x=>x.sourceUrl===ids.url).map(x=>x.objectId),[ids.sourceId]);
- assert.deepEqual(evidenceHistory.filter(x=>x.sourceRef?.objectId===ids.sourceId).map(x=>x.objectId),[ids.evidenceId]);
- const group=jobs.filter(x=>x.inputSourceRef?.objectId===ids.sourceId);assert.equal(group.length,1);
- const job=group[0],related=items.filter(x=>x.jobRef?.objectId===job.objectId);assert.equal(related.length,1);
- assert.equal(job.status,'succeeded');assert.equal(job.expectedCount,1);assert.equal(job.successCount,1);assert.equal(job.failureCount,0);
- assert.equal(job.producer.agent.key,session.meta.attribution.agentKey);assert.equal(job.producer.agent.name,'王刚001');
- assert.equal(related[0].targetClass,'KfEvidence');assert.equal(related[0].targetId,ids.evidenceId);
- assert.equal(related[0].itemKey,ids.itemKey);assert.equal(related[0].businessKey,ids.externalKey);assert.equal(related[0].operation,'created');
- assert.equal(related[0].status,'succeeded');assert.equal(related[0].attempts,1);
- Object.assign(reports.find(x=>x.key===ids.key),{jobId:job.objectId,itemId:related[0].objectId,created:1,unchanged:0,importJobSucceeded:true,recordOperation:'created',sameAgent:true,sourceCount:1,evidenceCount:1});
- });
- }
- step='old reports unchanged';
- const oldReports=[['G8IrkkDG7X','work/first-live-S1-v2-template.json'],['b7NOby5ys0','work/first-live-S2-template.json'],
- ['oiNxX7o27j','work/first-live-S3-template.json'],['ZW1FB5zeHr','work/first-live-S3-v2-template.json']];
- for(const [id,file]of oldReports){
- const row=artifacts.find(x=>x.objectId===id);assert.ok(row);const template=JSON.parse(fs.readFileSync(file,'utf8'));
- check(id+' template fields preserved',()=>{
- for(const field of ['title','rawText','version','stageCode'])assert.deepEqual(row[field],template[field]);
- assert.deepEqual(row.report.chapters,template.report.chapters);assert.deepEqual(row.report.evidence,template.report.evidence);
- assert.deepEqual(row.meta.workshop.sourceIds,template.sourceIds);assert.deepEqual(row.meta.workshop.pendingQuestions,template.pendingQuestions);
- assert.equal(row.ownerSession,sessionId);assert.equal(row.dataMode,'live');assert.equal(row.dashboardState,'workshop');
- assert.equal(row.dashboardPublishedAt??null,null);assert.equal(row.contentConfirmedAt??null,null);
- });
- }
- const result={ok:true,operation:'S4-public-import-independently-verified',at:new Date().toISOString(),readOnly:true,businessWrites:0,
- taskKey,sessionId,systemStage:'S3',checksPassed:checks.length,checks,imports:reports,
- archive:{localZip:zip,bytes:6100444,sha256:archiveSha,locatorLinkedFileHashesVerified:10,remoteArchiveLocationBasis:'Operations receipt'},
- distinctImportJobs:jobs.length,distinctImportItems:items.length,productsFromNewSources:0,opportunities:0,projects:0,s4Artifacts:0,
- oldReportTemplatesPreserved:true,purchaseVerified:false,physicalSampleVerified:false,marketDemandValidated:false,
- remoteInputFilesIndependentlyObtained:false,remoteReceiptsAndFilePermissionsBasis:'Operator report, not independent filesystem inspection',
- timingNote:'locator.httpCollectedAt equals browser navigation startedAt in the supplied source index; browserReadAt/collectedAt equals observedAt. Do not interpret it as a separate successful HTTP response timestamp.',
- publicationNote:'publishedAt null preserves unknown timezone; P2 locator.publishedAtReported preserves the displayed historical timestamp.'};
- fs.writeFileSync(output,JSON.stringify(result,null,2)+'\n',{flag:'wx'});
- console.log(JSON.stringify({ok:true,checksPassed:checks.length,imports:reports.map(({key,sourceId,evidenceId,jobId,evidenceKind,verificationState,quoteForm,publishedAtReported})=>({key,sourceId,evidenceId,jobId,evidenceKind,verificationState,quoteForm,publishedAtReported})),systemStage:'S3',opportunities:0,projects:0,s4Artifacts:0,locatorLinkedFileHashesVerified:10,oldReportTemplatesPreserved:true},null,2));
- }catch(error){
- const result={ok:false,operation:'S4-public-import-independent-verification-stopped',at:new Date().toISOString(),step,
- checkLine:error.stack?.match(/verify-S4-public-readback\.mjs:(\d+):/)?.[1]??null,checksPassed:checks.length,
- errorCode:'S4_PUBLIC_VERIFICATION_FAILED',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;
- }
|