// Independent read-only acceptance of the actual first live source imports. import fs from 'node:fs'; import crypto from 'node:crypto'; import assert from 'node:assert/strict'; import {request,classRows} from '../server/scripts/platform-client.mjs'; const bundle=JSON.parse(fs.readFileSync(new URL('../server/test/fixtures/first-live-sources-20261102.json',import.meta.url))); const categoryId='Mc9FJpryn4'; const imported=[ {key:'uni-jiangban',sourceId:'BgUB6dnxN1',products:['OH93MpSAur','1hhmH3P2jO','RyVDxfutN7','43cZQbN1s_'],evidence:['kHU2d7KCRd','4F0odlTmp7','OMPoQHwLtM','u-M-YlSAp5']}, {key:'uni-qiaomianguan',sourceId:'SFiKA0D7n9',products:['Sq74PECPh_','jSsWsk83J0','JojU9_ZB-p','WjigWybqon','rbgvCXmBwo'],evidence:['KebZUsjXjB','t1fLXcQoth','cPbDPYTJnl','Twk_M76Irf','zthpfSI4v-']} ]; const canonical=x=>x&&typeof x==='object'?Array.isArray(x)?'['+x.map(canonical).join(',')+']':'{'+Object.keys(x).sort().map(k=>JSON.stringify(k)+':'+canonical(x[k])).join(',')+'}':JSON.stringify(x); const get=(table,id)=>request('GET','/classes/'+table+'/'+encodeURIComponent(id)); const ptr=(className,objectId)=>({__type:'Pointer',className,objectId}); try{ const category=await get('KfCategory',categoryId),reports=[]; for(const row of imported){ const batch=bundle.batches.find(b=>b.key===row.key),task=batch.task; const source=await get('KfSource',row.sourceId); for(const field of ['title','sourceKind','sourceUrl','platform','dataMode','rawText'])assert.equal(source[field],task.source[field]); assert.equal(Date.parse(source.collectedAt?.iso||source.collectedAt),Date.parse(task.source.collectedAt)); assert.equal(source.contentHash,crypto.createHash('sha256').update(task.source.rawText).digest('hex')); const sourceMatches=await classRows('KfSource',{contentHash:source.contentHash,sourceUrl:source.sourceUrl,dataMode:'live'});assert.equal(sourceMatches.length,1);assert.equal(sourceMatches[0].objectId,row.sourceId); const [products,evidence,jobs]=await Promise.all([ classRows('KfProduct',{sourceRef:ptr('KfSource',row.sourceId)}),classRows('KfEvidence',{sourceRef:ptr('KfSource',row.sourceId)}), classRows('KfImportJob',{inputSourceRef:ptr('KfSource',row.sourceId)}) ]); assert.deepEqual(products.map(p=>p.objectId).sort(),row.products.slice().sort());assert.deepEqual(evidence.map(e=>e.objectId).sort(),row.evidence.slice().sort()); const lookup=new Map([['category',category]]); for(let i=0;ip.objectId===row.products[i]));lookup.set('evidence-'+(i+1),evidence.find(e=>e.objectId===row.evidence[i]));} for(const item of task.records){ const saved=lookup.get(item.itemKey);assert.ok(saved);assert.equal(saved.dataMode,'live'); for(const [field,v]of Object.entries(item.data)){ if(v?.$item)assert.equal(saved[field]?.objectId,lookup.get(v.$item)?.objectId); else if(field==='collectedAt')assert.equal(Date.parse(saved[field]?.iso||saved[field]),Date.parse(v)); else assert.equal(canonical(saved[field]),canonical(v)); } if(item.className==='KfEvidence')assert.ok(source.rawText.includes(saved.quote)); if(item.className==='KfProduct'){assert.equal(saved.netWeightG??null,null);assert.equal(saved.observedRetailPrice??null,null);assert.equal(saved.categoryRef?.objectId,categoryId);} } assert.equal(jobs.length,task.records.length); const operations=[]; for(const job of jobs){ 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,'agent-account:7c9f85363ca4c5a529a5b363308b065998924e2394e846145a5c62c904506d2e'); const items=await classRows('KfImportItem',{jobRef:ptr('KfImportJob',job.objectId)});assert.equal(items.length,1); const item=items[0];assert.equal(item.status,'succeeded');assert.equal(item.targetId,lookup.get(item.itemKey)?.objectId); operations.push(item.operation); } reports.push({key:row.key,sourceId:row.sourceId,productIds:row.products,evidenceIds:row.evidence,importJobs:jobs.length,created:operations.filter(x=>x==='created').length,unchanged:operations.filter(x=>x==='unchanged').length}); } assert.equal(reports.reduce((s,b)=>s+b.created,0),19);assert.equal(reports.reduce((s,b)=>s+b.unchanged,0),1); const existingSessions=await classRows('KfSession',{workshopKey:bundle.taskKey}); const result={ok:true,operation:'first-live-sources-independently-verified',at:new Date().toISOString(),readOnly:true,businessWrites:0,taskKey:bundle.taskKey,dataMode:'live',categoryId,sourcePages:2,productFlavorEntries:9,evidenceEntries:9,independentManufacturerCount:1,sourceAndQuoteVerified:true,sameAgentAttribution:true,imports:reports,matchingSessionsBeforeS1:existingSessions.length,formalReportPublished:false,consumerResearchPerformed:false,collectionPerformedBy:'Codex',importExecutedBy:'workshop Agent; execution receipt relayed by user'}; fs.writeFileSync('work/first-live-readback-20261102.json',JSON.stringify(result,null,2));console.log(JSON.stringify(result,null,2)); }catch{console.error(JSON.stringify({ok:false,error:'FIRST_LIVE_READBACK_FAILED',message:'只读核验未通过;未输出原始异常、账号或凭据。'}));process.exitCode=1;}