import { createHash } from 'node:crypto' import { isDeepStrictEqual as equal } from 'node:util' export const feeVoidScopeKeys=['marker','customerId','sourceFeeId','sourceVersion','sourceType'] export const feeSourceTypes=['OVERSEAS_INBOUND_FEE','OVERSEAS_PROCUREMENT_FEE','OVERSEAS_TRUCK_FEE','OVERSEAS_LOOSEBOARD_FEE', 'OVERSEAS_WAREHOUSE_FEE','OVERSEAS_CONTAINER_FEE','OVERSEAS_ORDER_FEE','OVERSEAS_DELIVERY_FEE','OVERSEAS_RELABEL_FEE'] const hash=value=>'sha256:'+createHash('sha256').update(value,'utf8').digest('hex') const without=(row,keys)=>Object.fromEntries(Object.entries(row).filter(([key])=>!keys.includes(key))) const date=value=>Date.parse(String(value).replace(' ','T').replace(/Z$/,'')+'Z') export function assertTestFeeVoidFaultProof(proof,scope) { const need=(passed,code)=>{if(!passed)throw Error('Invalid fee void fault proof: '+code)} need(proof?.readOnly===true && feeVoidScopeKeys.every(key=>typeof scope[key]==='string'&&proof[key]===scope[key]) && Object.keys(scope).length===feeVoidScopeKeys.length && feeSourceTypes.includes(scope.sourceType) && /^[A-Za-z0-9_-]{1,96}$/.test(scope.marker) && ['customerId','sourceFeeId','sourceVersion'].every(key=>/^[1-9][0-9]{0,19}$/.test(scope[key])) && Number(scope.sourceVersion)<=2147483647, 'SCOPE') const event=(row,action)=>{ need(row && /^[1-9][0-9]{0,19}$/.test(row.entryId) && /^[0-9a-fA-F-]{36}$/.test(row.eventId), 'EVENT_IDENTITY') const env=row.envelope,p=env?.payload need(row.eventType==='BusinessFeeSourceChanged' && row.customerId===scope.customerId && row.aggregateId===scope.sourceFeeId && row.aggregateType===scope.sourceType && row.eventKey===`BusinessFeeSourceChanged:LASTLEG:${scope.sourceType}:${scope.sourceFeeId}:${scope.sourceVersion}:${action}` && env?.eventId===row.eventId && env.eventType===row.eventType && env.tenantId===scope.customerId && Number(env.eventVersion)===1 && env.aggregateType===scope.sourceType && env.aggregateId===scope.sourceFeeId && String(env.aggregateVersion)===scope.sourceVersion && p?.action===action && p.sourceSystem==='LASTLEG' && p.customerId===scope.customerId && p.sourceFeeId===scope.sourceFeeId && String(p.sourceVersion)===scope.sourceVersion && p.sourceType===scope.sourceType && p.direction==='RECEIVABLE' && p.currency==='CNY' && p.reviewStatus==='APPROVED' && p.publicationStatus==='PUBLISHED' && p.sourceStatus===(action==='VOIDED'?'VOIDED':'ACTIVE'), 'EVENT_SCOPE') // Runtime preserves exact decimal lexemes and applies Java BigDecimal canonicalization. let parsed,canonical,unsigned try {parsed=JSON.parse(row.envelopeJson);canonical=JSON.parse(row.payloadCanonicalJson);unsigned=JSON.parse(row.sourceCanonicalJson)} catch {need(false,'CANONICAL_JSON')} need(equal(parsed,env) && equal(canonical,p) && equal(unsigned,{...p,action:null,sourceStatus:'ACTIVE',sourceHash:null}) && hash(row.payloadCanonicalJson)===row.payloadHash && env.payloadHash===row.payloadHash && hash(row.sourceCanonicalJson)===p.sourceHash, 'PAYLOAD_HASH') return p } const row=proof.producer,payload=event(row,'VOIDED'),prior=proof.prerequisite,publication=prior?.publication const original=event(publication,scope.sourceVersion==='1'?'PUBLISHED':'ADJUSTED') need(prior.ready===true && publication.publishStatus==='PUBLISHED' && equal(publication,proof.publicationNow) && publication.eventId!==row.eventId && publication.entryId!==row.entryId && publication.sourceCanonicalJson===row.sourceCanonicalJson && original.sourceHash===payload.sourceHash && prior.attempts?.some(a=>a.entryId===publication.entryId&&a.eventId===publication.eventId&&['APPLIED','DUPLICATE'].includes(a.outcome)), 'PUBLICATION_PREREQUISITE') const before=proof.beforeRetry,after=proof.afterRetry need(before?.publishStatus==='DEAD' && before.failedAttempts===12 && after?.publishStatus==='PUBLISHED' && row.publishStatus==='PUBLISHED' && [before,after].every(value=>value.entryId===row.entryId && value.eventId===row.eventId && value.payloadHash===row.payloadHash && equal(value.envelope,row.envelope) && value.envelopeJson===row.envelopeJson && value.payloadCanonicalJson===row.payloadCanonicalJson && value.sourceCanonicalJson===row.sourceCanonicalJson), 'ORIGINAL_RETRY') const attempts=proof.attempts,records=proof.proxyRecords?.filter(value=>value.matched===true) need(attempts?.length===13 && new Set(attempts.map(a=>a.attemptId)).size===13 && attempts.every((a,i)=>a.entryId===row.entryId && a.eventId===row.eventId && a.serviceName==='lastleg' && a.outcome===(i<11?'RETRY':i===11?'DEAD':'DUPLICATE')), 'ATTEMPTS') need(records?.length===13 && records.every((a,i)=>a.eventId===row.eventId && a.payloadHash===row.payloadHash && a.attempt===i+1 && a.outcome===(i===0?'APPLIED':'DUPLICATE') && (i<12?a.errorCode==='ACK_LOSS_INJECTED':a.errorCode===null)), 'REAL_ACK_LOSS') const audit=proof.retryAudit need(proof.retryAuditCount===1 && audit?.requestId===proof.retryRequestId && audit.entryId===row.entryId && audit.eventId===row.eventId && audit.payloadHash===row.payloadHash && audit.previousFailures===12, 'RETRY_AUDIT') const initial=prior.business,dead=before.fee,done=after.fee need(initial && dead && done && equal(dead.finance,done.finance), 'FINANCE_CHANGED_AFTER_DUPLICATE') const selected=(facts,local)=>{ const rows=local?facts.lastleg:facts.finance?.source need(Array.isArray(rows)&&rows.length>0&&new Set(rows.map(a=>a.id)).size===rows.length, 'SOURCE_ROWS') need(rows.every(a=>a.customerId===scope.customerId && a.sourceType===scope.sourceType && a.sourceFeeId===scope.sourceFeeId), 'SOURCE_SCOPE') const result=rows.filter(a=>String(local?a.sourceVersion:a.sourceFeeVersion)===scope.sourceVersion) need(result.length===1, 'UNIQUE_SOURCE_VERSION') need(Math.max(...rows.map(a=>Number(local?a.sourceVersion:a.sourceFeeVersion)))===Number(scope.sourceVersion), 'LATEST_VERSION') return result[0] } const l0=selected(initial,true),ld=selected(dead,true),la=selected(done,true) const f0=selected(initial,false),fd=selected(dead,false) need(l0.sourceStatus==='ACTIVE' && ld.sourceStatus==='ACTIVE' && la.sourceStatus==='VOIDED' && [l0,ld,la].every(a=>a.reviewStatus==='APPROVED'&&a.publicationStatus==='PUBLISHED'&&a.direction==='RECEIVABLE'), 'LASTLEG_ACK_BOUNDARY') need(equal(without(l0,['reviewReason','updatedBy','updatedAt']),without(ld,['reviewReason','updatedBy','updatedAt'])) && equal(without(ld,['sourceStatus','updatedAt']),without(la,['sourceStatus','updatedAt'])) && typeof ld.reviewReason==='string' && ld.reviewReason.trim() && date(la.updatedAt)>=date(ld.updatedAt), 'LASTLEG_UNEXPECTED_CHANGE') need(equal(initial.lastleg.filter(a=>a.id!==l0.id),dead.lastleg.filter(a=>a.id!==ld.id)) && equal(dead.lastleg.filter(a=>a.id!==ld.id),done.lastleg.filter(a=>a.id!==la.id)), 'OTHER_LOCAL_VERSIONS_CHANGED') need(f0.status==='AVAILABLE' && fd.status==='VOIDED' && Number(fd.operationVersion)===Number(f0.operationVersion)+1 && equal(without(f0,['status','operationVersion','updatedAt']),without(fd,['status','operationVersion','updatedAt'])) && f0.sourceHash===payload.sourceHash && f0.direction==='RECEIVABLE' && f0.currencyCode==='CNY' && f0.amount===l0.amount && f0.walletHoldId===null && equal(f0.sourceSnapshot,original), 'FINANCE_VOID_FACT') need(equal(initial.finance.source.filter(a=>a.id!==f0.id),dead.finance.source.filter(a=>a.id!==fd.id)), 'OTHER_FINANCE_VERSIONS_CHANGED') for(const facts of [initial,dead,done]) for(const key of ['bill','settlement','cost']) need(Array.isArray(facts.finance[key])&&facts.finance[key].length===0,'FINANCIAL_LINKS') const checkInbox=(rows,producer,resource)=>{ need(rows?.length===1 && rows[0].sourceSystem==='LASTLEG' && rows[0].eventType==='BusinessFeeSourceChanged' && rows[0].eventId===producer.eventId && rows[0].payloadHash===producer.payloadHash && rows[0].status==='PROCESSED' && rows[0].resultResourceId===resource.id && rows[0].processedAt && rows[0].errorCode===null && equal(rows[0].envelope,producer.envelope), 'FINANCE_INBOX') } checkInbox(initial.finance.inbox,publication,f0); checkInbox(dead.finance.inbox,row,fd) need(proof.consumer?.count===1 && equal(without(proof.consumer,['count']),dead.finance.inbox[0]), 'CONSUMER_IDENTITY') return proof }