@@ -0,0 +1,975 @@
import assert from 'node:assert/strict'
import { readFile , mkdir , writeFile } from 'node:fs/promises'
import { dirname , resolve } from 'node:path'
import { fileURLToPath , pathToFileURL } from 'node:url'
import { createHash } from 'node:crypto'
import { stableSnapshot } from './admin-test-env-modules/container-customs-acceptance-matrix.mjs'
const ROOT = resolve ( dirname ( fileURLToPath ( import . meta . url ) ) , '..' )
const PDA = '/api/platform/v1/first-leg/pda-loading'
const LEG = '/api/platform/v1/first-leg'
const CUSTOMS = '/api/platform/v1/customs/loading-plans'
const TYPES = [ 'PHOTO_STEP_1' , 'PHOTO_STEP_2' , 'PHOTO_STEP_3' , 'PHOTO_STEP_4' , 'SEAL' ]
const id = value => String ( value ? ? '' )
const positive = value => typeof value === 'string' && /^[1-9]\d*$/ . test ( value )
const rows = value => Array . isArray ( value ) ? value : [ ]
const sha = value => createHash ( 'sha256' ) . update ( value ) . digest ( 'hex' )
class CheckFailure extends Error { }
function need ( condition , message ) { if ( ! condition ) throw new CheckFailure ( message ) }
export function plan ( ) {
return { plan : true , networkRequests : 0 , businessWrites : false ,
cases : [ 'A: refund PARTIAL 3→2 + same-owner SKIP + buy PICK + skipped refund customer' ,
'B: only actual buy row PARTIAL 2→1 + all refund rows SKIP; all actual rows PARTIAL' ] ,
upstream : 'Use --fixture=NEW to prepare isolated customers/orders/measurements and two confirmed cabinets, or consume two passing manual-pda-batch-fixture reports' ,
operations : [ 'validate both unclaimed tasks before writes' , 'claim' , 'one atomic batch per task' ,
'upload five supplied image files' , 'complete and poll' , 'actual list/metrics/customs scope' ,
'same original completion replay and zero drift' , 'all-buy start/complete and refund-send rejection' , 'logout own sessions' ] ,
excluded : [ 'F01– F40 full regression' , 'SQL/deployment' , 'historical events or data repair' ,
'browser/device proof' , 'Outbox/ACK/downstream inventory evidence' , 'refund document workflow and XLSX download' ] ,
manifest : { schemaVersion : 1 , runId : 'PARTIAL-ACTUAL-<new-run>' , environment : 'TEST' ,
cases : [ 'A' , 'B' ] . map ( kind => ( { kind , sourceReport : '<new successful Admin fixture report>' ,
actions : [ { itemId : '<PDA item id from taskItems>' , action : 'PARTIAL' , finalPickedPackageCount : kind === 'A' ? 2 : 1 } ] ,
evidence : Object . fromEntries ( TYPES . map ( type => [ type , '<supplied PNG or JPEG path>' ] ) ) , sealNo : '<nonempty seal>' } ) ) } ,
execution : 'node --env-file=<authorized TEST test-acceptance.env> scripts/partial-loading-actual-test-env.mjs --execute --environment=TEST --run-id=<new-run> --fixture=<manifest.json>' }
}
export function executionOptions ( args , execArgs , env , cwd = process . cwd ( ) ) {
if ( ! args . length || args . length === 1 && args [ 0 ] === '--plan' ) return { execute : false }
need ( [ 4 , 5 ] . includes ( args . length ) && args [ 0 ] === '--execute' && args [ 1 ] === '--environment=TEST'
&& /^--run-id=PARTIAL-ACTUAL-[A-Za-z0-9_-]{1,40}$/ . test ( args [ 2 ] )
&& args [ 3 ] . startsWith ( '--fixture=' ) && args [ 3 ] . slice ( 10 ) , 'Explicit TEST, new PARTIAL-ACTUAL run-id and fixture required' )
need ( resolve ( cwd ) . replaceAll ( '\\' , '/' ) . toLowerCase ( ) === ROOT . replaceAll ( '\\' , '/' ) . toLowerCase ( ) , 'Run in the TEST backend workspace' )
need ( execArgs . length === 1 && execArgs [ 0 ] . startsWith ( '--env-file=' )
&& resolve ( execArgs [ 0 ] . slice ( 11 ) ) . replaceAll ( '\\' , '/' ) . endsWith ( '/.codex/env/chongchong-global/test-acceptance.env' ) , 'Use the authorized TEST env-file' )
need ( String ( env . CHONGCHONG _ENV ) . toUpperCase ( ) === 'TEST' , 'TEST profile required' )
for ( const key of [ 'CHONGCHONG_ADMIN_API_BASE_URL' , 'CHONGCHONG_ADMIN_USERNAME' , 'CHONGCHONG_ADMIN_PASSWORD' , 'ADMIN_PDA_USERNAME' , 'ADMIN_PDA_PASSWORD' ] ) need ( Boolean ( env [ key ] ) , ` Missing ${ key } ` )
const base = new URL ( env . CHONGCHONG _ADMIN _API _BASE _URL )
need ( [ 'https:' , 'http:' ] . includes ( base . protocol ) && ! base . username && ! base . password
&& base . pathname === '/api' && ! base . search && ! base . hash , 'Configured TEST admin API base must end in /api' )
const input = args [ 3 ] . slice ( 10 )
const completion = input . startsWith ( 'COMPLETION:' ) || input . startsWith ( 'VERIFY_A:' ) || input . startsWith ( 'B_ONLY:' )
need ( completion ? args [ 4 ] === '--ready-commit=ea9732749a9215e660530035c40afc69fe56a5ae' : args . length === 4 ,
'Original-command recovery requires explicit matching controller-ready commit' )
return { execute : true , runId : args [ 2 ] . slice ( 9 ) , fixture : input === 'NEW' ? 'NEW' : input . startsWith ( 'RESUME:' )
? { resumeReport : resolve ( input . slice ( 7 ) ) } : input . startsWith ( 'PREPARED:' )
? { preparedReport : resolve ( input . slice ( 9 ) ) } : input . startsWith ( 'B_ONLY:' ) ? { bOnlyReport : resolve ( input . slice ( 7 ) ) }
: input . startsWith ( 'VERIFY_A:' ) ? { verifyAReport : resolve ( input . slice ( 9 ) ) }
: completion ? { completionReport : resolve ( input . slice ( 11 ) ) } : resolve ( input ) ,
gateway : base . origin , ... ( completion ? { readyCommit : args [ 4 ] . slice ( 15 ) } : { } ) }
}
export function originalCompletionCommand ( failedReport ) {
need ( failedReport . runId === 'PARTIAL-ACTUAL-20261004-recovery-r7' && failedReport . success === false , 'Bind the original A failed execution' )
const attempts = rows ( failedReport . requests ) . filter ( item => item . method === 'POST'
&& item . path === ` ${ PDA } /tasks/2106713892758491138/complete ` )
need ( attempts . length === 1 && attempts [ 0 ] . state === 'RECEIVED' && attempts [ 0 ] . http === 200 && attempts [ 0 ] . code === '200' , 'Exactly one accepted original completion command required' )
const body = attempts [ 0 ] . request
assert . deepEqual ( body , { expectedTaskVersion : 2 , operationId : 'PARTIAL-ACTUAL-20261004-recovery-r3-A-complete' , remark : 'PARTIAL-ACTUAL-20261004-recovery-r3-A' } , 'Original immutable completion payload' )
const command = attempts [ 0 ] . response ? . data ? . completionCommand
need ( id ( command ? . id ) === '2106716718935052290' && command . operationId === body . operationId && Number ( command . expectedTaskVersion ) === 2 ,
'Original command identity/version mismatch' )
return { commandId : id ( command . id ) , body : structuredClone ( body ) }
}
const dataOf = response => {
need ( response . http === 200 && String ( response . body ? . code ) === '200' , ` API rejected ${ response . path } : HTTP= ${ response . http } , code= ${ response . body ? . code ? ? 'missing' } ` )
return response . body . data
}
const selected = ( value , names ) => Object . fromEntries ( names . map ( name => [ name , value ? . [ name ] ? ? null ] ) )
export function stableSource ( source ) {
return { summary : selected ( source . summary , [ 'id' , 'tenantId' , 'orderId' , 'orderNo' , 'inboundCode' , 'processStatus' , 'version' , 'packageCount' , 'totalPackageCount' , 'splitStatus' , 'actualGrossWeightKg' , 'actualVolumeCbm' ] ) ,
statusHistory : source . statusHistory , palletLocations : source . palletLocations }
}
export async function sourceSnapshot ( spec , request ) {
const result = [ ]
for ( const owner of spec . owners ) result . push ( { owner , view : dataOf ( await request ( 'GET' , ` /api/platform/inbounds/ ${ owner . inboundId } ?tenantId= ${ owner . tenantId } ` ) ) } )
// Failed/completed tasks are not an editable inventory-snapshot surface. The
// source detail API exposes the persisted block/slot/reservation and allocation
// quantities needed to prove these already-completed owners do not settle twice.
return { sources : result , inventory : { allocations : result . map ( item => ( { tenantId : item . owner . tenantId ,
inboundId : item . owner . inboundId , palletLocations : item . view . palletLocations } ) ) } }
}
export function verifyOldOwnersUnchanged ( before , after , expected ) {
for ( const item of before . sources ) {
const current = after . sources . find ( row => row . owner . inboundId === item . owner . inboundId )
need ( current , 'Original source disappeared from readback' )
assert . equal ( stableSnapshot ( stableSource ( current . view ) ) , stableSnapshot ( stableSource ( item . view ) ) , 'Original owner facts/version/history/resources must not repeat' )
const actual = expected . some ( row => row . inboundId === item . owner . inboundId && row . actual > 0 )
need ( actual ? current . view . summary . processStatus === 'LOADED' : current . view . summary . processStatus !== 'LOADED' , 'Whole skipped source cannot become LOADED' )
}
assert . equal ( stableSnapshot ( after . inventory ) , stableSnapshot ( before . inventory ) , 'Original domestic inventory must not be deducted again' )
}
export function verifyPhysicalFacts ( expected , task ) {
const actual = rows ( task . items ) . filter ( row => [ 'PICKED' , 'PARTIAL' ] . includes ( row . itemStatus ) && Number ( row . pickedPackageCount ) > 0 )
const expectedActual = expected . filter ( row => row . actual > 0 )
assert . deepEqual ( actual . map ( row => id ( row . loadingPlanItemId ) ) . sort ( ) , expectedActual . map ( row => row . itemId ) . sort ( ) , 'Original physical item set' )
for ( const item of expectedActual ) {
const row = actual . find ( row => id ( row . loadingPlanItemId ) === item . itemId )
need ( Number ( row . pickedPackageCount ) === item . actual && id ( row . tenantId ) === item . accountId && id ( row . inboundId ) === item . inboundId ,
'Original physical count/customer/source drift' )
}
}
async function readSmallPage ( path , request ) {
const page = dataOf ( await request ( 'GET' , path ) )
need ( Array . isArray ( page . records ) && Number ( page . page ) === 1 && Number ( page . size ) === 200
&& Number ( page . total ) === page . records . length && new Set ( page . records . map ( row => id ( row . id ) ) ) . size === page . records . length ,
'Bounded new-sample page incomplete or duplicated' )
return page . records
}
export async function downstreamSnapshot ( spec , request ) {
const result = [ ]
for ( const owner of spec . owners ) {
const query = new URLSearchParams ( { customerId : owner . tenantId , containerNo : spec . source . containerNo , page : '1' , size : '200' } )
const orders = await readSmallPage ( ` /api/platform/v1/last-leg/orders? ${ query } ` , request )
const code = spec . expected . find ( row => row . inboundId === owner . inboundId ) ? . frozen . inboundCode
need ( code , 'Frozen original inbound code required for inventory scope' )
const inventory = new URLSearchParams ( { customerId : owner . tenantId , inboundCode : code , page : '1' , size : '200' } )
const groupedBalances = await readSmallPage ( ` /api/platform/v1/last-leg/inventory/balances? ${ inventory } ` , request )
const balances = [ ]
for ( const row of groupedBalances ) balances . push ( dataOf ( await request ( 'GET' , ` /api/platform/v1/last-leg/inventory/balances/ ${ row . id } ?customerId= ${ owner . tenantId } ` ) ) )
const ledger = await readSmallPage ( ` /api/platform/v1/last-leg/inventory/ledger? ${ inventory } ` , request )
const details = [ ]
for ( const order of orders ) details . push ( dataOf ( await request ( 'GET' , ` /api/platform/v1/last-leg/orders/ ${ order . id } ?customerId= ${ owner . tenantId } ` ) ) )
result . push ( { owner , orders , balances , groupedBalances , ledger , details } )
}
return result
}
export function verifyDownstream ( spec , snapshot ) {
let packages = 0
for ( const item of snapshot ) {
const actualRows = spec . expected . filter ( row => row . inboundId === item . owner . inboundId && row . actual > 0 )
const expected = actualRows . reduce ( ( sum , row ) => sum + row . actual , 0 )
if ( ! expected ) {
need ( ! item . orders . length && ! item . balances . length && ! item . ledger . length , 'Skipped owner must have no tail cargo or transit inventory' )
continue
}
need ( item . orders . length === 1 && item . details . length === 1 , 'Exactly one consumer-created tail order per actual source' )
const order = item . orders [ 0 ] , detail = item . details [ 0 ]
need ( id ( order . customerId ) === item . owner . tenantId && id ( order . sourceInboundId ) === item . owner . inboundId
&& id ( order . sourceContainerBookingId ) === spec . source . containerBookingId && order . containerNo === spec . source . containerNo
&& Number ( order . packageCount ) === expected , 'Tail materialization must use exact actual customer/source/container boxes' )
need ( rows ( detail . statusHistory ) . filter ( row => row . toStatus === 'PENDING_ASSIGNMENT' && row . remark === '头程装柜完成自动生成' ) . length === 1 ,
'Exactly one actual loading-event creation history' )
need ( rows ( detail . cargo ) . length === actualRows . length , 'Skipped SKU cannot enter frozen tail cargo' )
for ( const row of actualRows ) {
const cargo = detail . cargo . find ( cargo => cargo . skuCode === row . frozen . skuCode )
need ( cargo && Number ( cargo . packageCount ) === row . actual , 'Tail SKU actual boxes mismatch' )
}
const sum = ( values , field ) => values . reduce ( ( total , row ) => total + Number ( row [ field ] ) , 0 )
need ( item . balances . every ( row => row . customerId === item . owner . tenantId && row . inboundCode === rowCode ( actualRows ) && id ( row . inboundOrderId ) === id ( order . id )
&& Number ( row . availablePackages ) === 0 && Number ( row . holdPackages ) === 0 && Number ( row . allocatedPackages ) === 0
&& Number ( row . pickedPackages ) === 0 && Number ( row . receivingPackages ) === 0 && Number ( row . procurementPackages ) === 0 )
&& sum ( item . balances , 'inTransitPackages' ) === expected && sum ( item . balances , 'totalPackages' ) === 0 ,
'Initial transit inventory must equal actual boxes only' )
need ( item . ledger . length === actualRows . length && sum ( item . ledger , 'deltaPackages' ) === expected
&& item . ledger . every ( row => Number ( row . beforePackages ) === 0 && Number ( row . afterPackages ) === Number ( row . deltaPackages )
&& row . customerId === item . owner . tenantId && row . inboundCode === rowCode ( actualRows ) && id ( row . inboundOrderId ) === id ( order . id ) && row . businessOrderNo === order . orderNo ) ,
'One initial inventory movement per actual SKU; no duplicate or skipped movement' )
packages += expected
}
return { actualPackages : packages , loadingEventMaterializationVerified : true , directOutboxAckAuditVerified : false }
}
const rowCode = actualRows => actualRows [ 0 ] . frozen . inboundCode
async function waitDownstream ( spec , request , sleep ) {
for ( let attempt = 0 ; attempt < 30 ; attempt ++ ) {
const snapshot = await downstreamSnapshot ( spec , request )
const expectedOrders = spec . expected . filter ( row => row . actual > 0 ) . map ( row => row . inboundId )
const active = [ ... new Set ( expectedOrders ) ]
if ( snapshot . reduce ( ( sum , item ) => sum + item . orders . length , 0 ) === active . length ) return { snapshot , facts : verifyDownstream ( spec , snapshot ) }
await sleep ( 1000 )
}
throw new CheckFailure ( 'Loading event materialization did not converge; no source/event replay' )
}
export async function recoverOriginalA ( spec , original , request , checkpoint , sleep = ms => new Promise ( done => setTimeout ( done , ms ) ) ) {
const path = ` ${ PDA } /tasks/ ${ spec . source . pdaTaskId } `
const task = dataOf ( await request ( 'GET' , ` ${ path } ?warehouseId= ${ spec . source . warehouseId } ` , undefined , 'pda' ) )
need ( task . taskStatus === 'COMPLETION_FAILED' && task . completionCommand ? . status === 'FAILED'
&& id ( task . completionCommand . id ) === original . commandId && task . completionCommand . operationId === original . body . operationId
&& Number ( task . completionCommand . expectedTaskVersion ) === original . body . expectedTaskVersion , 'Original failed command no longer matches; do not issue another command' )
verifyPhysicalFacts ( spec . expected , task )
const before = await sourceSnapshot ( spec , request )
for ( const item of before . sources ) {
const actual = spec . expected . some ( row => row . inboundId === item . owner . inboundId && row . actual > 0 )
need ( actual ? item . view . summary . processStatus === 'LOADED' : item . view . summary . processStatus !== 'LOADED' , 'Original completed/whole-skipped owner baseline mismatch' )
}
await checkpoint ( { stage : 'A_BEFORE_ORIGINAL_RECOVERY' , task , original , before } )
try { dataOf ( await request ( 'POST' , ` ${ path } /complete ` , original . body , 'pda' ) ) }
catch ( error ) {
const readback = await request ( 'GET' , ` ${ path } ?warehouseId= ${ spec . source . warehouseId } ` , undefined , 'pda' )
await checkpoint ( { stage : 'A_RECOVERY_REJECTED_OR_UNKNOWN_READBACK' , readback } )
throw error // no blind repetition of a write with an uncertain result
}
let completed
for ( let attempt = 0 ; attempt < 60 ; attempt ++ ) {
completed = dataOf ( await request ( 'GET' , ` ${ path } ?warehouseId= ${ spec . source . warehouseId } ` , undefined , 'pda' ) )
if ( completed . taskStatus === 'COMPLETED' && completed . workflowStatus === 'COMPLETED' ) break
need ( completed . taskStatus === 'COMPLETING' , ` Original recovery failed: ${ completed . completionCommand ? . failureStage } / ${ completed . completionCommand ? . failureCode } ` )
await sleep ( 1000 )
}
need ( completed . taskStatus === 'COMPLETED' && id ( completed . completionCommand . id ) === original . commandId , 'Same original command must finish' )
verifyPhysicalFacts ( spec . expected , completed )
const after = await sourceSnapshot ( spec , request ) ; verifyOldOwnersUnchanged ( before , after , spec . expected )
const detail = dataOf ( await request ( 'GET' , ` ${ LEG } /loading-lists/ ${ spec . source . loadingPlanId } ?warehouseId= ${ spec . source . warehouseId } ` ) )
const customs = dataOf ( await request ( 'GET' , ` ${ CUSTOMS } / ${ spec . source . loadingPlanId } ` ) )
const planState = dataOf ( await request ( 'GET' , ` ${ LEG } /loading-plans/ ${ spec . source . loadingPlanId } /lifecycle?warehouseId= ${ spec . source . warehouseId } ` ) )
const facts = verifyActual ( spec . expected , detail , customs )
need ( planState . detailResource === 'LOADING_LIST' && planState . planStatus === 'COMPLETED' , 'Recovered plan must expose completed loading-list lifecycle' )
const downstream = await waitDownstream ( spec , request , sleep )
await checkpoint ( { stage : 'A_AFTER_ORIGINAL_RECOVERY' , completed , after , detail , customs , planState , downstream } )
const operations = dataOf ( await request ( 'GET' , ` ${ path } /operations ` , undefined , 'pda' ) )
need ( rows ( operations ) . filter ( row => row . actionType === 'COMPLETE_LOADING' ) . length === 1 , 'Recovered completion audit must be unique' )
return { kind : 'A' , ... facts , ... downstream . facts , originalCommandId : original . commandId ,
oldOwnersAndDomesticInventoryUnchanged : true , wholeSkippedOwnerNotLoaded : true , completed : true }
}
export async function verifyRecoveredA ( spec , original , before , request , checkpoint , sleep = ms => new Promise ( done => setTimeout ( done , ms ) ) ) {
const path = ` ${ PDA } /tasks/ ${ spec . source . pdaTaskId } `
const completed = dataOf ( await request ( 'GET' , ` ${ path } ?warehouseId= ${ spec . source . warehouseId } ` , undefined , 'pda' ) )
need ( completed . taskStatus === 'COMPLETED' && completed . workflowStatus === 'COMPLETED'
&& completed . completionCommand ? . status === 'COMPLETED' && id ( completed . completionCommand . id ) === original . commandId
&& completed . completionCommand . operationId === original . body . operationId
&& Number ( completed . completionCommand . expectedTaskVersion ) === original . body . expectedTaskVersion , 'Same recovered original command must remain completed' )
verifyPhysicalFacts ( spec . expected , completed )
const after = await sourceSnapshot ( spec , request ) ; verifyOldOwnersUnchanged ( before , after , spec . expected )
const detail = dataOf ( await request ( 'GET' , ` ${ LEG } /loading-lists/ ${ spec . source . loadingPlanId } ?warehouseId= ${ spec . source . warehouseId } ` ) )
const customs = dataOf ( await request ( 'GET' , ` ${ CUSTOMS } / ${ spec . source . loadingPlanId } ` ) )
const plan = dataOf ( await request ( 'GET' , ` ${ LEG } /loading-plans/ ${ spec . source . loadingPlanId } /lifecycle?warehouseId= ${ spec . source . warehouseId } ` ) )
const facts = verifyActual ( spec . expected , detail , customs )
need ( plan . detailResource === 'LOADING_LIST' && plan . planStatus === 'COMPLETED' , 'Completed original plan must remain completed' )
const downstream = await waitDownstream ( spec , request , sleep )
const operations = dataOf ( await request ( 'GET' , ` ${ path } /operations ` , undefined , 'pda' ) )
need ( rows ( operations ) . filter ( row => row . actionType === 'COMPLETE_LOADING' ) . length === 1 , 'One original completion audit' )
await checkpoint ( { stage : 'A_READONLY_VERIFIED_AFTER_ORIGINAL_RECOVERY' , completed , after , detail , customs , plan , downstream , operations } )
return { kind : 'A' , ... facts , ... downstream . facts , originalCommandId : original . commandId , readOnlyVerification : true ,
oldOwnersAndDomesticAllocationSnapshotsUnchanged : true , wholeSkippedOwnerNotLoaded : true , completed : true }
}
export function validateZeroWorkTask ( source , task , ownUserId ) {
need ( id ( task . id ) === source . pdaTaskId && id ( task . loadingPlanId ) === source . loadingPlanId
&& id ( task . containerBookingId ) === source . containerBookingId && task . currentContainerNo === source . containerNo
&& task . taskStatus === 'LOADING' && Number ( task . version ) === Number ( source . beforeTaskVersion ) , 'Original task identity/version changed' )
need ( task . workflowStatus === 'UNASSIGNED' && task . assignment == null
|| task . workflowStatus === 'IN_PROGRESS' && id ( task . assignment ? . assigneeId ) === ownUserId ,
'Task must be unassigned or assigned to this own PDA session' )
need ( rows ( task . items ) . length > 0 && rows ( task . items ) . every ( item => item . itemStatus === 'PENDING'
&& Number ( item . pickedPackageCount ) === 0 ) , 'Prepared task already has physical work' )
}
export function validatePreparedResume ( report ) {
need ( report . runId === 'PARTIAL-ACTUAL-20261004-recovery-r3' && report . success === false && rows ( report . cases ) . length === 0
&& report . recovery ? . originalOwner ? . inboundId === '2106706513799155714' && report . recovery ? . originalSha256 ,
'Only this original-source two-cabinet preparation may continue' )
need ( ! rows ( report . requests ) . some ( item => item . method === 'POST' && /\/pda-loading\/tasks\/\d+\/(claim|batch|complete|evidence)$/ . test ( item . path ) ) ,
'Preparation continuation cannot repeat any PDA work' )
const prepared = rows ( report . preparation ) . filter ( item => item . stage === 'PDA_UNASSIGNED' )
need ( prepared . length === 2 && prepared [ 0 ] . kind === 'A' && prepared [ 1 ] . kind === 'B'
&& prepared . every ( item => item . source ? . sourceMarker === ` ${ report . runId } - ${ item . kind } ` && item . spec ? . kind === item . kind ) , 'Exactly the prepared A/B checkpoints required' )
return prepared
}
export function multipartBody ( file ) {
if ( file instanceof FormData ) return file
need ( file ? . content !== undefined , 'Multipart descriptor requires content bytes' )
const content = file . content instanceof Uint8Array ? file . content : new Uint8Array ( file . content )
const form = new FormData ( )
form . append ( 'file' , new Blob ( [ content ] , { type : file . type || 'application/octet-stream' } ) , file . name || 'fixture.bin' )
return form
}
export function requestInit ( method , body , token , extraHeaders = { } ) {
const headers = new Headers ( { Accept : 'application/json' , ... extraHeaders } )
if ( token ) headers . set ( 'Authorization' , ` Bearer ${ token } ` )
if ( body instanceof FormData ) headers . delete ( 'Content-Type' ) // fetch supplies the actual boundary
else if ( body !== undefined ) headers . set ( 'Content-Type' , 'application/json' )
return { method , redirect : 'error' , signal : AbortSignal . timeout ( 30_000 ) , headers ,
body : body === undefined ? undefined : body instanceof FormData ? body : JSON . stringify ( body ) }
}
export function multipartAdapter ( request , roleFor ) {
return ( method , path , file , token , headers = { } ) => request ( method , path , multipartBody ( file ) , roleFor ( token ) , headers )
}
export function validateResumeProof ( report , readback ) {
const owner = { tenantId : '2106706477225971713' , orderId : '2106706495895306242' , inboundId : '2106706513799155714' ,
orderNo : 'FL2106706495895306242' , inboundCode : 'NYEXX2610040025' , marker : 'PARTIAL-ACTUAL-20261004-r1-A-REFUND' }
need ( report . runId === 'PARTIAL-ACTUAL-20261004-r1' && report . success === false && rows ( report . cases ) . length === 0 ,
'Only the explicitly authorized failed original run may resume' )
const rejected = rows ( report . requests ) . filter ( item => item . path === ` /api/platform/v1/files/measurements/ ${ owner . tenantId } / ${ owner . inboundId } ` )
need ( rejected . length === 1 && rejected [ 0 ] . http === 200 && rejected [ 0 ] . code === 'S0500' && rejected [ 0 ] . requestId , 'Original one rejected upload required' )
const summary = readback . inbound ? . summary , measurement = readback . measurement
need ( readback . verified === true && readback . sessionClosed === true && id ( summary ? . id ) === owner . inboundId
&& id ( summary ? . tenantId ) === owner . tenantId && id ( summary ? . orderId ) === owner . orderId && summary ? . orderNo === owner . orderNo
&& summary ? . inboundCode === owner . inboundCode && summary ? . processStatus === 'WAIT_MEASURE'
&& measurement ? . measurementStatus === 'DRAFT' && rows ( measurement . weightResults ) . length === 0 , 'Frozen readback must match original WAIT_MEASURE/DRAFT without weights' )
return owner
}
export function validateResumeLive ( owner , frozen , inbound , measurement ) {
const summary = inbound ? . summary
need ( id ( summary ? . id ) === owner . inboundId && id ( summary ? . tenantId ) === owner . tenantId && id ( summary ? . orderId ) === owner . orderId
&& summary ? . orderNo === owner . orderNo && summary ? . inboundCode === owner . inboundCode
&& summary ? . processStatus === 'WAIT_MEASURE' && measurement ? . measurementStatus === frozen . measurement . measurementStatus
&& [ 'DRAFT' , 'RECEIVED' ] . includes ( measurement ? . measurementStatus )
&& rows ( measurement . weightResults ) . length === 0 && id ( measurement . measurementId ) === id ( frozen . measurement . measurementId )
&& measurement . version === frozen . measurement . version && summary . version === frozen . inbound . summary . version ,
'Original sample changed; stop before any recovery write' )
}
export async function resumeMeasurementDraft ( context , owner , initial ) {
const root = ` ${ LEG } /inbounds/ ${ owner . inboundId } ` , query = ` tenantId= ${ owner . tenantId } `
const ok = response => { need ( response . http === 200 && String ( response . body ? . code ) === '200' , ` Original measurement rejected ${ response . path } ` ) ; return response . body . data }
const bytes = Buffer . from ( 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=' , 'base64' )
const uploaded = ok ( await context . requestMultipart ( 'POST' , ` /api/platform/v1/files/measurements/ ${ owner . tenantId } / ${ owner . inboundId } ` ,
{ content : bytes , type : 'image/png' , name : ` recovery-weight- ${ owner . inboundId } .png ` } ) )
need ( uploaded . fileId , 'Original recovery upload did not return a file' )
const declarations = rows ( initial . latestDeclaration ? . items )
need ( declarations . length === 2 && declarations . map ( item => Number ( item . packageCount ) ) . sort ( ) . join ( ',' ) === '1,3' , 'Original two-SKU declaration must remain 3+1' )
// Save the two declared SKU identities separately. The generic single-SKU fixture
// had aggregated all four boxes into the first detail; no carton scanning is repeated.
const draft = ok ( await context . request ( 'PUT' , ` ${ root } /measurement/draft? ${ query } ` , {
measurementId : initial . measurementId , version : initial . version , actualGoodsName : '原样本两SKU恢复测量' ,
items : declarations . map ( item => ( { inboundDetailId : item . inboundDetailId , itemNo : String ( item . itemNo ) ,
boxSpecification : item . cargoName , packageCount : Number ( item . packageCount ) ,
lengthCm : Number ( item . lengthCm ) , widthCm : Number ( item . widthCm ) , heightCm : Number ( item . heightCm ) ,
intelligentImageFileId : uploaded . fileId , coefficientMode : 'ACTUAL' , remark : owner . marker } ) ) ,
} ) )
const options = ok ( await context . request ( 'GET' , ` ${ root } /weight-pallet-options? ${ query } &page=1&size=20&keyword=YW-TP-002 ` ) )
const pallet = rows ( options . records ) . find ( item => item . palletCode === 'YW-TP-002' )
need ( pallet ? . palletId , 'Original source warehouse needs the existing YW-TP-002 pallet' )
const weight = ok ( await context . request ( 'POST' , ` ${ root } /weight-results? ${ query } ` , { fileId : id ( uploaded . fileId ) ,
palletId : id ( pallet . palletId ) , ocrProvider : 'ADMIN_ACCEPTANCE' , ocrRawText : null , ocrWeightValue : null , ocrWeightUnit : null , confidence : null } ) )
const confirmed = ok ( await context . request ( 'POST' , ` ${ root } /weight-results/ ${ weight . id } /confirm? ${ query } ` , {
version : weight . version , adjustedWeightValue : 100 , adjustedWeightUnit : 'KG' , adjustmentReason : ` ${ owner . marker } HTTP人工称重夹具 ` } ) )
need ( confirmed . recognitionStatus === 'CONFIRMED' , 'Original source weight must be confirmed' )
const current = ok ( await context . request ( 'GET' , ` ${ root } /measurement? ${ query } ` ) )
need ( id ( current . measurementId ) === id ( draft . measurementId ) && current . measurementStatus === 'DRAFT' , 'Original draft identity retained' )
const submitted = ok ( await context . request ( 'POST' , ` ${ root } /measurement/submit? ${ query } ` , { measurementId : current . measurementId , version : current . version } ) )
need ( submitted . processStatus === 'MEASURE_PENDING' , 'Original draft must enter actual measurement review' )
const pending = ok ( await context . request ( 'GET' , ` /api/platform/inbounds/ ${ owner . inboundId } ? ${ query } ` ) )
ok ( await context . request ( 'POST' , ` /api/platform/v1/inbounds/ ${ owner . inboundId } /measurements/approve? ${ query } ` ,
{ measurementId : current . measurementId , expectedVersion : pending . summary . version , reason : null } , context . token ,
{ 'Idempotency-Key' : ` ${ owner . marker } -recovery-approve ` } ) )
let final
for ( let attempt = 0 ; attempt < 30 ; attempt ++ ) {
final = ok ( await context . request ( 'GET' , ` ${ root } /measurement? ${ query } ` ) )
if ( final . processStatus === 'WAIT_SPLIT' && final . measurementStatus === 'APPROVED' ) break
await new Promise ( done => setTimeout ( done , 1000 ) )
}
need ( final . processStatus === 'WAIT_SPLIT' && final . measurementStatus === 'APPROVED' , 'Original recovery did not reach WAIT_SPLIT' )
need ( rows ( final . items ) . length === 2 && rows ( final . items ) . every ( item => declarations . some ( declaration =>
id ( declaration . inboundDetailId ) === id ( item . inboundDetailId ) && Number ( declaration . packageCount ) === Number ( item . packageCount ) ) ) , 'Original SKU measurements retain declaration identities and 3+1 counts' )
context . createdResources . inboundMeasurement = { marker : owner . marker , tenantId : owner . tenantId , inboundId : owner . inboundId ,
orderId : owner . orderId , inboundCode : owner . inboundCode , measurementId : id ( final . measurementId ) ,
finalStatus : final . measurementStatus , processStatus : final . processStatus }
}
export function redact ( value ) {
if ( Array . isArray ( value ) ) return value . map ( redact )
if ( value && typeof value === 'object' ) return Object . fromEntries ( Object . entries ( value ) . map ( ( [ key , child ] ) =>
[ key , /password|token|secret|authorization/i . test ( key ) ? '[REDACTED]' : redact ( child ) ] ) )
return value
}
// This is a bounded producer, not a run of the full acceptance module list.
// The three reused modules only create qualified customer + submitted/approved/measured order.
export async function prepareFreshCases ( runId , context , savePrepared , resume = null ) {
const { run : qualification } = await import ( './admin-test-env-modules/merchant-qualification-fixture.mjs' )
const { run : orders } = await import ( './admin-test-env-modules/orders.mjs' )
const { run : measurement } = await import ( './admin-test-env-modules/inbound-measurement.mjs' )
const { selectSessionPdaOperator } = await import ( './admin-test-env-modules/domestic-pda-operator.mjs' )
const { publishPlatformLogisticsChannels , resolveOceanBookingAgent } = await import ( './admin-test-env-modules/ocean-booking-fixture.mjs' )
const { ensureMatsonCarrierPartner } = await import ( './admin-test-env-modules/carrier-partner-fixture.mjs' )
const { channelGroup } = await import ( './admin-test-env-modules/smart-loading-channel-group.mjs' )
const { iso6346ContainerNumber } = await import ( './admin-test-env-modules/iso6346-container-number.mjs' )
const ok = response => { need ( response . http === 200 && String ( response . body ? . code ) === '200' , ` Fresh fixture rejected ${ response . path } ` ) ; return response . body . data }
const produced = [ ]
for ( const [ kind , definitions ] of [ [ 'A' , [ [ 'REFUND' , 'TAX_REFUND' , [ 3 , 1 ] ] , [ 'BUY' , 'BUY_DECLARATION' , [ 1 ] ] , [ 'SKIP' , 'TAX_REFUND' , [ 1 ] ] ] ] ,
[ 'B' , [ [ 'BUY' , 'BUY_DECLARATION' , [ 2 ] ] , [ 'SKIP' , 'TAX_REFUND' , [ 1 ] ] ] ] ] ) {
const owners = [ ]
for ( const [ role , taxMode , counts ] of definitions ) {
context . marker = ` ${ runId } - ${ kind } - ${ role } `
context . context = { orderProfile : { taxMode , fulfillmentType : 'STANDARD' } , orderFulfillmentType : 'STANDARD' ,
waitSplitVariant : { skuCount : counts . length , packageCounts : counts , packageCountPerSku : counts [ 0 ] } }
context . createdResources = { }
const originalRecovery = resume && kind === 'A' && role === 'REFUND'
let originalMeasurement
if ( originalRecovery ) {
const original = resume . owner
const inbound = ok ( await context . request ( 'GET' , ` /api/platform/inbounds/ ${ original . inboundId } ?tenantId= ${ original . tenantId } ` ) )
const measurementView = ok ( await context . request ( 'GET' , ` ${ LEG } /inbounds/ ${ original . inboundId } /measurement?tenantId= ${ original . tenantId } ` ) )
validateResumeLive ( original , resume . readback , inbound , measurementView )
context . marker = original . marker
context . createdResources . orders = { ... original , receivingWarehouseId : id ( inbound . summary . warehouseId ) ,
fulfillmentType : 'STANDARD' , taxMode : 'TAX_REFUND' }
await savePrepared ( { stage : 'ORIGINAL_RECOVERY_CHECKED' , kind , role , ... original , measurementId : measurementView . measurementId ,
inboundVersion : inbound . summary . version , measurementVersion : measurementView . version } )
originalMeasurement = measurementView
} else {
await qualification ( context , { publishEnvironment : false } )
await orders ( context )
}
await savePrepared ( { stage : 'APPROVED_ORDER' , kind , role , tenantId : id ( context . createdResources . orders ? . tenantId ) ,
inboundId : id ( context . createdResources . orders ? . inboundId ) , orderId : id ( context . createdResources . orders ? . orderId ) } )
if ( originalRecovery ) await resumeMeasurementDraft ( context , resume . owner , originalMeasurement )
else await measurement ( context )
const source = context . createdResources . inboundMeasurement
need ( source ? . inboundId && source . processStatus === 'WAIT_SPLIT' && source . finalStatus === 'APPROVED' , 'New legal measurement did not reach WAIT_SPLIT' )
owners . push ( { role , taxMode , counts , tenantId : id ( source . tenantId ) , inboundId : id ( source . inboundId ) ,
orderId : id ( source . orderId || context . createdResources . orders ? . orderId ) , orderNo : context . createdResources . orders ? . orderNo ,
inboundCode : source . inboundCode || context . createdResources . orders ? . inboundCode } )
await savePrepared ( { stage : 'WAIT_SPLIT' , kind , owners : structuredClone ( owners ) } )
}
context . marker = ` ${ runId } - ${ kind } `
const primary = owners [ 0 ]
const inbound = ok ( await context . request ( 'GET' , ` /api/platform/inbounds/ ${ primary . inboundId } ?tenantId= ${ primary . tenantId } ` ) )
const warehouseId = id ( inbound . summary . warehouseId ) , channelCode = inbound . summary . channelCode
need ( warehouseId && channelCode , 'Fresh source warehouse/channel missing' )
await publishPlatformLogisticsChannels ( context , warehouseId , 'partial-loading-prepare' )
const agent = await resolveOceanBookingAgent ( context , 'partial-loading-prepare' , 'MATSON' , { deploymentBlocker : true } )
const carrier = await ensureMatsonCarrierPartner ( context , 'partial-loading-prepare' , { deploymentBlocker : true } )
need ( agent ? . id && carrier ? . id , 'Existing booking master data required' )
const container = ok ( await context . request ( 'POST' , '/api/platform/v1/containers' , {
channelGroupCode : channelGroup ( channelCode ) , warehouseId , containerType : '40HQ' , capacityCbm : 68 , maxWeightKg : 19500 } ) )
const bookingId = id ( container . id )
let smartPlan = ok ( await context . request ( 'GET' , ` /api/platform/v1/first-leg/smart-loading/plans/by-booking/ ${ bookingId } ` ) )
const planId = id ( smartPlan . id )
await savePrepared ( { stage : 'EDITING' , kind , owners , containerBookingId : bookingId , loadingPlanId : planId } )
const candidateResponse = ok ( await context . request ( 'GET' , ` /api/platform/v1/first-leg/smart-loading/candidates?bookingId= ${ bookingId } &warehouseId= ${ warehouseId } ` ) )
const candidates = rows ( candidateResponse )
for ( const owner of owners ) {
const own = candidates . filter ( item => id ( item . inboundId ) === owner . inboundId && id ( item . accountId ) === owner . tenantId )
. sort ( ( a , b ) => id ( a . inboundDetailId ) . localeCompare ( id ( b . inboundDetailId ) ) )
need ( own . length === owner . counts . length , 'Fresh owner candidate SKU set mismatch' )
for ( const candidate of own ) {
const packages = Number ( candidate . availablePackageCount )
need ( owner . counts . includes ( packages ) , 'Candidate count differs from fresh declared boxes' )
smartPlan = ok ( await context . request ( 'POST' , ` /api/platform/v1/first-leg/smart-loading/plans/ ${ planId } /assignments ` , {
inboundId : owner . inboundId , inboundDetailId : id ( candidate . inboundDetailId ) , packageCount : packages ,
expectedVersion : Number ( smartPlan . version ) , operationId : ` ${ context . marker } -assign- ${ candidate . inboundDetailId } ` } ) )
}
}
const booking = ok ( await context . request ( 'POST' , '/api/platform/v1/ocean-bookings' , { expectedVersion : 0 , data : {
channelCode : channelGroup ( channelCode ) , warehouseId , billOfLadingNo : ` BL- ${ context . marker } ` ,
shippingCompanyId : id ( carrier . id ) , shippingCompanyName : carrier . name , carrierCode : 'MATSON' ,
bookingAgentId : id ( agent . id ) , bookingAgentName : agent . name , vesselName : ` 测试船- ${ context . marker } ` , voyageNo : 'PA-TEST' ,
departurePortCode : 'CNSZX' , departurePortName : '深圳' , destinationPortCode : 'USLAX' , destinationPortName : '洛杉矶' , placeOfDelivery : 'LAX' ,
estimatedSailingAt : '2026-10-05T08:00:00' , estimatedArrivalAt : '2026-10-25T08:00:00' , remark : context . marker ,
parties : [ 'SHIPPER' , 'CONSIGNEE' , 'NOTIFY_PARTY' ] . map ( partyType => ( { partyType , partyName : ` ${ context . marker } - ${ partyType } ` } ) ) ,
containerRequirements : [ { containerType : '40HQ' , containerKind : 'FCL' , quantity : 1 , capacityCbm : 68 , maxWeightKg : 19500 } ] ,
} } , context . token , { 'Idempotency-Key' : ` ${ context . marker } -booking ` } ) )
const oceanId = id ( booking . oceanBooking . id ) , form = new FormData ( )
form . append ( 'expectedVersion' , String ( booking . oceanBooking . version ) ) ; form . append ( 'documentType' , 'BOOKING_CONFIRMATION' )
form . append ( 'file' , new Blob ( [ ` ${ context . marker } controlled TEST booking confirmation ` ] , { type : 'text/plain' } ) , ` ${ context . marker } -booking.txt ` )
const document = ok ( await context . requestMultipart ( 'POST' , ` /api/platform/v1/ocean-bookings/ ${ oceanId } /documents/upload ` , form ,
context . token , { 'Idempotency-Key' : ` ${ context . marker } -booking-file ` } ) )
const released = ok ( await context . request ( 'POST' , ` /api/platform/v1/ocean-bookings/ ${ oceanId } /release ` ,
{ expectedVersion : Number ( document . bookingVersion ) , reason : context . marker } , context . token , { 'Idempotency-Key' : ` ${ context . marker } -release ` } ) )
need ( released . oceanBooking . status === 'RELEASED' , 'New booking release required' )
const currentContainer = ok ( await context . request ( 'GET' , ` /api/platform/v1/containers/domestic/ ${ bookingId } ` ) ) . container
const containerNo = iso6346ContainerNumber ( context . marker )
ok ( await context . request ( 'POST' , ` /api/platform/v1/containers/ ${ bookingId } /ocean-booking-link ` , {
oceanBookingId : oceanId , requirementId : id ( released . containerRequirements [ 0 ] . id ) , containerNo , expectedVersion : currentContainer . version } ) )
const preload = ok ( await context . request ( 'GET' , ` ${ LEG } /loading-plans/ ${ planId } ?warehouseId= ${ warehouseId } ` ) )
const operator = selectSessionPdaOperator ( ok ( await context . request ( 'GET' , ` /api/platform/v1/iam/users/domestic-pda-operators?warehouseId= ${ warehouseId } ` ) ) , context . pdaToken )
const confirmation = ok ( await context . request ( 'POST' , ` ${ LEG } /loading-plans/ ${ planId } /confirm ` ,
{ expectedVersion : Number ( preload . summary . version ) , assigneeId : id ( operator . userId ) } , context . token , { 'Idempotency-Key' : ` ${ context . marker } -confirm ` } ) )
const taskId = id ( confirmation . readiness . pdaTaskId )
const task = ok ( await context . request ( 'GET' , ` ${ PDA } /tasks/ ${ taskId } ?warehouseId= ${ warehouseId } ` , undefined , context . pdaToken ) )
const source = { schemaVersion : 1 , sourceMarker : context . marker , preparedAt : new Date ( ) . toISOString ( ) , stage : 'MANUAL_PDA_CLAIM_AND_BATCH_READY' ,
pdaTaskId : taskId , loadingPlanId : planId , containerBookingId : bookingId , warehouseId , containerNo ,
cabinetNo : container . cabinetNo , oceanBookingId : oceanId , bookingNo : released . oceanBooking . bookingNo , planNo : smartPlan . planNo ,
beforeTaskVersion : task . version , taskItems : task . items . map ( item => ( { id : id ( item . id ) , loadingPlanItemId : id ( item . loadingPlanItemId ) ,
tenantId : id ( item . tenantId ) , inboundId : id ( item . inboundId ) , inboundDetailId : id ( item . inboundDetailId ) , plannedPackageCount : item . plannedPackageCount } ) ) }
const actions = task . items . map ( item => {
const owner = owners . find ( owner => owner . inboundId === id ( item . inboundId ) )
need ( owner , 'Task includes only fresh produced owners' )
const partial = owner === primary && Number ( item . plannedPackageCount ) === ( kind === 'A' ? 3 : 2 )
return { itemId : id ( item . id ) , action : partial ? 'PARTIAL' : owner . role === 'BUY' ? 'PICK' : 'SKIP' ,
... ( partial ? { finalPickedPackageCount : kind === 'A' ? 2 : 1 } : { } ) }
} )
const spec = { kind , actions , sealNo : ` SEAL- ${ context . marker } ` }
const freshPreload = ok ( await context . request ( 'GET' , ` ${ LEG } /loading-plans/ ${ planId } ?warehouseId= ${ warehouseId } ` ) )
expectedRows ( spec , task , freshPreload )
produced . push ( { spec , source , owners } )
await savePrepared ( { stage : 'PDA_UNASSIGNED' , kind , source , owners , spec } )
}
return produced
}
export function validateManifest ( manifest , runId ) {
need ( manifest ? . schemaVersion === 1 && manifest . environment === 'TEST' && manifest . runId === runId , 'Fixture run/environment mismatch' )
need ( rows ( manifest . cases ) . length === 2 && manifest . cases [ 0 ] . kind === 'A' && manifest . cases [ 1 ] . kind === 'B' , 'Exactly A then B required' )
for ( const item of manifest . cases ) {
need ( typeof item . sourceReport === 'string' && item . sourceReport . length > 0 , 'New official fixture source report required' )
need ( rows ( item . actions ) . length >= 2 && new Set ( item . actions . map ( row => row . itemId ) ) . size === item . actions . length , 'Every PDA item needs one unique action' )
for ( const action of item . actions ) need ( positive ( action . itemId ) && [ 'PICK' , 'PARTIAL' , 'SKIP' ] . includes ( action . action )
&& ( action . action !== 'PARTIAL' || Number . isSafeInteger ( action . finalPickedPackageCount ) && action . finalPickedPackageCount > 0 ) , 'Invalid PDA action' )
need ( typeof item . sealNo === 'string' && item . sealNo . trim ( ) . length > 0 && item . sealNo . length <= 64 , 'Seal required' )
need ( TYPES . every ( type => typeof item . evidence ? . [ type ] === 'string' && item . evidence [ type ] ) , 'Five supplied evidence paths required' )
}
return manifest
}
export function fixtureSource ( report , runId , gateway , now = Date . now ( ) ) {
const value = report ? . createdResources ? . manualPdaBatchFixture
need ( report . success === true && report . fatal == null && report . summary ? . failed === 0
&& report . gateway ? . replace ( /\/$/ , '' ) === gateway , 'Official fixture report did not pass in this TEST Gateway' )
need ( value ? . schemaVersion === 1 && value . stage === 'MANUAL_PDA_CLAIM_AND_BATCH_READY'
&& typeof value . sourceMarker === 'string' && value . sourceMarker . startsWith ( ` ${ runId } - ` )
&& /^[A-Za-z0-9_-]{1,80}$/ . test ( value . sourceMarker ) , 'Fresh run-bound PDA preparation required' )
const age = now - Date . parse ( value . preparedAt )
need ( Number . isFinite ( age ) && age >= 0 && age <= 24 * 3600_000 , 'Fixture must be prepared within this run/day' )
for ( const key of [ 'pdaTaskId' , 'loadingPlanId' , 'containerBookingId' , 'warehouseId' ] ) need ( positive ( value [ key ] ) , ` Invalid source ${ key } ` )
need ( rows ( value . taskItems ) . length > 0 , 'Missing original PDA identities' )
return value
}
// Decimal arithmetic is exact; no binary floating point at HALF_UP boundaries.
export function scale ( value , actual , planned , precision ) {
if ( value == null ) return null
const text = String ( value )
need ( /^\d+(?:\.\d+)?$/ . test ( text ) && Number . isSafeInteger ( actual ) && actual > 0
&& Number . isSafeInteger ( planned ) && planned >= actual , 'Invalid nonnegative frozen decimal/ratio' )
const [ whole , fraction = '' ] = text . split ( '.' )
const numerator = BigInt ( whole + fraction ) * BigInt ( actual ) * 10 n * * BigInt ( precision )
const denominator = BigInt ( planned ) * 10 n * * BigInt ( fraction . length )
const rounded = ( 2 n * numerator + denominator ) / ( 2 n * denominator )
const padded = rounded . toString ( ) . padStart ( precision + 1 , '0' )
return precision ? ` ${ padded . slice ( 0 , - precision ) } . ${ padded . slice ( - precision ) } ` : padded
}
export function expectedRows ( caseSpec , task , preload ) {
const byId = new Map ( rows ( task . items ) . map ( item => [ id ( item . id ) , item ] ) )
need ( byId . size === task . items . length && byId . size === caseSpec . actions . length
&& caseSpec . actions . every ( item => byId . has ( item . itemId ) ) , 'Actions must cover every live PDA item exactly once' )
const planById = new Map ( rows ( preload . items ) . map ( item => [ id ( item . itemId ) , item ] ) )
need ( planById . size === byId . size , 'Preload and PDA scope mismatch' )
const expected = caseSpec . actions . map ( action => {
const pick = byId . get ( action . itemId ) , frozen = planById . get ( id ( pick . loadingPlanItemId ) )
need ( frozen && id ( frozen . accountId ) === id ( pick . tenantId ) && id ( frozen . inboundId ) === id ( pick . inboundId )
&& id ( frozen . inboundDetailId ) === id ( pick . inboundDetailId ) , 'Frozen source/customer/detail identity mismatch' )
const planned = Number ( pick . plannedPackageCount )
need ( pick . itemStatus === 'PENDING' && Number ( pick . pickedPackageCount ) === 0 && pick . unitType === 'PACKAGE'
&& ! pick . palletId && planned > 0 && Number ( frozen . packageCount ) === planned , 'Require zero-picked PACKAGE sources' )
const actual = action . action === 'SKIP' ? 0 : action . action === 'PICK' ? planned : action . finalPickedPackageCount
need ( action . action !== 'PARTIAL' || actual < planned , 'PARTIAL must be less than original plan' )
return { action : action . action , pickId : action . itemId , itemId : id ( frozen . itemId ) , accountId : id ( frozen . accountId ) ,
inboundId : id ( frozen . inboundId ) , inboundDetailId : id ( frozen . inboundDetailId ) , taxMode : frozen . customsTaxMode ,
planned , actual , frozen , orderId : id ( frozen . orderId ) }
} )
const partial = expected . filter ( item => item . action === 'PARTIAL' ) , full = expected . filter ( item => item . action === 'PICK' )
const skipped = expected . filter ( item => item . action === 'SKIP' ) , active = expected . filter ( item => item . actual > 0 )
need ( expected . every ( item => [ 'TAX_REFUND' , 'BUY_DECLARATION' ] . includes ( item . taxMode ) ) , 'Authoritative tax mode required' )
if ( caseSpec . kind === 'A' ) {
need ( partial . length === 1 && partial [ 0 ] . taxMode === 'TAX_REFUND' && partial [ 0 ] . planned === 3 && partial [ 0 ] . actual === 2
&& full . length === 1 && full [ 0 ] . taxMode === 'BUY_DECLARATION' && full [ 0 ] . accountId !== partial [ 0 ] . accountId
&& skipped . some ( item => item . inboundId === partial [ 0 ] . inboundId )
&& skipped . some ( item => item . taxMode === 'TAX_REFUND' && ! active . some ( row => row . accountId === item . accountId ) ) , 'A needs mixed customers, partial refund, same-source skip and entirely skipped refund customer' )
} else {
need ( active . length === 1 && partial . length === 1 && full . length === 0
&& partial [ 0 ] . taxMode === 'BUY_DECLARATION' && partial [ 0 ] . planned === 2 && partial [ 0 ] . actual === 1
&& skipped . length > 0 && skipped . every ( item => item . taxMode === 'TAX_REFUND' ) , 'B must have exactly one actual BUY PARTIAL row and only skipped refund rows' )
}
return expected
}
export function verifyActual ( expected , detail , customs ) {
const active = expected . filter ( item => item . actual > 0 )
assert . deepEqual ( rows ( detail . items ) . map ( item => id ( item . itemId ) ) . sort ( ) , active . map ( item => item . itemId ) . sort ( ) , 'Actual loading row set' )
for ( const item of active ) {
const actual = detail . items . find ( row => id ( row . itemId ) === item . itemId ) , frozen = item . frozen
assert . equal ( Number ( actual . packageCount ) , item . actual , 'Actual boxes' )
assert . equal ( id ( actual . accountId ) , item . accountId , 'Actual customer' )
assert . equal ( id ( actual . inboundId ) , item . inboundId , 'Actual source' )
const units = Number ( frozen . unitsPerPackage )
const quantity = item . actual === item . planned ? Number ( frozen . productQuantity )
: units > 0 ? units * item . actual : Number ( scale ( frozen . productQuantity , item . actual , item . planned , 0 ) )
assert . equal ( Number ( actual . productQuantity ) , quantity , 'Actual units' )
for ( const [ field , precision ] of [ [ 'grossWeightKg' , 3 ] , [ 'netWeightKg' , 3 ] , [ 'dimensionalWeightKg' , 3 ] , [ 'volumeCbm' , 4 ] ] )
assert . equal ( scale ( actual [ field ] , 1 , 1 , precision ) , scale ( frozen [ field ] , item . actual , item . planned , precision ) , ` Actual ${ field } ` )
}
const boxes = active . reduce ( ( sum , item ) => sum + item . actual , 0 )
assert . equal ( Number ( detail . summary . totalPackageCount ) , boxes , 'Actual total boxes' )
assert . equal ( Number ( customs . actualBoxCount ) , boxes , 'Customs actual boxes' )
const inboundKeys = values => [ ... new Set ( values . map ( item => ` ${ id ( item . tenantId ? ? item . accountId ) } : ${ id ( item . inboundId ) } ` ) ) ] . sort ( )
assert . deepEqual ( inboundKeys ( rows ( customs . inbounds ) ) , inboundKeys ( active ) , 'Customs source/customer set' )
assert . equal ( Number ( customs . taxRefundCount ) , new Set ( active . filter ( item => item . taxMode === 'TAX_REFUND' ) . map ( item => item . inboundId ) ) . size , 'Refund coverage excludes skipped rows' )
assert . equal ( Number ( detail . summary . totalQuantity ) , detail . items . reduce ( ( sum , item ) => sum + Number ( item . productQuantity ) , 0 ) , 'Quantity total' )
for ( const [ field , summary , precision ] of [ [ 'grossWeightKg' , 'totalGrossWeightKg' , 3 ] , [ 'netWeightKg' , 'totalNetWeightKg' , 3 ] , [ 'volumeCbm' , 'totalVolumeCbm' , 4 ] ] ) {
const sum = detail . items . reduce ( ( value , item ) => value + BigInt ( scale ( item [ field ] ? ? 0 , 1 , 1 , precision ) . replace ( '.' , '' ) ) , 0 n )
assert . equal ( BigInt ( scale ( detail . summary [ summary ] ? ? 0 , 1 , 1 , precision ) . replace ( '.' , '' ) ) , sum , ` Actual ${ summary } ` )
}
return { actualBoxes : boxes , actualRows : active . length , taxRefundCount : Number ( customs . taxRefundCount ) }
}
export async function runCase ( spec , source , prepared , request , sleep = ms => new Promise ( done => setTimeout ( done , ms ) ) ) {
const { expected , task , images } = prepared
const path = ` ${ PDA } /tasks/ ${ source . pdaTaskId } ` , key = suffix => ` ${ source . sourceMarker } - ${ suffix } ` . slice ( 0 , 128 )
const data = response => { need ( response . http === 200 && String ( response . body ? . code ) === '200' , ` API rejected ${ response . path } : HTTP= ${ response . http } , code= ${ response . body ? . code ? ? 'missing' } ` ) ; return response . body . data }
const readTask = async ( ) => data ( await request ( 'GET' , ` ${ path } ?warehouseId= ${ source . warehouseId } ` , undefined , 'pda' ) )
const claimed = data ( await request ( 'POST' , ` ${ path } /claim ` , { expectedAssignmentVersion : Number ( task . assignment ? . version ? ? 0 ) , operationId : key ( 'claim' ) } , 'pda' ) )
need ( claimed . workflowStatus === 'IN_PROGRESS' , 'Claim must enter IN_PROGRESS' )
let picked
// Batch must contain every still-pending row, and forbids SKIP + physical picks
// for one owner in the same command. Resolve that owner's zero-picked SKU through
// the formal individual SKIP first; then one atomic batch handles all remaining rows.
const individualSkips = expected . filter ( item => item . action === 'SKIP'
&& expected . some ( other => other . inboundId === item . inboundId && other . actual > 0 ) )
for ( const item of individualSkips ) {
const current = await readTask ( ) , row = current . items . find ( row => id ( row . id ) === item . pickId )
data ( await request ( 'POST' , ` ${ path } /items/ ${ item . pickId } /skip ` , { warehouseId : source . warehouseId ,
version : Number ( row . version ) , operationId : key ( ` skip- ${ item . pickId } ` ) , reason : source . sourceMarker } , 'pda' ) )
}
const lines = expected . filter ( item => ! individualSkips . some ( skip => skip . pickId === item . pickId ) )
{
const current = await readTask ( )
const inventory = data ( await request ( 'GET' , ` ${ path } /inventory-snapshot?warehouseId= ${ source . warehouseId } ` , undefined , 'pda' ) )
const batch = { warehouseId : source . warehouseId , loadingPlanId : source . loadingPlanId , containerBookingId : source . containerBookingId ,
expectedVersion : Number ( current . version ) , inventorySnapshotToken : inventory . snapshotToken ,
items : lines . map ( item => ( { itemId : item . pickId , action : item . action ,
version : Number ( current . items . find ( row => id ( row . id ) === item . pickId ) ? . version ) ,
... ( item . action === 'PARTIAL' ? { finalPickedPackageCount : item . actual } : item . action === 'PICK' ? { pickedPackageCount : item . actual } : { reason : source . sourceMarker } ) } ) ) }
need ( inventory . snapshotToken && batch . items . every ( item => Number . isSafeInteger ( item . version ) ) , 'Fresh inventory and item versions required' )
picked = data ( await request ( 'POST' , ` ${ path } /batch ` , batch , 'pda' , { 'Idempotency-Key' : key ( 'resolved-batch' ) } ) )
}
need ( picked . workflowStatus === 'AWAITING_REVIEW' && expected . every ( item => {
const row = rows ( picked . items ) . find ( row => id ( row . id ) === item . pickId )
return row && Number ( row . pickedPackageCount ) === item . actual && row . itemStatus === ( { PICK : 'PICKED' , PARTIAL : 'PARTIAL' , SKIP : 'SKIPPED' } ) [ item . action ]
} ) , 'Resolved picks and all-PARTIAL B must be persisted before completion' )
for ( const type of TYPES ) {
const image = images [ type ] , form = new FormData ( )
for ( const [ name , value ] of Object . entries ( { warehouseId : source . warehouseId , expectedVersion : 0 , evidenceType : type ,
remark : source . sourceMarker , ... ( type === 'SEAL' ? { sealNo : spec . sealNo } : { } ) } ) ) form . append ( name , String ( value ) )
form . append ( 'file' , new Blob ( [ image . bytes ] , { type : image . type } ) , ` ${ source . sourceMarker } - ${ type } . ${ image . type === 'image/png' ? 'png' : 'jpg' } ` )
data ( await request ( 'POST' , ` ${ path } /evidence ` , form , 'pda' , { 'Idempotency-Key' : key ( ` evidence- ${ type } ` ) } ) )
}
const evidence = data ( await request ( 'GET' , ` ${ path } /evidence?warehouseId= ${ source . warehouseId } ` , undefined , 'pda' ) )
need ( TYPES . every ( type => rows ( evidence . evidences ) . some ( item => item . evidenceType === type && item . status === 'ACTIVE'
&& ( type !== 'SEAL' || item . sealNo === spec . sealNo ) ) ) , 'Five active evidence types and seal required' )
const ready = await readTask ( ) , body = { expectedTaskVersion : Number ( ready . version ) , operationId : key ( 'complete' ) , remark : source . sourceMarker }
data ( await request ( 'POST' , ` ${ path } /complete ` , body , 'pda' ) )
let completed
for ( let attempt = 0 ; attempt < 60 ; attempt ++ ) {
completed = await readTask ( )
if ( completed . taskStatus === 'COMPLETED' && completed . workflowStatus === 'COMPLETED' ) break
need ( completed . taskStatus === 'COMPLETING' , ` Completion stopped: task= ${ completed . taskStatus } , command= ${ completed . completionCommand ? . status ? ? 'missing' } , stage= ${ completed . completionCommand ? . failureStage ? ? 'missing' } , code= ${ completed . completionCommand ? . failureCode ? ? 'missing' } ` )
await sleep ( 1000 )
}
need ( completed . taskStatus === 'COMPLETED' && completed . workflowStatus === 'COMPLETED' , 'Completion polling exhausted' )
const snapshot = async ( ) => ( { task : await readTask ( ) ,
detail : data ( await request ( 'GET' , ` ${ LEG } /loading-lists/ ${ source . loadingPlanId } ?warehouseId= ${ source . warehouseId } ` ) ) ,
customs : data ( await request ( 'GET' , ` ${ CUSTOMS } / ${ source . loadingPlanId } ` ) ) ,
operations : data ( await request ( 'GET' , ` ${ path } /operations ` , undefined , 'pda' ) ) } )
const before = await snapshot ( ) , facts = verifyActual ( expected , before . detail , before . customs )
need ( rows ( before . operations ) . filter ( item => item . actionType === 'COMPLETE_LOADING' ) . length === 1 , 'One COMPLETE_LOADING audit required' )
data ( await request ( 'POST' , ` ${ path } /complete ` , body , 'pda' ) ) // original key, version and body
const after = await snapshot ( )
assert . equal ( stableSnapshot ( after ) , stableSnapshot ( before ) , 'Exact completion replay has zero projection/version/history drift' )
if ( spec . kind === 'B' ) {
need ( before . customs . taxRefundCount === 0 && before . customs . status === 'READY_TO_START' , 'Actual all-buy readiness' )
const start = data ( await request ( 'POST' , ` ${ CUSTOMS } / ${ source . loadingPlanId } /start ` ,
{ expectedVersion : before . customs . version , confirm : true , remark : source . sourceMarker } , 'admin' , { 'Idempotency-Key' : key ( 'customs-start' ) } ) )
need ( start . status === 'DECLARING' , 'All-buy start' )
const reject = await request ( 'POST' , ` ${ CUSTOMS } / ${ source . loadingPlanId } /send-to-broker ` ,
{ expectedVersion : start . version , confirm : true , remark : source . sourceMarker } , 'admin' , { 'Idempotency-Key' : key ( 'refund-send-negative' ) } )
need ( reject . http === 200 && String ( reject . body ? . code ) === 'A0409' , 'All-buy must reject refund send' )
const unchanged = data ( await request ( 'GET' , ` ${ CUSTOMS } / ${ source . loadingPlanId } ` ) )
assert . equal ( stableSnapshot ( unchanged ) , stableSnapshot ( start ) , 'Rejected refund send leaves customs unchanged' )
const result = data ( await request ( 'POST' , ` ${ CUSTOMS } / ${ source . loadingPlanId } /complete-all-buy ` ,
{ expectedVersion : unchanged . version , confirm : true , remark : source . sourceMarker } , 'admin' , { 'Idempotency-Key' : key ( 'customs-complete' ) } ) )
need ( result . status === 'COMPLETED' , 'All-buy completion' )
}
return { kind : spec . kind , ... facts , source , actions : spec . actions , allActualRowsPartial : expected . filter ( item => item . actual > 0 ) . every ( item => item . action === 'PARTIAL' ) ,
completed : true , completionReplayZeroDrift : true }
}
export async function execute ( args = process . argv . slice ( 2 ) , execArgs = process . execArgv , env = process . env ) {
const options = executionOptions ( args , execArgs , env )
if ( ! options . execute ) return plan ( )
const directory = resolve ( ROOT , 'reports' , ` partial-loading-actual- ${ options . runId } ` )
const bOnlyMode = typeof options . fixture === 'object' && options . fixture . bOnlyReport
const verifyAMode = typeof options . fixture === 'object' && options . fixture . verifyAReport
const completionMode = typeof options . fixture === 'object' && ( options . fixture . completionReport || options . fixture . verifyAReport )
const preparedMode = typeof options . fixture === 'object' && options . fixture . preparedReport
const resumeMode = typeof options . fixture === 'object' && options . fixture . resumeReport
const fresh = options . fixture === 'NEW' || resumeMode
let resume
if ( resumeMode ) {
const originalBytes = await readFile ( options . fixture . resumeReport )
const readbackPath = resolve ( dirname ( options . fixture . resumeReport ) , 'blocked-readback.json' )
const readbackBytes = await readFile ( readbackPath ) , readback = JSON . parse ( readbackBytes )
const original = JSON . parse ( originalBytes )
resume = { owner : validateResumeProof ( original , readback ) , readback ,
originalReport : options . fixture . resumeReport , originalSha256 : sha ( originalBytes ) , readbackSha256 : sha ( readbackBytes ) }
const attemptedPath = resolve ( ROOT , 'reports/partial-loading-actual-PARTIAL-ACTUAL-20261004-recovery-r1/report.json' )
let attemptedBytes
try { attemptedBytes = await readFile ( attemptedPath ) } catch ( error ) { if ( error . code !== 'ENOENT' ) throw error }
if ( attemptedBytes ) {
const attempted = JSON . parse ( attemptedBytes )
const mutations = rows ( attempted . requests ) . filter ( item => item . method !== 'GET' && ! item . path . startsWith ( '/api/auth/' ) && ! item . path . includes ( 'carton-label-pdf' ) )
need ( attempted . success === false && attempted . recovery ? . originalSha256 === resume . originalSha256
&& mutations . length === 1 && mutations [ 0 ] . path === ` ${ LEG } /inbounds/ ${ resume . owner . inboundId } /measurement/carton-scans?tenantId= ${ resume . owner . tenantId } `
&& mutations [ 0 ] . http === 200 && mutations [ 0 ] . code === '200' && mutations [ 0 ] . response . data . created === false
&& mutations [ 0 ] . response . data . measurementVersion === readback . measurement . version + 1 ,
'Prior recovery must prove its sole accepted original re-scan; no blind retry' )
resume . readback = structuredClone ( readback )
resume . readback . measurement . version = mutations [ 0 ] . response . data . measurementVersion
resume . readback . measurement . measurementStatus = 'RECEIVED' // the proven scan changed this draft into RECEIVED
resume . priorRecovery = { report : attemptedPath , sha256 : sha ( attemptedBytes ) ,
knownOriginalRescanRequestId : mutations [ 0 ] . requestId , expectedMeasurementVersion : resume . readback . measurement . version ,
expectedMeasurementStatus : 'RECEIVED' }
}
}
const manifestBytes = bOnlyMode ? await readFile ( options . fixture . bOnlyReport ) : completionMode ? await readFile ( options . fixture . completionReport || options . fixture . verifyAReport ) : preparedMode ? await readFile ( options . fixture . preparedReport ) : fresh ? Buffer . from ( resumeMode ? 'RESUME' : 'NEW' ) : await readFile ( options . fixture )
const manifest = fresh || preparedMode || completionMode || bOnlyMode ? null : validateManifest ( JSON . parse ( manifestBytes ) , options . runId )
const sources = [ ] , specs = [ ]
let original
let originalBefore
let aEventProof
if ( bOnlyMode ) {
const eventProofBytes = await readFile ( resolve ( ROOT , 'reports/partial-A-completion-event-proof-20261004-r1.json' ) )
const eventProof = JSON . parse ( eventProofBytes )
need ( eventProof . readOnly === true && eventProof . verified === true && Object . values ( eventProof . checks ) . every ( value => value === true )
&& rows ( eventProof . rows ) . length === 2 && rows ( eventProof . rows ) . every ( row => row . status === 'PROCESSED' && row . eventVersion === 3 ) ,
'Controller exact A Outbox/ACK/Inbox proof required before B' )
aEventProof = { report : 'reports/partial-A-completion-event-proof-20261004-r1.json' , sha256 : sha ( eventProofBytes ) , checks : eventProof . checks , events : eventProof . rows }
let evidence = JSON . parse ( manifestBytes )
need ( evidence . success === true && evidence . cases ? . length === 1 && evidence . cases [ 0 ] . kind === 'A'
&& evidence . cases [ 0 ] . originalCommandId === '2106716718935052290' && evidence . cases [ 0 ] . actualPackages === 3
&& evidence . cases [ 0 ] . oldOwnersAndDomesticAllocationSnapshotsUnchanged === true && evidence . cases [ 0 ] . wholeSkippedOwnerNotLoaded === true ,
'B requires the verified original A recovery first' )
for ( let index = 0 ; index < 2 ; index ++ ) {
const parent = await readFile ( evidence . completionRecovery . originalReport )
need ( sha ( parent ) === evidence . completionRecovery . originalSha256 , 'A evidence chain SHA changed' )
evidence = JSON . parse ( parent )
}
originalCompletionCommand ( evidence )
const preparationBytes = await readFile ( evidence . preparedRecovery . report )
need ( sha ( preparationBytes ) === evidence . preparedRecovery . sha256 , 'Original prepared B SHA changed' )
const preparation = JSON . parse ( preparationBytes ) , checkpoint = validatePreparedResume ( preparation ) [ 1 ]
const taskPath = ` ${ PDA } /tasks/ ${ checkpoint . source . pdaTaskId } ?warehouseId= ${ checkpoint . source . warehouseId } `
const preloadPath = ` ${ LEG } /loading-plans/ ${ checkpoint . source . loadingPlanId } ?warehouseId= ${ checkpoint . source . warehouseId } `
const initialTask = rows ( preparation . requests ) . filter ( row => row . path === taskPath && row . method === 'GET' ) . at ( - 1 ) . response . data
const initialPreload = rows ( preparation . requests ) . filter ( row => row . path === preloadPath && row . method === 'GET' ) . at ( - 1 ) . response . data
const bytes = Buffer . from ( 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=' , 'base64' )
specs . push ( { ... checkpoint , expected : expectedRows ( checkpoint . spec , initialTask , initialPreload ) ,
images : Object . fromEntries ( TYPES . map ( type => [ type , { bytes , type : 'image/png' , sha256 : sha ( bytes ) } ] ) ) } ) ; sources . push ( checkpoint . source )
}
if ( completionMode ) {
let failed = JSON . parse ( manifestBytes )
if ( verifyAMode ) {
originalBefore = rows ( failed . checkpoints ) . find ( row => row . stage === 'A_BEFORE_ORIGINAL_RECOVERY' ) ? . before
need ( originalBefore && failed . completionRecovery ? . originalReport , 'Original recovery before snapshot required' )
const originalBytes = await readFile ( failed . completionRecovery . originalReport )
need ( sha ( originalBytes ) === failed . completionRecovery . originalSha256 , 'Original failed report SHA changed' )
failed = JSON . parse ( originalBytes )
}
original = originalCompletionCommand ( failed )
const preparationBytes = await readFile ( failed . preparedRecovery . report )
need ( sha ( preparationBytes ) === failed . preparedRecovery . sha256 , 'Original preparation report SHA changed' )
const preparation = JSON . parse ( preparationBytes )
for ( const checkpoint of validatePreparedResume ( preparation ) ) {
const taskPath = ` ${ PDA } /tasks/ ${ checkpoint . source . pdaTaskId } ?warehouseId= ${ checkpoint . source . warehouseId } `
const preloadPath = ` ${ LEG } /loading-plans/ ${ checkpoint . source . loadingPlanId } ?warehouseId= ${ checkpoint . source . warehouseId } `
const initialTask = rows ( preparation . requests ) . filter ( row => row . path === taskPath && row . method === 'GET' ) . at ( - 1 ) ? . response ? . data
const initialPreload = rows ( preparation . requests ) . filter ( row => row . path === preloadPath && row . method === 'GET' ) . at ( - 1 ) ? . response ? . data
specs . push ( { ... checkpoint , expected : expectedRows ( checkpoint . spec , initialTask , initialPreload ) } ) ; sources . push ( checkpoint . source )
}
}
if ( preparedMode ) {
const bytes = Buffer . from ( 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=' , 'base64' )
for ( const checkpoint of validatePreparedResume ( JSON . parse ( manifestBytes ) ) ) {
specs . push ( { ... checkpoint , images : Object . fromEntries ( TYPES . map ( type => [ type , { bytes , type : 'image/png' , sha256 : sha ( bytes ) } ] ) ) } )
sources . push ( checkpoint . source )
}
}
for ( const spec of manifest ? . cases || [ ] ) {
const sourceBytes = await readFile ( resolve ( dirname ( options . fixture ) , spec . sourceReport ) )
const source = fixtureSource ( JSON . parse ( sourceBytes ) , options . runId , options . gateway )
const images = { }
for ( const type of TYPES ) {
const bytes = await readFile ( resolve ( dirname ( options . fixture ) , spec . evidence [ type ] ) )
const png = bytes . subarray ( 0 , 8 ) . equals ( Buffer . from ( [ 137 , 80 , 78 , 71 , 13 , 10 , 26 , 10 ] ) )
const jpeg = bytes [ 0 ] === 255 && bytes [ 1 ] === 216 && bytes [ 2 ] === 255
need ( bytes . length > 16 && bytes . length <= 20 * 1024 * 1024 && ( png || jpeg ) , 'Evidence must be supplied bounded PNG/JPEG bytes' )
images [ type ] = { bytes , type : png ? 'image/png' : 'image/jpeg' , sha256 : sha ( bytes ) }
}
sources . push ( source ) ; specs . push ( { spec , source , sourceSha256 : sha ( sourceBytes ) , images } )
}
if ( ! fresh && ! bOnlyMode ) {
for ( const key of [ 'pdaTaskId' , 'loadingPlanId' , 'containerBookingId' ] ) need ( new Set ( sources . map ( value => value [ key ] ) ) . size === 2 , 'Two independent new cabinet identities required' )
need ( ! sources [ 0 ] . taskItems . some ( a => sources [ 1 ] . taskItems . some ( b => id ( a . inboundId ) === id ( b . inboundId ) ) ) , 'Cases cannot share source inbound resources' )
}
await mkdir ( directory ) // exclusive; never overwrite evidence or silently rerun business writes
const report = { success : false , runId : options . runId , startedAt : new Date ( ) . toISOString ( ) , manifestSha256 : sha ( manifestBytes ) , requests : [ ] , cases : [ ] , sessions : { } ,
... ( bOnlyMode ? { BContinuation : { verifiedAReport : options . fixture . bOnlyReport , verifiedASha256 : sha ( manifestBytes ) , readyCommit : options . readyCommit , aEventProof } , syntheticHttpEvidence : true } : { } ) ,
... ( completionMode ? { completionRecovery : { originalReport : options . fixture . completionReport || options . fixture . verifyAReport , originalSha256 : sha ( manifestBytes ) , original , readyCommit : options . readyCommit , BDeferred : true , readOnly : Boolean ( verifyAMode ) } } : { } ) ,
... ( preparedMode ? { preparedRecovery : { report : options . fixture . preparedReport , sha256 : sha ( manifestBytes ) } , syntheticHttpEvidence : true } : { } ) ,
... ( resume ? { recovery : { originalReport : resume . originalReport , originalSha256 : resume . originalSha256 ,
readbackSha256 : resume . readbackSha256 , originalOwner : resume . owner , originalCreationSkipped : true , priorRecovery : resume . priorRecovery } } : { } ) ,
scope : completionMode ? [ 'direct Outbox/ACK audit' , 'raw Warehouse SQL audit' , 'browser/device acceptance' , 'B deferred in this dispatch' ] : plan ( ) . excluded ,
fixtures : specs . map ( ( { spec , source , sourceSha256 , images } ) => ( { kind : spec . kind , source , sourceSha256 ,
... ( images ? { imageHashes : Object . fromEntries ( TYPES . map ( type => [ type , images [ type ] . sha256 ] ) ) } : { } ) } ) ) }
const sessions = { } , save = ( ) => writeFile ( resolve ( directory , 'report.json' ) , JSON . stringify ( redact ( report ) , null , 2 ) )
const nativeFetch = globalThis . fetch
let requestQueue = Promise . resolve ( ) , transportUnknown = false
const serialFetch = ( url , init ) => {
const pending = requestQueue . then ( ( ) => { need ( ! transportUnknown || init . method === 'GET' || new URL ( url ) . pathname === '/api/auth/logout' , 'Earlier unknown transport result; stop new commands and preserve samples' ) ; return nativeFetch ( url , init ) } )
requestQueue = pending . then ( ( ) => { } , ( ) => { transportUnknown = true } )
return pending
}
const recordSession = ( payload , role ) => {
if ( payload ? . data ? . accessToken ) {
const already = Object . entries ( sessions ) . find ( ( [ , value ] ) => value . token === payload . data . accessToken )
const name = already ? . [ 0 ] || ( role === 'admin' || role === 'pda' ? role : ` customer- ${ Object . keys ( sessions ) . length } ` )
sessions [ name ] = { token : payload . data . accessToken , refresh : payload . data . refreshToken }
}
}
async function request ( method , path , body , role = 'admin' , headers = { } ) {
need ( ! verifyAMode || method === 'GET' || [ '/api/auth/login' , '/api/auth/logout' ] . includes ( path ) , 'A post-recovery verification is read-only' )
const auth = path . startsWith ( '/api/auth/' )
const entry = { method , path , role , intendedAt : new Date ( ) . toISOString ( ) , state : 'PENDING' ,
... ( auth ? { } : body instanceof FormData ? { multipart : true } : body === undefined ? { } : { request : body } ) ,
idempotencyKey : headers [ 'Idempotency-Key' ] }
if ( body instanceof FormData ) {
entry . multipartParts = [ ]
for ( const [ name , value ] of body . entries ( ) ) entry . multipartParts . push ( typeof value === 'string' ? { name , value }
: { name , fileName : value . name , size : value . size , type : value . type , sha256 : sha ( new Uint8Array ( await value . arrayBuffer ( ) ) ) } )
}
report . requests . push ( entry ) ; await save ( )
try {
const response = await serialFetch ( options . gateway + path , requestInit ( method , body , sessions [ role ] ? . token , headers ) )
const payload = await response . json ( )
if ( path === '/api/auth/login' ) recordSession ( payload , role )
Object . assign ( entry , { state : 'RECEIVED' , http : response . status , code : payload . code , requestId : payload . requestId , traceId : payload . traceId ,
... ( auth ? { } : { response : payload } ) } ) ; await save ( )
return { method , path , http : response . status , body : payload , requestId : payload . requestId , traceId : payload . traceId }
} catch { entry . state = 'UNKNOWN' ; await save ( ) ; throw new CheckFailure ( ` Transport/response failed: ${ method } ${ path } ; preserve original request, do not auto-retry ` ) }
}
try {
for ( const [ role , username , password ] of [ [ 'admin' , env . CHONGCHONG _ADMIN _USERNAME , env . CHONGCHONG _ADMIN _PASSWORD ] , [ 'pda' , env . ADMIN _PDA _USERNAME , env . ADMIN _PDA _PASSWORD ] ] ) {
const response = await request ( 'POST' , '/api/auth/login' , { loginId : username , password } , role )
const data = response . body ? . data
if ( data ? . accessToken ) sessions [ role ] = { token : data . accessToken , refresh : data . refreshToken }
need ( response . http === 200 && String ( response . body ? . code ) === '200' && data ? . accessToken && data ? . refreshToken , 'Incomplete own login session' )
report . sessions [ role ] = { loggedIn : true , closed : false }
}
if ( fresh ) {
// Reused producers have a few raw binary/multipart fetches. Instrument them too,
// serialize through the same queue, persist intent first, and never expose credentials.
globalThis . fetch = async ( url , init = { } ) => {
const parsed = new URL ( url ) ; need ( parsed . origin === options . gateway , 'Producer network must stay in TEST Gateway' )
const entry = { method : init . method || 'GET' , path : parsed . pathname + parsed . search , producerRaw : true , intendedAt : new Date ( ) . toISOString ( ) , state : 'PENDING' }
const form = init . body instanceof FormData
if ( form ) {
entry . multipart = [ ]
for ( const [ name , value ] of init . body . entries ( ) ) entry . multipart . push ( typeof value === 'string'
? { name , value } : { name , size : value . size , type : value . type , sha256 : sha ( new Uint8Array ( await value . arrayBuffer ( ) ) ) } )
} else if ( init . body ) { try { entry . request = redact ( JSON . parse ( init . body ) ) } catch { entry . requestSha256 = sha ( String ( init . body ) ) } }
report . requests . push ( entry ) ; await save ( )
let response
try { response = await serialFetch ( url , { ... init , redirect : 'error' } ) }
catch { entry . state = 'UNKNOWN' ; await save ( ) ; throw new CheckFailure ( ` Producer transport unknown: ${ entry . method } ${ entry . path } ` ) }
const bytes = new Uint8Array ( await response . clone ( ) . arrayBuffer ( ) )
entry . http = response . status ; entry . state = 'RECEIVED'
if ( ( response . headers . get ( 'content-type' ) || '' ) . includes ( 'json' ) ) {
const payload = JSON . parse ( new TextDecoder ( ) . decode ( bytes ) ) ; entry . code = payload . code
entry . requestId = payload . requestId ; entry . traceId = payload . traceId ; entry . response = redact ( payload )
if ( parsed . pathname === '/api/auth/login' ) recordSession ( payload , 'producer' )
} else { entry . size = bytes . length ; entry . sha256 = sha ( bytes ) }
await save ( ) ; return response
}
const { AcceptanceRun , parseArguments } = await import ( './admin-test-env-acceptance.mjs' )
const context = new AcceptanceRun ( { ... parseArguments ( [ '--modules' , 'orders,inbound-measurement' , '--quiet' , 'true' ] ) , gateway : options . gateway ,
username : env . CHONGCHONG _ADMIN _USERNAME , password : env . CHONGCHONG _ADMIN _PASSWORD , customerName : '' , waitSplitCount : 1 ,
routeMode : 'fixed' , orderProfileMode : 'fixed' } )
context . token = sessions . admin . token ; context . pdaToken = sessions . pda . token
const originalStep = context . addStep . bind ( context )
context . addStep = ( ... args ) => { const passed = originalStep ( ... args ) ; if ( ! passed ) throw new CheckFailure ( ` Fresh producer gate failed: ${ args [ 0 ] } / ${ args [ 1 ] } ` ) ; return passed }
const roleFor = token => token ? Object . entries ( sessions ) . find ( ( [ , value ] ) => value . token === token ) ? . [ 0 ] || 'admin' : 'producer'
context . request = ( method , path , body , token = context . token , headers = { } ) => {
need ( method === 'GET' || ! /^\/api\/platform\/v1\/(suppliers|business-partners|ocean-bookings\/channels)/ . test ( path ) , 'Shared booking master data writes require controller coordination' )
return request ( method , path , body , roleFor ( token ) , headers )
}
const adapter = multipartAdapter ( request , roleFor )
context . requestMultipart = ( method , path , file , token = context . token , headers = { } ) => adapter ( method , path , file , token , headers )
const freshCases = await prepareFreshCases ( options . runId , context , async checkpoint => {
report . preparation || = [ ] ; report . preparation . push ( checkpoint ) ; await save ( )
} , resume )
globalThis . fetch = nativeFetch
const bytes = Buffer . from ( 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=' , 'base64' )
report . syntheticHttpEvidence = true // controlled HTTP fixture; never camera/device proof
for ( const value of freshCases ) specs . push ( { ... value , images : Object . fromEntries ( TYPES . map ( type => [ type , { bytes , type : 'image/png' , sha256 : sha ( bytes ) } ] ) ) } )
report . fixtures = specs . map ( ( { spec , source , owners } ) => ( { kind : spec . kind , source , owners } ) )
await save ( )
}
const ok = response => { need ( response . http === 200 && String ( response . body ? . code ) === '200' , ` Preflight API rejected ${ response . path } ` ) ; return response . body . data }
const ownPdaId = JSON . parse ( Buffer . from ( sessions . pda . token . split ( '.' ) [ 1 ] , 'base64url' ) . toString ( ) ) . sub
need ( positive ( ownPdaId ) , 'Own PDA subject required' )
// Both cabinets are checked before the first claim/batch/evidence/complete write.
if ( completionMode ) {
const checkpoint = async snapshot => {
report . checkpoints || = [ ] ; report . checkpoints . push ( snapshot ) ; await save ( )
}
report . cases . push ( verifyAMode ? await verifyRecoveredA ( specs [ 0 ] , original , originalBefore , request , checkpoint )
: await recoverOriginalA ( specs [ 0 ] , original , request , checkpoint ) )
report . success = true // this dispatch is A-only; B is explicitly deferred and untouched
} else {
for ( const item of specs ) {
const { source , spec } = item , path = ` ${ PDA } /tasks/ ${ source . pdaTaskId } `
const task = ok ( await request ( 'GET' , ` ${ path } ?warehouseId= ${ source . warehouseId } ` , undefined , 'pda' ) )
validateZeroWorkTask ( source , task , ownPdaId )
const originalIds = source . taskItems . map ( row => id ( row . id ) ) . sort ( )
assert . deepEqual ( task . items . map ( row => id ( row . id ) ) . sort ( ) , originalIds , 'Source fixture item set' )
const preload = ok ( await request ( 'GET' , ` ${ LEG } /loading-plans/ ${ source . loadingPlanId } ?warehouseId= ${ source . warehouseId } ` ) )
need ( [ 'EDITING' , 'LOADING' ] . includes ( preload . summary . status ) , 'Zero-pick preload must be EDITING or own claimed LOADING' )
const operations = ok ( await request ( 'GET' , ` ${ path } /operations ` , undefined , 'pda' ) )
need ( ! rows ( operations ) . some ( row => [ 'BATCH' , 'COMPLETE_LOADING' ] . includes ( row . actionType ) )
&& rows ( operations ) . filter ( row => row . actionType === 'CLAIM' ) . every ( row => row . operationId === ` ${ source . sourceMarker } -claim ` ) ,
'No earlier physical picking/completion or foreign claim allowed' )
const evidence = ok ( await request ( 'GET' , ` ${ path } /evidence?warehouseId= ${ source . warehouseId } ` , undefined , 'pda' ) )
need ( ! rows ( evidence . evidences ) . some ( row => row . status === 'ACTIVE' ) , 'New task must have empty active evidence baseline' )
item . task = task ; item . expected = expectedRows ( spec , task , preload )
}
for ( const item of specs ) {
const result = await runCase ( item . spec , item . source , item , request )
if ( bOnlyMode ) {
const downstream = await waitDownstream ( item , request , ms => new Promise ( done => setTimeout ( done , ms ) ) )
result . downstream = downstream . facts ; report . BAfter = { source : await sourceSnapshot ( item , request ) , downstream }
}
report . cases . push ( result ) ; await save ( )
}
report . success = report . cases . length === ( bOnlyMode ? 1 : 2 )
}
} catch ( error ) {
report . failure = error instanceof CheckFailure || error instanceof assert . AssertionError ? error . message : 'Local response/fixture format failure'
} finally {
globalThis . fetch = nativeFetch
// A transport-unknown business command stops all further business writes;
// own session logout is still allowed and does not replay that command.
transportUnknown = false
for ( const [ role , session ] of Object . entries ( sessions ) . reverse ( ) ) {
try {
const response = await request ( 'POST' , '/api/auth/logout' , { refreshToken : session . refresh } , role )
const closed = response . http === 200 && String ( response . body ? . code ) === '200'
report . sessions [ role ] = { loggedIn : true , closed } ; if ( ! closed ) report . success = false
} catch { report . sessions [ role ] = { loggedIn : true , closed : false } ; report . success = false }
}
report . finishedAt = new Date ( ) . toISOString ( ) ; await save ( )
}
return { success : report . success , report : resolve ( directory , 'report.json' ) }
}
if ( process . argv [ 1 ] && import . meta . url === pathToFileURL ( resolve ( process . argv [ 1 ] ) ) . href ) {
execute ( ) . then ( result => { console . log ( JSON . stringify ( result ) ) ; if ( result . success === false ) process . exitCode = 1 } )
. catch ( ( ) => { console . error ( 'Partial-loading acceptance did not start; check TEST env-file, new run-id and legal fixture manifest.' ) ; process . exitCode = 1 } )
}