2026-10-03 15:46:42 +08:00
import { mkdir , writeFile } from 'node:fs/promises'
import { resolve , dirname } from 'node:path'
import { pathToFileURL , fileURLToPath } from 'node:url'
import { createHash } from 'node:crypto'
const root = resolve ( dirname ( fileURLToPath ( import . meta . url )), '..' )
const endpoint = '/merchant/v1/orders/workbench'
class VerificationError extends Error {}
function requireFact ( value , message ) { if ( ! value ) throw new VerificationError ( message ) }
const ids = rows => rows . map ( row => String ( row . orderId ))
const equalIds = ( a , b ) => JSON . stringify ( ids ( a ). sort ()) === JSON . stringify ( ids ( b ). sort ())
const digest = rows => createHash ( 'sha256' ). update ( JSON . stringify ( ids ( rows ). sort ())). digest ( 'hex' )
export function executionOptions ( args , execArgs , env ) {
if ( ! args . length || args . length === 1 && args [ 0 ] === '--plan' ) return { execute : false }
requireFact ( args . length === 3 && args [ 0 ] === '--execute' && args [ 1 ] === '--environment=TEST'
&& /^--run-id=[A-Za-z0-9_-]{1,60}$/ . test ( args [ 2 ]), 'Explicit TEST execution and a new run-id are required' )
const files = execArgs . filter ( arg => arg . startsWith ( '--env-file=' ))
requireFact ( files . length === 1 && execArgs . length === 1 && resolve ( files [ 0 ]. slice ( 11 )). replaceAll ( '\\' , '/' )
. endsWith ( '/.codex/env/chongchong-global/test-acceptance.env' ), 'Use the explicitly authorized test-acceptance.env' )
requireFact ( String ( env . CHONGCHONG_ENV ). toUpperCase () === 'TEST' , 'TEST profile required' )
for ( const key of [ 'CHONGCHONG_MERCHANT_API_BASE_URL' , 'CHONGCHONG_MERCHANT_USERNAME' , 'CHONGCHONG_MERCHANT_PASSWORD' ])
requireFact ( Boolean ( env [ key ]), `Missing ${ key } ` )
const base = new URL ( env . CHONGCHONG_MERCHANT_API_BASE_URL )
requireFact ([ 'http:' , 'https:' ]. includes ( base . protocol ) && ! base . username && ! base . password
2026-10-03 15:53:55 +08:00
&& base . pathname === '/' && ! base . search && ! base . hash , 'Expected the configured TEST Gateway origin' )
return { execute : true , runId : args [ 2 ]. slice ( 9 ), base : base . origin + '/api' }
2026-10-03 15:46:42 +08:00
}
export function validatePage ( page , number , size ) {
const total = Number ( page ? . total ), pages = Number ( page ? . pages )
requireFact ( Number . isSafeInteger ( total ) && total >= 0 && Number ( page . page ) === number
&& Number ( page . size ) === size && pages === Math . ceil ( total / size ), 'Invalid page metadata' )
requireFact ( Array . isArray ( page . records ) && page . records . length === Math . min ( size , Math . max ( 0 , total - ( number - 1 ) * size )), 'Truncated page' )
requireFact ( page . records . every ( row => row . orderId != null ) && new Set ( ids ( page . records )). size === page . records . length , 'Missing or duplicate order identity' )
return page
}
/** GET-only verifier. Caller owns authentication and durable, sanitized request evidence. */
export async function verifyWorkbench ( read , customerId ) {
const checks = []
const check = ( name , ok ) => { requireFact ( ok , name ); checks . push ({ name , passed : true }) }
async function all ( path , workbench = false , params = {}) {
2026-10-09 17:36:24 +08:00
let rows = [], progress = [], expected = null , first
2026-10-03 15:46:42 +08:00
for ( let number = 1 ; ; number ++ ) {
const query = new URLSearchParams ({ ... params , page : String ( number ), size : '200' })
const response = await read ( ` ${ path } ? ${ query } ` )
const page = validatePage ( workbench ? response . page : response , number , 200 )
if ( number === 1 ) { expected = Number ( page . total ); first = response }
requireFact ( Number ( page . total ) === expected , 'Dataset changed while paging; rerun in a stable window' )
requireFact ( page . records . every ( row => String ( row . tenantId ) === String ( customerId )), 'Customer scope mismatch' )
2026-10-09 17:36:24 +08:00
if ( workbench ) {
requireFact ( Array . isArray ( response . progress ) && response . progress . length === page . records . length
&& new Set ( response . progress . map ( p => String ( p . orderId ))). size === page . records . length
&& response . progress . every ( p => page . records . some ( r => String ( r . orderId ) === String ( p . orderId ))
&& [ 'none' , 'loaded' , 'declared' , 'cleared' , 'scheduled' , 'delivering' , 'delivered' ]. includes ( p . stage )
&& typeof p . partial === 'boolean' ), 'Invalid progress coverage' )
progress . push (... response . progress )
}
2026-10-03 15:46:42 +08:00
rows . push (... page . records )
if ( number >= Number ( page . pages )) break
requireFact ( number < 1000 , 'Pagination safety bound exceeded' )
}
requireFact ( rows . length === expected && new Set ( ids ( rows )). size === expected , 'Duplicate or missing orders across pages' )
2026-10-09 17:36:24 +08:00
return { rows , first , progress }
2026-10-03 15:46:42 +08:00
}
const workbench = await all ( endpoint , true )
check ( 'sample actually exceeds the former 200-order limit' , workbench . rows . length > 200 )
const formal = await all ( '/merchant/v1/orders' )
check ( 'all formal orders match the independent unified query' , equalIds ( workbench . rows . filter ( r => r . mainStatus !== 'DRAFT' ), formal . rows ))
const queues = workbench . first . queues
2026-10-09 17:36:24 +08:00
for ( const name of [ 'all' , 'fee' , 'customs' , 'draft' , 'loaded' , 'declared' , 'cleared' , 'scheduled' , 'delivering' , 'delivered' ]) {
2026-10-03 15:46:42 +08:00
check ( `queue ${ name } has a valid count` , Number . isSafeInteger ( Number ( queues ? .[ name ])) && Number ( queues [ name ]) >= 0 )
}
check ( 'all queue count matches complete rows' , Number ( queues . all ) === workbench . rows . length )
check ( 'fee count matches authoritative confirmation flags' , Number ( queues . fee ) === workbench . rows . filter ( r => r . customerFeeConfirmationRequired === true ). length )
check ( 'draft count matches draft rows' , Number ( queues . draft ) === workbench . rows . filter ( r => r . mainStatus === 'DRAFT' ). length )
2026-10-09 17:36:24 +08:00
for ( const queue of [ 'fee' , 'customs' , 'draft' , 'loaded' , 'declared' , 'cleared' , 'scheduled' , 'delivering' , 'delivered' ]) {
2026-10-03 15:46:42 +08:00
const selected = await all ( endpoint , true , { queue })
check ( ` ${ queue } complete page count` , selected . rows . length === Number ( queues [ queue ]))
const own = new Set ( ids ( workbench . rows ))
check ( ` ${ queue } contains only listed customer orders` , ids ( selected . rows ). every ( id => own . has ( id )))
if ( queue === 'fee' ) check ( 'fee queue exact membership' , equalIds ( selected . rows , workbench . rows . filter ( r => r . customerFeeConfirmationRequired === true )))
if ( queue === 'draft' ) check ( 'draft queue exact membership' , equalIds ( selected . rows , workbench . rows . filter ( r => r . mainStatus === 'DRAFT' )))
2026-10-09 17:36:24 +08:00
if ([ 'loaded' , 'declared' , 'cleared' , 'scheduled' , 'delivering' , 'delivered' ]. includes ( queue )) {
const expectedIds = new Set ( workbench . progress . filter ( p => p . stage === queue ). map ( p => String ( p . orderId )))
check ( ` ${ queue } progress exact membership` , selected . rows . length === expectedIds . size && ids ( selected . rows ). every ( id => expectedIds . has ( id )))
check ( ` ${ queue } selected progress agrees` , selected . progress . every ( p => p . stage === queue ))
}
2026-10-03 15:46:42 +08:00
}
for ( const [ page , size ] of [[ 21 , 10 ], [ 2 , 50 ]]) {
const result = await read ( ` ${ endpoint } ?page= ${ page } &size= ${ size } ` )
validatePage ( result . page , page , size )
check ( `page ${ page } size ${ size } preserves exact ordered slice` , JSON . stringify ( ids ( result . page . records ))
=== JSON . stringify ( ids ( workbench . rows . slice (( page - 1 ) * size , page * size ))))
}
const channels = workbench . first . channelNames
check ( 'channel options include every listed channel' , Array . isArray ( channels )
&& workbench . rows . every ( r => ! r . channelName || channels . includes ( r . channelName )))
const channel = workbench . rows . find ( r => r . channelName ) ? . channelName
requireFact ( channel , 'A channel-filter sample is required' )
const filtered = await all ( endpoint , true , { channelName : channel })
check ( 'channel filter applies before counting and paging' , equalIds ( filtered . rows , workbench . rows . filter ( r => r . channelName === channel )))
const spoofed = await read ( ` ${ endpoint } ?page=1&size=200&tenantId=0&customerId=0` )
check ( 'spoofed customer parameters cannot broaden scope' , equalIds ( spoofed . page . records , workbench . first . page . records )
&& Number ( spoofed . page . total ) === workbench . rows . length )
const refreshed = await read ( ` ${ endpoint } ?page=1&size=200` )
check ( 'dataset stayed stable through verification' , Number ( refreshed . page . total ) === workbench . rows . length
&& JSON . stringify ( ids ( refreshed . page . records )) === JSON . stringify ( ids ( workbench . first . page . records )))
return { checks , total : workbench . rows . length , formalTotal : formal . rows . length ,
membershipSha256 : digest ( workbench . rows ), queues ,
limitations : [ 'customs queue business membership requires separate customs evidence' , 'GET evidence does not replace browser or second-customer authorization tests' ] }
}
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 : true , businessWrites : false , checks : [ 'complete pagination' , 'independent formal-order set' , 'queue counts' , 'page 21' , 'channel filtering' , 'customer parameter isolation' ], required : 'Deploy Firstleg and Order workbench APIs first; use a stable customer dataset with more than 200 rows' }
const directory = resolve ( root , 'reports' , `client-workbench- ${ options . runId } ` )
await mkdir ( directory ) // Exclusive run: existing evidence is never replaced by a rerun.
const report = { startedAt : new Date (). toISOString (), readOnlyBusiness : true , success : false , requests : [], sessionClosed : false }
const save = () => writeFile ( resolve ( directory , 'report.json' ), JSON . stringify ( report , null , 2 ))
let token = '' , refresh = ''
async function request ( method , path , body ) {
requireFact ( method === 'GET' || method === 'POST' && [ '/auth/login' , '/auth/logout' ]. includes ( path ), 'Business writes prohibited' )
const query = new URLSearchParams ( path . split ( '?' )[ 1 ] || '' )
const entry = { method , path : path . split ( '?' )[ 0 ], outcome : 'PENDING' ,
page : query . get ( 'page' ) || undefined , size : query . get ( 'size' ) || undefined , queue : query . get ( 'queue' ) || undefined ,
queryKeys : [... query . keys ()], querySha256 : createHash ( 'sha256' ). update ( query . toString ()). digest ( 'hex' ) }
report . requests . push ( entry ); await save ()
try {
const response = await fetch ( options . base + path , { method , redirect : 'error' , signal : AbortSignal . timeout ( 30000 ),
headers : { Accept : 'application/json' , ...( body ? { 'Content-Type' : 'application/json' } : {}), ...( token ? { Authorization : `Bearer ${ token } ` } : {}) },
body : body ? JSON . stringify ( body ) : undefined })
const payload = await response . json ()
Object . assign ( entry , { outcome : 'RECEIVED' , http : response . status , code : payload . code , requestId : payload . requestId , traceId : payload . traceId })
await save ()
const safeCode = /^[A-Z0-9]{1,16}$/ . test ( String ( payload . code )) ? String ( payload . code ) : 'INVALID_CODE'
requireFact ( response . ok && String ( payload . code ) === '200' , `API rejected ${ entry . path } : HTTP ${ response . status } , code ${ safeCode } ` )
return payload . data
} catch ( error ) { if ( entry . outcome === 'PENDING' ) entry . outcome = 'UNKNOWN' ; await save (); throw error }
}
try {
const login = await request ( 'POST' , '/auth/login' , { loginId : env . CHONGCHONG_MERCHANT_USERNAME , password : env . CHONGCHONG_MERCHANT_PASSWORD })
token = login ? . accessToken || '' ; refresh = login ? . refreshToken || ''
requireFact ( token && refresh , 'Incomplete authentication response' )
const profile = await request ( 'GET' , '/merchant/v1/iam/tenant/profile' )
requireFact ( profile ? . id != null , 'Missing authenticated customer profile' )
Object . assign ( report , await verifyWorkbench ( path => request ( 'GET' , path ), profile . id ))
report . success = true
} catch ( error ) {
report . success = false
// Fixed verifier/HTTP classifications only; never persist an arbitrary response body or credential.
report . failure = error instanceof VerificationError ? error . message : 'Transport or response format failure'
} finally {
if ( token ) {
try { await request ( 'POST' , '/auth/logout' , { refreshToken : refresh }); report . sessionClosed = true }
catch { report . success = false ; report . logoutFailure = true }
}
report . success = report . success && report . sessionClosed
report . completedAt = new Date (). toISOString (); await save ()
}
return { success : report . success , sessionClosed : report . sessionClosed , 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 ( 'Workbench verification did not start; check explicit TEST profile and new run-id.' ); process . exitCode = 1 })
}