This commit is contained in:
2026-10-20 16:45:04 +08:00
parent 0ff1eefff6
commit 0dd9c8db07
44 changed files with 889 additions and 93 deletions
+6
View File
@@ -273,6 +273,12 @@ node --env-file="<仓库外灰度验收环境文件>" scripts/admin-test-env-acc
`--scenario competition` 同样仅使用本轮新样本,验证占用中的车辆/司机拒绝第二张空排车、装车后尚未安排派送的发车拒绝,以及已成功装车原键的两个 Admin/PDA 请求并发重放;原操作员恢复唯一结果,另一操作员被拒绝,完整库存与主单事实不变。该场景不宣称首次有效命令竞争或完整权限矩阵通过。第二次签收确定成功后增加最多30秒只读完结等待,再进入既有跨接口一致性快照;不重放签收写入,也不修改既有失败报告或共享验证器。
`--scenario first-competition` 在真实整车拣货核平后,以同一当前版本、两个不同请求键同时提交一次Admin/PDA首次装车命令;只允许一方成功、另一方版本冲突,整车只增加一个版本,完整库存不变。真实PDA回执原样返回;若Admin胜出,保留胜者回执及PDA拒绝,停止既有PDA正常链,不冒充PDA成功、不重复装车或发运。PDA胜出时继续正常履约、原键双端竞争重放与结案。
`--scenario relabel` 在本轮双客户正常履约后,另建独立客户并以正式仓储转运/PDA实收三箱;三份箱唛换标由客户确认,两份TRUCK合入同一新实托,一份PICKUP自动建独立出库。核验零扫码取消PICKUP出库保留原确认成果与完整库存;两TRUCK来源按服务器完整selectionGroup建同车,2箱只算1实托,零扫码撤销后重新正式建车,再撤销本轮未执行安排。PICKUP取消后保持待独立安排,缺公开再次安排入口时明确未验证,不改成TRUCK或重做完工确认。所有成功开出的客户会话在finally退出;若换标检查失败且没有未知写,已完成的正常车次仍正式结案释放资源,原失败证据保留。
`--scenario relabel-shared --resume-report <原报告> --resume-report-sha256 <冻结哈希>` 仅继续本轮三份均已确认、PICKUP取消VERIFIED、尚无任何共享建单请求且正常车次已结案的换标候选读取失败检查点。核对当前原始库存及三申请确认成果不变后,按精确换标业务号读取候选,仍要求完整selectionGroup闭包;只首次建共享运输安排,不重建客户/实收/换标,不重复取消PICKUP,不重复正常履约或资源释放。任何未知写阻断此入口;原报告保持失败。
`--scenario bol-empty|bol-same|bol-different --pdf-python <已安装pypdf的Python绝对路径>` 分别创建同目的仓无预约号、同号、不同号的两客户新样本。预约场景通过原有来源预约入口登记明确标记TEST的确认资料及1箱真实份额,再按返回的batchKey/version/来源归属关联原生排车;不借旧预约,不新增日历功能。实际BOL PDF、独立只读二进制请求账本和提取文字保存在该批 `evidence/`;核对跨客户合并/分组、各托只打印一次、总计2箱、打印前后完整库存不变,再继续正常履约结案。JSON请求计数不含独立二进制GET,二者分别留证;不把文字检查作为视觉验收。
BOL长托盘码按现行模板省略,自动核验使用完整入仓码与本轮一箱一托来源映射守恒,不声称PDF完整展示托盘码。若本轮在该显示断言处停止且尚未开始履约,可显式传原 `--resume-report` 和 `--resume-report-sha256` 到同一BOL场景、新runId;仅接受原READY夹具、精确已知错误、全部请求确定回执及无履约命令的冻结检查点。保留原失败,重新打印并核对当前状态后首次推进履约,不重建预约/排车,不允许未知写重试。
+3 -1
View File
@@ -6,6 +6,8 @@
其BOL扩展通过可选`shipmentReferences`和`prepareBatchAllocations`为本轮实收来源提供显式TEST货件参考及正式预约份额;分配准备后重新读取候选当前版本,默认夹具不创建预约。`bol-empty/same/different` 核验真实PDF页数、约号、逐托唯一出现及箱数守恒,打印前后原始库存和主单快照完全不变;实际二进制请求另存只读账本。
其 `relabel` 扩展新建独立第三客户,以三箱正式转运来货和PDA实收准备真实换标来源,逐箱换标/物理移绑/完工/客户确认后分别验证PICKUP零扫码出库取消和两TRUCK申请同实托的原生建车、撤销与再次安排。它不调用要求历史五箱DS来源的旧生产入口,只复用其完整原生selectionGroup建单检查;非TRUCK再次安排缺入口时保留实际缺口。
`procurement-sample-fulfillment.mjs` 是采购样品完整正常路径的内部验收助手,钱包综合模块也调用同一流程,并将采购请求证据独占保存于 `reports/procurement-wallet-checkpoints/<marker>.json`;客户会话在 finally 退出。由 `../procurement-sample-test-env.mjs` 注入本轮独立已充值客户、客户会话、有效收货仓、受控上传及权威钱包读取。创建新样本,执行正额货款、两次海外收货、两箱装箱、预估冻结、国际交寄、逐箱国内收货、实际核费及寄客户,逐阶段核对钱包现金/冻结和费用来源,关键请求原键重放。每次请求前必须 checkpoint;未知结果停写保留样本,不自动新键重试或清理。已接入显式 TEST 入口;未执行时不计作真实 HTTP 通过,也不覆盖异常、退款或权限全部矩阵。离线合同 `procurement-sample-fulfillment-contract.test.mjs` 验证前置缺失、余额不足、请求前持久化及未知结果停写。
`phase5-root-fulfillment` 可由总入口显式 `--prepare-domestic-pda-h5 true` 仅准备本轮国内H5待拣任务。调用 `phase3-order-splitting.loadOneFragment` 到正式计划确认、待拣任务及箱唛就绪即返回,不领取、不拣货、不继续海外;结果固定 `PREPARED_NOT_VERIFIED`,不算全链验收。默认完整链不变,范围及授权见根脚本README。
@@ -477,7 +479,7 @@ Client 全量为单文件,新增的客户账单、海外仓自助、跨境采
- `appointment-work` 写命令(`SAVE_FOLLOWUP`、`CONFIRM_ROUTE`、`SAVE_REQUIREMENT`)需要本轮可编辑工作项与操作人 ID;真实 HTTP 依赖 PDA 链产生工作事项。
- `self-operated-analytics` 数据采集任务与重试需要领星账号和真实采集批次;2026-10-20 空参 HTTP 返回 `B0001`,已按非阻断缺口记录。
- `fleet-management` 只读(车次、出车记录、车队报价、拼车需求)已通过真实 HTTP;车次写命令仍需要本轮排车、车辆与司机资源。
- Client 海外仓只读列表与退货预报写链路已通过真实 HTTP(2026-10-20 Client 全量 406 项通过、2 项跳过);贴标、仓储转运、货物废弃和退货换标的创建链路仍需要平台海外 PDA 链产出的库存。
- Client 海外仓只读列表与退货预报写链路已通过真实 HTTP(2026-10-20 Client 全量 406 项通过、2 项跳过)。P5 换标创建仍失败:普通入仓候选 `cartonMark` 服务端返回 null,而创建接口强制要求 `cartonMark` 等于来源入仓单箱号(`cartonBelongsToInbound`),候选未提供来源箱号,属后端契约缺口;需补候选箱号或在普通入仓场景取消该校验后再验收。仓储转运、货物废弃与退货换标创建同样需要真实库存与箱号前置。
- `pda-releases` 真实 APK/WGT 上传、发布和停发需人工准备有效安装包。
- Client 入库计划与派送单详情在新建客户无数据时跳过,需先由平台海外链产出单据后定向复核。
@@ -1,5 +1,8 @@
import assert from 'node:assert/strict'
import { verifyFleetCrossCustomer } from './fleet-cross-customer-acceptance.mjs'
import { run as createQualifiedMerchant } from './merchant-qualification-fixture.mjs'
import { nativeRelabelCreateBody } from '../overseas-relabel-real-acceptance.mjs'
import { finishFleetTrip } from './fleet-trip-fulfillment.mjs'
const ROOT = '/api/platform/v1/last-leg', MODULE = 'dispatch-workflow-extensions'
const OPTIONS = { scenario: MODULE, deploymentBlocker: true }
@@ -8,7 +11,7 @@ const id = value => String(value ?? '')
export const extensionMatrix = Object.freeze([
{ scenario: 'dual-destination', scope: 'Two fresh customers, two distinct real FBA destinations, one main/trip; distinct fee inputs and isolated execution documents', readiness: 'READY' },
{ scenario: 'reviews', scope: 'Both rejection/resubmission rounds, zero-pick return and cargo/resource changes, stale/version/idempotency conflicts', readiness: 'READY' },
{ scenario: 'relabel', scope: 'Confirmed relabel cancellation/rearrangement and multiple applications sharing one physical pallet', readiness: 'REQUIRES_FORMAL_RELABEL_FIXTURE' },
{ scenario: 'relabel', scope: 'Fresh three-box customer receipt, confirmed zero-scan cancellation, two applications sharing one pallet and native rearrangement', readiness: 'READY_FORMAL_THREE_BOX_RECEIPT' },
{ scenario: 'competition', scope: 'Two-request Admin/PDA replay race, occupied resources and unarranged departure gates; first-execution race remains untested', readiness: 'READY_BOUNDED_REPLAY' },
{ scenario: 'bol', scope: 'Fresh same-destination cargo, empty/same/different formal appointment allocations and actual PDF conservation', readiness: 'READY_FORMAL_APPOINTMENTS' },
])
@@ -87,7 +90,7 @@ export async function verifyDualDestination(context, fixture, options) {
dispatchNo: fixture.dispatchNo, status: 'CHECKING', syntheticTestMaterials: true, physicalMeasurementVerified: false,
hardwareVerified: false, midTransactionRollback: 'NOT_TESTED_NO_CONTROLLED_INJECTION', expectedFees: {}, isolation: [] }
const original = context.request
let arrangement, deferredFailure, raceDone = false, unarrangedDone = false
let arrangement, deferredFailure, raceDone = false, firstRaceDone = false, unarrangedDone = false
const mainPath = `${ROOT}/dispatches/${encodeURIComponent(fixture.dispatchNo)}`
const pdaPath = `/api/pda/v1/last-leg/dispatches/${encodeURIComponent(fixture.dispatchNo)}`
context.request = async function (method, path, body, ...rest) {
@@ -96,11 +99,18 @@ export async function verifyDualDestination(context, fixture, options) {
proof.expectedFees = applyIndependentFees(body, arrangement?.groups, fixture.members, context.marker)
proof.arrangeRequest = clone(body); await context.checkpoint()
}
const response = await original.call(this, method, path, body, ...rest)
let response
if (options.firstCompetition && !firstRaceDone && method === 'POST' && path === `${pdaPath}/loading-complete`
&& fixture.tripProof.loadingCommand?.body?.idempotencyKey === body?.idempotencyKey) {
firstRaceDone = true
const outcome = await executeFirstLoadingRace(context, fixture, options, original.bind(this), body)
response = outcome.pdaResponse
if (outcome.error) deferredFailure = outcome.error
} else response = await original.call(this, method, path, body, ...rest)
if (method === 'GET' && path === `${ROOT}/dispatches/${encodeURIComponent(fixture.dispatchNo)}/delivery-arrangement`
&& response.http === 200 && String(response.body?.code) === '200') arrangement = clone(response.body.data)
// 写请求已收到确定回执后,只做独立扩展;后续核验失败延至下一读请求抛出,不能把已知成功伪报成未知写。
if (response.http === 200 && String(response.body?.code) === '200') {
if (!deferredFailure && response.http === 200 && String(response.body?.code) === '200') {
try {
if (options.competition && method === 'POST' && path === `${pdaPath}/loading-complete` && !raceDone
&& response.body.data?.replayed !== true && response.body.data?.stage === 'AFTER_LOADING') {
@@ -206,6 +216,49 @@ export async function verifyLoadingReplayRace(context, fixture, options, send, b
race.status = 'VERIFIED'; await context.checkpoint()
}
/** 首次命令使用两个不同键和同一真实版本;胜者必须唯一,真实PDA回执原样交还调用者。 */
export async function executeFirstLoadingRace(context, fixture, options, send, body) {
const proof = context.createdResources.dispatchWorkflowCompetition
assert(proof && proof.status === 'CHECKING')
const adminBody = { ...clone(body), idempotencyKey: `${context.marker}-EXT-FIRST-ADMIN-LOAD` }
const race = proof.firstLoadingRace = { status: 'INTENT', pdaBody: clone(body), adminBody,
before: await competitionSnapshot(context, fixture, options, send) }
assert.equal(race.before.main.stage, 'LOADING'); assert.equal(Number(race.before.main.counts.pickedPackageCount), 2)
assert.equal(id(race.before.main.recordVersion), id(body.recordVersion)); await context.checkpoint()
const results = await Promise.allSettled([
send('POST', `/api/pda/v1/last-leg/dispatches/${encodeURIComponent(fixture.dispatchNo)}/loading-complete`, clone(body), fixture.pdaToken),
send('POST', `${ROOT}/dispatches/${encodeURIComponent(fixture.dispatchNo)}/loading-complete`, adminBody),
])
race.responses = results.map(result => result.status === 'fulfilled' ? receipt(result.value) : { outcome: 'UNKNOWN_RESULT' })
race.status = results.every(result => result.status === 'fulfilled') ? 'RESPONDED' : 'UNKNOWN_RESULT'; await context.checkpoint()
for (const result of results) if (result.status === 'rejected') throw result.reason
const responses = results.map(result => result.value)
try {
const winnerIndexes = responses.map((response, index) => String(response.body?.code) === '200' ? index : -1).filter(index => index >= 0)
assert.equal(winnerIndexes.length, 1)
const winnerIndex = winnerIndexes[0], winner = responses[winnerIndex], loser = responses[1 - winnerIndex]
assert.equal(String(loser.body?.code), 'A0409')
assert.equal(winner.body.data.stage, 'AFTER_LOADING')
assert.equal(winner.body.data.result?.confirmedLoad?.manifestHash, body.manifestHash)
assert.equal(BigInt(winner.body.data.recordVersion), BigInt(body.recordVersion) + 1n)
race.after = await competitionSnapshot(context, fixture, options, send)
assert.equal(race.after.main.stage, 'AFTER_LOADING')
assert.equal(id(race.after.main.recordVersion), id(winner.body.data.recordVersion))
assert.deepEqual(race.after.stock, race.before.stock)
assert.equal(Number(race.after.main.counts.packageCount), 2)
assert.equal(Number(race.after.main.counts.pickedPackageCount), 2)
race.winner = winnerIndex === 0 ? 'PDA' : 'ADMIN'; race.winnerResult = clone(winner.body.data)
race.status = 'VERIFIED'; proof.firstExecutionRace = 'VERIFIED'
context.assert(MODULE, 'First valid Admin/PDA loading race commits exactly one version and preserves complete inventories', winner, true, '', OPTIONS)
await context.checkpoint()
if (winnerIndex !== 0) return { pdaResponse: responses[0], error: Error('Admin won the first loading race; original PDA rejection retained, no fulfillment replay') }
return { pdaResponse: responses[0] }
} catch (error) {
race.verificationError = error.message; await context.checkpoint()
return { pdaResponse: responses[0], error }
}
}
async function verifyUnarrangedDeparture(context, fixture, options, send, arrangement) {
const proof = context.createdResources.dispatchWorkflowCompetition
const state = proof.unarrangedDeparture = { status: 'INTENT', before: await competitionSnapshot(context, fixture, options, send),
@@ -239,9 +292,13 @@ export async function verifyCompetitionExtension(context, fixture, options) {
proof.resourceConflict.status = 'VERIFIED'
const verified = await verifyDualDestination(context, fixture, { ...options, competition: true })
assert.equal(proof.loadingReplayRace?.status, 'VERIFIED'); assert.equal(proof.unarrangedDeparture?.status, 'VERIFIED')
proof.status = 'VERIFIED_BOUNDED_SCOPE'; await context.checkpoint(); return verified
if (options.firstCompetition) assert.equal(proof.firstLoadingRace?.status, 'VERIFIED')
proof.status = options.firstCompetition ? 'VERIFIED_FIRST_EXECUTION_AND_REPLAY' : 'VERIFIED_BOUNDED_SCOPE'
await context.checkpoint(); return verified
}
export const verifyFirstCompetitionExtension = (context, fixture, options) => verifyCompetitionExtension(context, fixture, { ...options, firstCompetition: true })
/** 仅使用本轮PDA实收的一箱来源,正式创建预约并向原生建单返回实际份额。 */
export async function prepareBolAppointments(context, members, scenario, warehouseId) {
assert(['bol-empty', 'bol-same', 'bol-different'].includes(scenario))
@@ -427,6 +484,261 @@ export async function verifyReviewExtension(context, fixture, options) {
return verifyDualDestination(context, fixture, options)
}
export function assertConfirmedRelabelCancellation(before, after, outbound, stockBefore, stockAfter) {
assert.equal(before.application.status, 'READY_FOR_OUTBOUND')
assert(before.application.customerConfirmedAt && before.application.outboundOrderNo)
assert.equal(after.application.relabelNo, before.application.relabelNo)
assert.equal(after.application.customerConfirmedAt, before.application.customerConfirmedAt)
assert.equal(after.application.status, 'READY_FOR_OUTBOUND')
assert.equal(after.application.deliveryMethod, before.application.deliveryMethod)
assert.equal(after.application.outboundOrderNo ?? null, null)
assert.equal(after.application.fulfillmentStatus, 'WAIT_OUTBOUND_CREATION')
assert.deepEqual(after.items, before.items)
assert.equal(outbound.outbound.status, 'CANCELLED')
assert.equal(outbound.outbound.outboundNo, before.application.outboundOrderNo)
assert.deepEqual(stockAfter, stockBefore, 'Zero-scan cancellation preserves all completed relabel stock and immutable ledger rows')
}
/** 独立第三客户三箱正式实收;不改变正常链两个客户的一箱库存基线。 */
export async function exerciseRelabelExtensions(context, fixture, { readInventory }) {
const proof = context.createdResources.dispatchWorkflowRelabel = { marker: context.marker, status: 'PREPARING',
syntheticTestMaterials: true, hardwareVerified: false, physicalMeasurementVerified: false,
scenarios: [], sharedDispatches: [], nonTruckRearrangement: 'NOT_TESTED_NO_FORMAL_PUBLIC_ENTRY' }
const key = label => `${context.marker}-RL-${label}`
const api = async (method, path, body, token) => {
const response = await context.request(method, path, body, token)
const data = context.apiSuccess(MODULE, 'Formal relabel extension HTTP', response, '', OPTIONS)
assert.equal(response.http, 200); assert.equal(String(response.body?.code), '200'); return data
}
const png = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aH1kAAAAASUVORK5CYII=', 'base64')
const upload = async (path, name) => {
const response = await context.requestMultipart('POST', path, { name, type: 'image/png', content: png }, fixture.pdaToken)
const data = context.apiSuccess(MODULE, 'Upload explicitly synthetic TEST relabel evidence', response, '', OPTIONS)
assert.equal(String(response.body?.code), '200'); assert(data?.fileId); return id(data.fileId)
}
const master = await api('GET', `/api/platform/v1/warehouse-admin/${encodeURIComponent(fixture.members[0].destination.warehouseId)}?tenantId=0`)
const address = master.addresses?.find(row => row.addressLine1)
assert(address && master.warehouse?.status === 'ACTIVE')
const child = Object.create(context)
child.marker = `${context.marker}-RELABEL`; child.options = { ...context.options, customerName: `TEST换标扩展-${context.marker.slice(-12)}` }
child.context = {}; child.createdResources = {}
child.request = async (...args) => context.request(...args)
await createQualifiedMerchant(child, { publishEnvironment: false })
const credentials = child.context.merchantQualificationFixture
assert(credentials?.tenantId && credentials.marker === child.marker)
proof.customerId = id(credentials.tenantId); proof.qualification = clone(child.createdResources.merchantQualificationFixture)
let session
try { session = await api('POST', '/api/auth/login', { loginId: credentials.username, password: credentials.password }, '') }
finally { delete child.context.merchantQualificationFixture }
assert(session?.accessToken && session.refreshToken)
const merchantToken = session.accessToken, customerId = proof.customerId
const stock = () => readInventory({ marker: context.marker, customerId, warehouseId: fixture.warehouseId })
try {
const profile = await api('GET', '/api/merchant/v1/iam/tenant/profile', undefined, merchantToken)
assert.equal(id(profile.id), customerId)
const skuCode = `RL-${context.marker.slice(-12)}`
const merchantRoot = '/api/merchant/v1/last-leg/warehouse-transfers'
const create = { sourceLastlegOrderIds: [], operatingWarehouseId: fixture.warehouseId,
fbaWarehouseCode: master.warehouse.warehouseCode, destinationType: 'FBA', destinationWarehouseId: id(master.warehouse.id),
destinationWarehouseCode: master.warehouse.warehouseCode, destinationWarehouseName: master.warehouse.warehouseName,
deliveryAddress: [address.countryCode, address.stateProvince, address.city, address.addressLine1, address.postalCode].filter(Boolean).join(' '),
packageCount: 3, volumeCbm: 3, deliveryMethod: 'CUSTOMER_DELIVERY', estimatedArrivalAt: new Date(Date.now() + 3600000).toISOString().slice(0, 19),
needRelabel: false, remark: `${context.marker} TEST三箱换标独立来货`, idempotencyKey: key('RECEIPT-CREATE'),
items: [{ skuCode, productNameZh: 'TEST三箱换标货物', packageCount: 3, quantity: 3 }] }
proof.receiptCreate = clone(create); await context.checkpoint()
let incoming = await api('POST', merchantRoot, create, merchantToken)
proof.incomingNo = incoming.transferNo; assert(proof.incomingNo)
const merchantPath = `${merchantRoot}/${encodeURIComponent(proof.incomingNo)}`
incoming = await api('POST', `${merchantPath}/submit`, undefined, merchantToken)
incoming = await api('POST', `${ROOT}/warehouse-transfers/${encodeURIComponent(proof.incomingNo)}/review?customerId=${customerId}`,
{ decision: 'APPROVE', remark: `${context.marker} TEST三箱独立实收` })
assert.equal(incoming.status, 'WAIT_PDA_RECEIVING')
const pda = `/api/pda/v1/last-leg/warehouse-transfers/${encodeURIComponent(proof.incomingNo)}`
const task = await api('GET', pda, undefined, fixture.pdaToken)
assert.equal(Number(task.packageCount), 3); assert.equal(task.items.length, 1); assert.equal(task.cartons.length, 3)
assert.equal(task.items[0].skuCode, skuCode); assert.equal(Number(task.items[0].quantity), 3)
assert.equal(new Set(task.cartons.map(row => row.cartonMark)).size, 3)
const draft = await api('POST', `${pda}/pallets/drafts`, { idempotencyKey: key('DRAFT') }, fixture.pdaToken)
assert.equal(draft.pallets.length, 1)
const palletPath = `${pda}/pallets/${encodeURIComponent(draft.pallets[0].id)}`
const generated = await api('POST', `${palletPath}/generate`, { idempotencyKey: key('GENERATE') }, fixture.pdaToken)
const pallet = generated.pallets[0]; assert(pallet.warehousePalletId && pallet.palletNo)
await api('POST', `${palletPath}/bind`, { palletCode: pallet.palletNo, idempotencyKey: key('BIND') }, fixture.pdaToken)
const counted = await api('POST', `${pda}/cartons/count`, { forecastPackageCount: 3, idempotencyKey: key('COUNT'),
boxes: task.cartons.map(carton => ({ cartonMark: carton.cartonMark, transferItemId: id(task.items[0].id), transferPalletId: id(draft.pallets[0].id),
length: 100, width: 100, height: 100, weight: 100, items: [{ skuCode, quantity: 1 }] })) }, fixture.pdaToken)
assert.equal(counted.cartons.length, 3); assert(counted.cartons.every(row => row.status === 'COUNTED' && Number(row.quantity) === 1))
assert.equal(Number(counted.pallets[0].cartonCount), 3); assert.equal(Number(counted.pallets[0].grossWeightKg), 300)
const photoId = await upload(`${pda}/measurement-photos/files`, `${context.marker}-TEST-three-boxes.png`)
incoming = await api('POST', `${pda}/palletize`, { idempotencyKey: key('PALLETIZE'), photoFileIds: [photoId] }, fixture.pdaToken)
assert.equal(incoming.status, 'WAIT_CUSTOMER_CONFIRMATION')
incoming = await api('POST', `${merchantPath}/confirm-measurement`, { idempotencyKey: key('CONFIRM-RECEIPT'), remark: context.marker }, merchantToken)
assert.equal(incoming.status, 'READY_FOR_DISPATCH')
proof.receipt = await stock()
assert.equal(proof.receipt.balances.length, 1); assert.equal(proof.receipt.positions.length, 1); assert.equal(proof.receipt.ledger.length, 1)
assert.equal(Number(proof.receipt.balances[0].availablePackages), 3)
assert.equal(id(proof.receipt.positions[0].palletId), id(pallet.warehousePalletId))
const candidates = await api('GET', `/api/merchant/v1/last-leg/relabel-applications/inventory-candidates?operationWarehouseId=${fixture.warehouseId}`, undefined, merchantToken)
const sources = candidates.filter(row => id(row.inventoryBalanceId) === id(proof.receipt.balances[0].id) && row.inboundCode === proof.incomingNo)
assert.equal(sources.length, 1); const source = sources[0]
const positions = source.positions.filter(row => id(row.positionId) === id(proof.receipt.positions[0].id))
assert.equal(positions.length, 1); assert.equal(Number(positions[0].availablePackages), 3)
const resolve = code => api('POST', '/api/pda/v1/last-leg/pallet-operations/pallets/resolve', { code }, fixture.pdaToken)
let sharedTarget
for (const [index, method] of ['TRUCK', 'TRUCK', 'PICKUP'].entries()) {
const name = `${method}-${index + 1}`, scenario = { name, operationType: 'CARTON_LABEL', deliveryMethod: method }
proof.scenarios.push(scenario)
const item = { sourceInventoryBalanceId: id(source.inventoryBalanceId), sourceInventoryPositionId: id(positions[0].positionId),
sourceInboundOrderId: id(source.inboundOrderId), skuCode, productNameZh: 'TEST三箱换标货物', totalPackageCount: 1, notifiedPackageCount: 1,
shipmentNo: `TEST-RL-${context.marker.slice(-12)}-${index + 1}`, cartonMark: task.cartons[index].cartonMark,
fbaCartonMark: `TEST-NEW-${context.marker.slice(-12)}-${index + 1}`, destinationFbaWarehouseCode: master.warehouse.warehouseCode,
operationType: 'CARTON_LABEL', skuItems: [], labelRequirement: `${context.marker} TEST换标成果` }
scenario.createBody = { customerOrderNo: `TEST-${context.marker.slice(-12)}-${name}`, operationWarehouseId: fixture.warehouseId,
labelDeadline: new Date(Date.now() + 7 * 86400000).toISOString().slice(0, 19), contactName: 'TEST换标验收', contactPhone: '19900000000',
remark: context.marker, idempotencyKey: key(`${name}-CREATE`), deliveryMethod: method, printShippingLabel: false, items: [item] }
await context.checkpoint()
const created = await api('POST', `${ROOT}/relabel-applications?customerId=${customerId}`, scenario.createBody)
scenario.relabelNo = created.application.relabelNo; assert(scenario.relabelNo)
assert.equal(created.application.status, 'PENDING_REVIEW')
assert(created.application.lastlegOrderId)
const approved = await api('PUT', `${ROOT}/orders/${encodeURIComponent(created.application.lastlegOrderId)}/relabel-review?customerId=${customerId}`,
{ status: 'PENDING_PROCESSING', remark: `${context.marker} TEST正式审核后允许PDA作业` })
assert.equal(approved.application.status, 'PENDING_PROCESSING'); scenario.approved = clone(approved)
const taskRoot = `/api/pda/v1/last-leg/relabel-tasks/${encodeURIComponent(scenario.relabelNo)}`
let detail = await api('POST', `${taskRoot}/claim`, { idempotencyKey: key(`${name}-CLAIM`) }, fixture.pdaToken)
assert.equal(detail.items.length, 1)
detail = await api('POST', `${taskRoot}/units`, { itemId: id(detail.items[0].itemId), oldLabel: item.cartonMark, newLabel: item.fbaCartonMark,
skuVerifications: [], expectedVersion: detail.version, idempotencyKey: key(`${name}-SCAN`) }, fixture.pdaToken)
let target
if (index === 1) target = sharedTarget
else target = await api('POST', '/api/pda/v1/last-leg/pallet-operations/relabel-pallet-label', { relabelNo: scenario.relabelNo,
idempotencyKey: key(`${name}-LABEL`) }, fixture.pdaToken)
assert(target?.palletId && target.palletCode); if (index === 0) sharedTarget = target
const from = await resolve(pallet.palletNo), to = await resolve(target.palletCode)
await api('POST', '/api/pda/v1/last-leg/pallet-operations/relabel-disassemble', { relabelNo: scenario.relabelNo,
idempotencyKey: key(`${name}-MOVE`), source: { palletCode: from.palletCode, expectedVersion: from.palletVersion },
target: { palletCode: to.palletCode, expectedVersion: to.palletVersion }, cartonMarks: [item.cartonMark], reason: `${context.marker} TEST实际箱移绑` }, fixture.pdaToken)
scenario.targets = [clone(target)]
const fileId = await upload(`${taskRoot}/photos/files`, `${context.marker}-${name}-TEST.png`)
detail = await api('POST', `${taskRoot}/complete`, { photoFileIds: [fileId], expectedVersion: detail.version,
idempotencyKey: key(`${name}-COMPLETE`), remark: context.marker }, fixture.pdaToken)
assert.equal(detail.applicationStatus, 'CUSTOMER_CONFIRM_PENDING')
const confirmed = await api('POST', `/api/merchant/v1/last-leg/relabel-applications/${encodeURIComponent(scenario.relabelNo)}/confirm-completion`,
{ idempotencyKey: key(`${name}-CONFIRM`), remark: context.marker }, merchantToken)
assert.equal(confirmed.application.status, 'READY_FOR_OUTBOUND'); assert(confirmed.application.customerConfirmedAt)
scenario.confirmed = clone(confirmed); scenario.items = clone(confirmed.items); scenario.relabelVerified = true
scenario.destinationFbaWarehouseCode = master.warehouse.warehouseCode
await context.checkpoint()
}
proof.sharedPhysical = await resolve(sharedTarget.palletCode)
assert.equal(Number(proof.sharedPhysical.totalPackages), 2)
const pickup = proof.scenarios[2], relabelPath = `${ROOT}/relabel-applications/${encodeURIComponent(pickup.relabelNo)}`
const before = await api('GET', relabelPath), stockBefore = await stock()
assert.equal(before.application.fulfillmentStatus, 'OUTBOUND_CREATED'); assert(before.application.outboundOrderNo)
const outboundPath = `${ROOT}/outbound-orders/${encodeURIComponent(before.application.outboundOrderNo)}`
const outboundBefore = await api('GET', outboundPath)
assert(['WAIT_PICK', 'WAIT_OUTBOUND'].includes(outboundBefore.outbound.status))
proof.cancel = { status: 'INTENT', before, stockBefore, outboundBefore, body: { reason: `${context.marker} TEST已确认换标零扫码撤销本次出库安排` } }
await context.checkpoint()
await api('POST', `${outboundPath}/cancel`, proof.cancel.body)
const after = await api('GET', relabelPath), outboundAfter = await api('GET', outboundPath), stockAfter = await stock()
Object.assign(proof.cancel, { after, outboundAfter, stockAfter })
assertConfirmedRelabelCancellation(before, after, outboundAfter, stockBefore, stockAfter)
proof.cancel.status = 'VERIFIED'; await context.checkpoint()
context.assert(MODULE, 'Confirmed relabel zero-scan outbound cancellation preserves completed items, customer confirmation and all raw stock', null, true, '', OPTIONS)
await verifyRelabelSharedDispatches(context, fixture, { readInventory }, proof)
return proof
} finally {
await api('POST', '/api/auth/logout', { refreshToken: session.refreshToken }, merchantToken)
proof.ownedMerchantSessionClosed = true; await context.checkpoint()
}
}
export async function verifyRelabelSharedDispatches(context, fixture, { readInventory }, proof) {
const customerId = proof.customerId, truck = proof.scenarios.slice(0, 2)
const stock = () => readInventory({ marker: context.marker, customerId, warehouseId: fixture.warehouseId })
const api = async (method, path, body) => {
const response = await context.request(method, path, body)
const data = context.apiSuccess(MODULE, 'Exact owned relabel shared physical selection', response, '', OPTIONS)
assert.equal(String(response.body?.code), '200'); return data
}
assert.equal(proof.cancel.status, 'VERIFIED'); assert.equal(truck.length, 2)
assert(truck.every(row => row.relabelVerified && row.deliveryMethod === 'TRUCK'))
proof.status = 'CHECKING_SHARED_NATIVE'
for (const round of [1, 2]) {
const byKey = new Map()
for (const scenario of truck) {
const page = await api('GET', `${ROOT}/dispatches/candidates?${new URLSearchParams({ warehouseId: fixture.warehouseId,
sourceStage: 'RELABEL', orderNo: scenario.relabelNo })}`)
assert.equal(page.complete, true)
for (const row of page.candidates) {
assert(truck.some(item => item.relabelNo === row.selection?.relabelNo))
const prior = byKey.get(row.sourceKey)
if (prior) assert.deepEqual(prior, row)
byKey.set(row.sourceKey, row)
}
}
const rows = [...byKey.values()]
assert.equal(rows.length, 2); assert.equal(new Set(rows.map(row => id(row.selection.warehousePalletId))).size, 1)
const createBody = nativeRelabelCreateBody(`${context.marker}-ROUND-${round}`, truck, rows, customerId, fixture.warehouseId)
const run = { round, status: 'INTENT', createBody, stockBefore: await stock() }; proof.sharedDispatches.push(run); await context.checkpoint()
run.result = await api('POST', `${ROOT}/dispatches`, createBody)
assert(run.result.dispatchNo)
const path = `${ROOT}/dispatches/${encodeURIComponent(run.result.dispatchNo)}`
run.detail = await api('GET', path); run.items = await api('GET', `${path}/items`)
assert.equal(Number(run.detail.counts.packageCount), 2); assert.equal(Number(run.detail.counts.palletCount), 1)
assert.equal(Number(run.detail.counts.pickedPackageCount), 0); assert.equal(run.items.items.length, 2)
assert.equal(new Set(run.items.items.map(row => id(row.warehousePalletId))).size, 1)
assert(run.items.items.every(row => id(row.customerId) === customerId && Number(row.scheduledPackages) === 1))
run.cancelBody = { recordVersion: run.detail.recordVersion, idempotencyKey: `${context.marker}-RL-SHARED-${round}-CANCEL`, reason: `${context.marker} TEST零扫码验证后撤销安排保留换标成果` }
await context.checkpoint(); run.cancelled = await api('POST', `${path}/cancel`, run.cancelBody)
assert.equal(run.cancelled.status, 'CANCELLED'); run.stockAfter = await stock(); assert.deepEqual(run.stockAfter, run.stockBefore)
for (const scenario of truck) {
const current = await api('GET', `${ROOT}/relabel-applications/${encodeURIComponent(scenario.relabelNo)}`)
assert.equal(current.application.status, 'READY_FOR_OUTBOUND'); assert.equal(current.application.customerConfirmedAt, scenario.confirmed.application.customerConfirmedAt)
assert.deepEqual(current.items, scenario.confirmed.items)
}
run.status = 'VERIFIED'; await context.checkpoint()
}
assert.notEqual(proof.sharedDispatches[0].result.dispatchNo, proof.sharedDispatches[1].result.dispatchNo)
context.assert(MODULE, 'Two confirmed applications share one actual pallet without double counting and can be natively rearranged after zero-pick cancellation', null, true, '', OPTIONS)
proof.status = 'VERIFIED_WITH_NON_TRUCK_REARRANGEMENT_GAP'; await context.checkpoint(); return proof
}
export async function reconcileRelabelShared(context, fixture, options, prior) {
const proof = context.createdResources.dispatchWorkflowRelabel = clone(prior.createdResources.dispatchWorkflowRelabel)
assert.equal(proof.sharedDispatches.length, 0); assert.equal(proof.cancel.status, 'VERIFIED')
assert(proof.scenarios.every(row => row.relabelVerified === true))
const read = async path => {
const response = await context.request('GET', path)
assert.equal(String(response.body?.code), '200'); return response.body.data
}
const main = await read(`${ROOT}/dispatches/${encodeURIComponent(fixture.dispatchNo)}`)
const trip = await read(`${ROOT}/fleet/trips/${encodeURIComponent(fixture.tripNo)}`)
assert.equal(main.stage, 'DELIVERED'); assert.equal(trip.trip.status, 'COMPLETED')
assert(trip.trip.vehicleRelease && trip.trip.driverRelease)
const currentStock = await options.readInventory({ marker: context.marker, customerId: proof.customerId, warehouseId: fixture.warehouseId })
assert.deepEqual(currentStock, proof.cancel.stockAfter)
for (const scenario of proof.scenarios) {
const current = await read(`${ROOT}/relabel-applications/${encodeURIComponent(scenario.relabelNo)}`)
assert.equal(current.application.status, 'READY_FOR_OUTBOUND'); assert.equal(current.application.customerConfirmedAt, scenario.confirmed.application.customerConfirmedAt)
assert.deepEqual(current.items, scenario.confirmed.items)
}
await verifyRelabelSharedDispatches(context, fixture, options, proof)
context.createdResources.fleetCrossCustomer = clone(prior.createdResources.fleetCrossCustomer)
return context.createdResources.fleetCrossCustomer
}
export async function verifyRelabelExtension(context, fixture, options) {
const verified = await verifyDualDestination(context, fixture, options)
try { await exerciseRelabelExtensions(context, fixture, options) }
catch (error) {
if (!context.createdResources.fleetWriteBoundary && !fixture.tripProof.closure) await finishFleetTrip(context, MODULE, fixture.tripProof)
throw error
}
return verified
}
/** Read-only terminal reconciliation after the known sequential-read completion race; never replay fulfillment. */
export async function reconcileReviewTerminal(context, fixture, { readInventory }, prior) {
assert.equal(prior.success, false)
@@ -1,18 +1,18 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { applyIndependentFees, feeCents, assertDualDestinationFixture, extensionMatrix, verifyLoadingReplayRace, assertBolPages } from './dispatch-workflow-extension-acceptance.mjs'
import { applyIndependentFees, feeCents, assertDualDestinationFixture, extensionMatrix, verifyLoadingReplayRace, executeFirstLoadingRace, assertBolPages, assertConfirmedRelabelCancellation } from './dispatch-workflow-extension-acceptance.mjs'
import { parseExtensionArguments, runDispatchExtensions } from '../dispatch-workflow-extensions-test-env.mjs'
test('default plan imports without credentials/network and keeps all unexecuted gaps explicit', async () => {
const plan = await runDispatchExtensions(parseExtensionArguments([]), {})
assert.equal(plan.execute, false); assert.equal(plan.matrix.length, 5)
assert.equal(extensionMatrix.find(row => row.scenario === 'relabel').readiness, 'REQUIRES_FORMAL_RELABEL_FIXTURE')
assert.equal(extensionMatrix.find(row => row.scenario === 'relabel').readiness, 'READY_FORMAL_THREE_BOX_RECEIPT')
})
test('execution requires fresh explicit TEST scope and declared synthetic load, never coerces a plan', () => {
const args = ['--execute', '--environment', 'TEST', '--run-id', 'EXT1', '--scenario', 'dual-destination', '--warehouse-id', '10',
'--destination-warehouse-id', '20', '--pda-env-file', 'private.env', '--synthetic-pallet-spaces', '1']
assert.equal(parseExtensionArguments(args).execute, true)
for (const [from, to] of [['TEST', 'DEV'], ['EXT1', 'plan'], ['dual-destination', 'relabel'], ['10', ''], ['1', '0']])
for (const [from, to] of [['TEST', 'DEV'], ['EXT1', 'plan'], ['dual-destination', 'unsupported'], ['10', ''], ['1', '0']])
assert.throws(() => parseExtensionArguments(args.map(value => value === from ? to : value)))
assert.throws(() => parseExtensionArguments([...args, '--execute']))
})
@@ -101,3 +101,38 @@ test('BOL PDF evidence enforces empty/same/different appointment grouping and ac
[pdf('ISA1', ['PALLET_A'], 1), pdf('ISA1', ['PALLET_B'], 1)]])
assert.throws(() => assertBolPages(bad, fixture, appointments, 'bol-different'))
})
test('confirmed relabel cancellation conserves customer-confirmed results, method and full inventory while clearing only outbound association', () => {
const before = { application: { relabelNo: 'RL-OWN', customerConfirmedAt: '2026-10-20T15:00:00', status: 'READY_FOR_OUTBOUND',
deliveryMethod: 'PICKUP', fulfillmentStatus: 'OUTBOUND_CREATED', outboundOrderNo: 'OB-OWN' }, items: [{ id: 'I', fbaCartonMark: 'NEW', processedPackages: 1 }] }
const after = structuredClone(before); after.application.outboundOrderNo = null; after.application.fulfillmentStatus = 'WAIT_OUTBOUND_CREATION'
const outbound = { outbound: { outboundNo: 'OB-OWN', status: 'CANCELLED' } }, stock = { positions: [{ held: 1, available: 0 }], ledger: [{ immutable: 1 }] }
assert.doesNotThrow(() => assertConfirmedRelabelCancellation(before, after, outbound, stock, structuredClone(stock)))
for (const mutate of [a => { a.application.customerConfirmedAt = null }, a => { a.application.status = 'CANCELLED' },
a => { a.application.deliveryMethod = 'TRUCK' }, a => { a.application.outboundOrderNo = 'OB-OWN' }, a => { a.items[0].processedPackages = 0 }]) {
const bad = structuredClone(after); mutate(bad)
assert.throws(() => assertConfirmedRelabelCancellation(before, bad, outbound, stock, stock))
}
assert.throws(() => assertConfirmedRelabelCancellation(before, after, outbound, stock, { ...stock, ledger: [] }))
})
for (const winner of ['PDA', 'ADMIN']) test(`first valid loading CAS race records ${winner} winner and preserves the actual PDA receipt`, async () => {
const context = { marker: 'CURRENT', createdResources: { dispatchWorkflowCompetition: { status: 'CHECKING' } }, checkpoint: async () => {}, assert: () => {} }
const fixture = { dispatchNo: 'OWN', tripNo: 'TRIP', warehouseId: 'W', pdaToken: 'OWN_PDA', members: [{ customerId: 'A' }, { customerId: 'B' }] }
const body = { recordVersion: '7', idempotencyKey: 'ORIGINAL_PDA', manifestHash: 'ACTUAL_HASH' }
const success = { http: 200, body: { code: '200', data: { stage: 'AFTER_LOADING', recordVersion: '8', result: { confirmedLoad: { manifestHash: 'ACTUAL_HASH' } } } } }
const rejected = { http: 200, body: { code: 'A0409' } }, pending = []; let completed = false
const send = async (method, path, payload, token) => {
if (method === 'GET') return { http: 200, body: { code: '200', data: { stage: completed ? 'AFTER_LOADING' : 'LOADING',
recordVersion: completed ? '8' : '7', counts: { packageCount: 2, pickedPackageCount: 2 } } } }
return new Promise(resolve => {
pending.push({ resolve, token, payload })
if (pending.length === 2) { completed = true; for (const request of pending) request.resolve((request.token ? 'PDA' : 'ADMIN') === winner ? success : rejected) }
})
}
const outcome = await executeFirstLoadingRace(context, fixture, { readInventory: async scope => ({ ...scope, picked: 1 }) }, send, body)
assert.equal(pending.length, 2); assert.notEqual(pending[0].payload.idempotencyKey, pending[1].payload.idempotencyKey)
assert.equal(context.createdResources.dispatchWorkflowCompetition.firstLoadingRace.winner, winner)
assert.equal(outcome.pdaResponse, winner === 'PDA' ? success : rejected)
assert.equal(Boolean(outcome.error), winner === 'ADMIN')
})
@@ -8,7 +8,7 @@ import { createFleetCrossCustomerFixture } from './admin-test-env-modules/fleet-
import { finishFleetTrip } from './admin-test-env-modules/fleet-trip-fulfillment.mjs'
import { AcceptanceRun } from './admin-test-env-acceptance.mjs'
import { fixtureContract } from './admin-test-env-modules/fleet-cross-customer-acceptance.mjs'
import { discoverSecondDestination, verifyDualDestination, verifyReviewExtension, verifyCompetitionExtension, prepareBolAppointments, verifyBolExtension, reconcileReviewTerminal, extensionMatrix } from './admin-test-env-modules/dispatch-workflow-extension-acceptance.mjs'
import { discoverSecondDestination, verifyDualDestination, verifyReviewExtension, verifyCompetitionExtension, verifyFirstCompetitionExtension, verifyRelabelExtension, reconcileRelabelShared, prepareBolAppointments, verifyBolExtension, reconcileReviewTerminal, extensionMatrix } from './admin-test-env-modules/dispatch-workflow-extension-acceptance.mjs'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const hash = bytes => createHash('sha256').update(bytes).digest('hex')
@@ -27,11 +27,11 @@ export function parseExtensionArguments(args) {
seen.add(name); config[field] = args[++index]
}
if (config.environment !== 'TEST' || !/^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/.test(config.runId)) throw Error('Explicit TEST/fresh run ID required')
if (config.execute && (!['dual-destination', 'reviews', 'competition', 'review-terminal', 'bol-empty', 'bol-same', 'bol-different'].includes(config.scenario) || config.runId === 'plan' || !config.pdaEnvFile
if (config.execute && (!['dual-destination', 'reviews', 'competition', 'first-competition', 'relabel', 'relabel-shared', 'review-terminal', 'bol-empty', 'bol-same', 'bol-different'].includes(config.scenario) || config.runId === 'plan' || !config.pdaEnvFile
|| !/^[1-9]\d*$/.test(config.warehouseId) || !/^[1-9]\d*$/.test(config.destinationWarehouseId) || config.syntheticPalletSpaces !== '1'))
throw Error('Ready scenario, explicit warehouse/destination/PDA config and synthetic pallet-spaces=1 required')
if (config.scenario === 'review-terminal' && (!config.resumeReport || !/^[0-9a-f]{64}$/.test(config.resumeReportSha256))) throw Error('Explicit frozen review report required')
if (config.scenario !== 'review-terminal' && (config.resumeReport || config.resumeReportSha256)
if (['review-terminal', 'relabel-shared'].includes(config.scenario) && (!config.resumeReport || !/^[0-9a-f]{64}$/.test(config.resumeReportSha256))) throw Error('Explicit frozen owned report required')
if (!['review-terminal', 'relabel-shared'].includes(config.scenario) && (config.resumeReport || config.resumeReportSha256)
&& (!config.scenario.startsWith('bol-') || !config.resumeReport || !/^[0-9a-f]{64}$/.test(config.resumeReportSha256)))
throw Error('Resume options require an explicit frozen owned checkpoint')
if (config.execute && config.scenario.startsWith('bol-') && !config.pdfPython) throw Error('Explicit PDF reader runtime required')
@@ -42,16 +42,24 @@ export async function runDispatchExtensions(config, env = process.env) {
limits: 'No deploy, SQL writes, permission edits, deletion, old manual main mutation, blind retries or uncontrolled fault injection.' }
if (env.CHONGCHONG_ENV !== 'test') throw Error('Only the explicitly loaded TEST configuration is accepted')
const bol = config.scenario.startsWith('bol-')
const relabelResume = config.scenario === 'relabel-shared'
if (bol) execFileSync(config.pdfPython, ['-c', 'import pypdf'], { stdio: 'pipe', timeout: 10000 })
let prior
if (config.scenario === 'review-terminal' || bol && config.resumeReport) {
if (config.scenario === 'review-terminal' || relabelResume || bol && config.resumeReport) {
const sourcePath = resolve(ROOT, config.resumeReport)
const prefix = bol ? 'fleet-cross-DISPATCH20261020EXTBOL' : 'fleet-cross-DISPATCH20261020EXTREV'
const prefix = bol ? 'fleet-cross-DISPATCH20261020EXTBOL' : relabelResume ? 'fleet-cross-DISPATCH20261020EXTRELABEL' : 'fleet-cross-DISPATCH20261020EXTREV'
if (!sourcePath.startsWith(join(ROOT, 'reports', prefix))) throw Error('Only this extension-owned frozen report can be reconciled')
const bytes = readFileSync(sourcePath)
if (hash(bytes) !== config.resumeReportSha256) throw Error('Original review report changed')
prior = JSON.parse(bytes)
if (bol) {
if (relabelResume) {
const proof = prior.createdResources?.dispatchWorkflowRelabel
if (prior.success !== false || prior.error !== "Expected values to be strictly equal:\n\n'S0500' !== '200'\n"
|| proof?.cancel?.status !== 'VERIFIED' || proof.sharedDispatches?.length !== 0 || proof.scenarios?.length !== 3
|| !proof.scenarios.every(row => row.relabelVerified === true) || prior.createdResources?.fleetWriteBoundary
|| prior.createdResources?.fleetTrips?.[0]?.closure?.status !== 'CARGO_COMPLETED_AND_RESOURCES_RELEASED'
|| prior.createdResources?.fleetCrossCustomer?.status !== 'VERIFIED') throw Error('Not the bounded owned relabel candidate read checkpoint')
} else if (bol) {
if (prior.success !== false || prior.error !== 'Expected values to be strictly equal:\n\n0 !== 1\n'
|| prior.createdResources?.dispatchWorkflowBol?.status !== 'CHECKING' || prior.createdResources.dispatchWorkflowBol.scenario !== config.scenario
|| prior.createdResources?.fleetCrossCustomerFixture?.status !== 'READY' || prior.createdResources?.fleetCrossCustomer
@@ -72,7 +80,8 @@ export async function runDispatchExtensions(config, env = process.env) {
if (!pda.PDA_USERNAME || !pda.PDA_PASSWORD) throw Error('Missing authorized existing overseas PDA credentials')
const files = ['scripts/dispatch-workflow-extensions-test-env.mjs', 'scripts/admin-test-env-modules/dispatch-workflow-extension-acceptance.mjs',
'scripts/admin-test-env-modules/fleet-cross-customer-fixture.mjs', 'scripts/fleet-cross-customer-test-env.mjs',
'scripts/admin-test-env-acceptance.mjs', 'scripts/admin-test-env-modules/fleet-cross-customer-acceptance.mjs', 'scripts/admin-test-env-modules/fleet-trip-fulfillment.mjs']
'scripts/admin-test-env-acceptance.mjs', 'scripts/admin-test-env-modules/fleet-cross-customer-acceptance.mjs', 'scripts/admin-test-env-modules/fleet-trip-fulfillment.mjs',
'scripts/admin-test-env-modules/merchant-qualification-fixture.mjs', 'scripts/overseas-relabel-real-acceptance.mjs']
const seal = Object.fromEntries(files.map(path => [path, hash(readFileSync(join(ROOT, path)))]))
const stable = () => { for (const [path, expected] of Object.entries(seal)) if (hash(readFileSync(join(ROOT, path))) !== expected) throw Error(`Source drift: ${path}`) }
mkdirSync(join(ROOT, 'reports'), { recursive: true })
@@ -95,7 +104,7 @@ export async function runDispatchExtensions(config, env = process.env) {
if (prior) {
const tripProof = structuredClone(prior.createdResources.fleetTrips[0])
context.createdResources.fleetTrips = [tripProof]
context.createdResources[bol ? 'explicitBolCheckpoint' : 'explicitReviewTerminal'] = { sourceReport: config.resumeReport, sourceReportSha256: config.resumeReportSha256,
context.createdResources[bol ? 'explicitBolCheckpoint' : relabelResume ? 'explicitRelabelCheckpoint' : 'explicitReviewTerminal'] = { sourceReport: config.resumeReport, sourceReportSha256: config.resumeReportSha256,
originalFailurePreserved: true, fulfillmentReplay: false, fixtureCreationReplay: false }
if (bol) {
context.createdResources.dispatchWorkflowBolAppointments = structuredClone(prior.createdResources.dispatchWorkflowBolAppointments)
@@ -132,6 +141,7 @@ export async function runDispatchExtensions(config, env = process.env) {
},
verify: async (...args) => {
stable()
if (relabelResume) return reconcileRelabelShared(...args, prior)
if (prior && !bol) return reconcileReviewTerminal(...args, prior)
if (bol) {
const [context, fixture, options] = args
@@ -155,15 +165,25 @@ export async function runDispatchExtensions(config, env = process.env) {
await context.checkpoint(); return result.pages
} })
}
return (config.scenario === 'reviews' ? verifyReviewExtension : config.scenario === 'competition' ? verifyCompetitionExtension : verifyDualDestination)(...args)
return (config.scenario === 'reviews' ? verifyReviewExtension : config.scenario === 'competition' ? verifyCompetitionExtension
: config.scenario === 'first-competition' ? verifyFirstCompetitionExtension
: config.scenario === 'relabel' ? verifyRelabelExtension : verifyDualDestination)(...args)
},
finish: async (...args) => {
stable()
if (relabelResume) {
if (args[2]?.closure?.status !== 'CARGO_COMPLETED_AND_RESOURCES_RELEASED') throw Error('Original normal trip closure missing')
return args[2].closure
}
return finishFleetTrip(...args)
},
finish: async (...args) => { stable(); return finishFleetTrip(...args) },
})
return { success: result.success, error: result.error, runId: config.runId, requestCount: result.requestCount, summary: result.summary,
sessionsClosed: result.sessionsClosed, scenario: config.scenario, extensionStatus: result.createdResources?.dispatchWorkflowExtensions?.status,
reviewStatus: result.createdResources?.dispatchWorkflowReviews?.status,
competitionStatus: result.createdResources?.dispatchWorkflowCompetition?.status,
bolStatus: result.createdResources?.dispatchWorkflowBol?.status,
relabelStatus: result.createdResources?.dispatchWorkflowRelabel?.status,
report: join(reportDirectory, 'report.json'), syntheticTestMaterials: true, hardwareVerified: false, physicalMeasurementVerified: false }
}
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
@@ -57,6 +57,9 @@ export const SYNCHRONOUS_CLIENTS = Object.freeze([
http('lastleg-service/src/main/java/com/baiyue/lastleg/fleet/reporting/HttpTripOperationsEvidenceGateway.java',
'lastleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED',
'出车实测凭证按平台/车次独立归属;上传无远端幂等键,当前/历史版本引用经Lastleg校验后读取,不自动重试上传'),
http('lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/gateway/HttpOwnedBusinessFileGateway.java',
'lastleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED',
'自有业务照片按平台0、原排车及照片分类复合归属;上传前记录本域命令意图,无远端幂等键且未知停写,不自动重传;当前及历史引用校验后只读元数据和内容'),
http('lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/gateway/HttpDispatchEvidenceGateway.java',
'lastleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED',
'排车车辆及司机证据通过File内部令牌入口按平台0、证据类别和排车ID复合核验;POST上传无远端幂等键,禁止自动重试,GET元数据及有界内容只读,不借文件上传推进排车状态'),
@@ -123,7 +123,7 @@ test('all raw HTTP transports have reviewed context, access and idempotency meta
}
const reviewed = SYNCHRONOUS_CLIENTS.filter(client => client.transport === 'HTTP')
.map(client => client.path).sort()
assert.equal(reviewed.length, 29)
assert.equal(reviewed.length, 30)
assert.deepEqual(discovered.sort(), reviewed)
})
@@ -139,6 +139,19 @@ test('every reviewed synchronous client has a complete and exclusive executable
}
})
test('owned business photos use bounded File transport with no automatic upload retry', async () => {
const file = 'lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/gateway/HttpOwnedBusinessFileGateway.java'
const reviewed = SYNCHRONOUS_CLIENTS.find(client => client.path === file)
assert.equal(reviewed?.target, 'file-service')
assert.equal(reviewed?.access, 'READ_WRITE')
assert.equal(reviewed?.idempotency, 'MIXED')
assert.deepEqual(reviewed?.contexts, ['REQUEST'])
const source = await readFile(path.join(root, file), 'utf8')
assert.match(source, /factory\.build\(builder,baseUrl,properties\.getCustomerVisitFile\(\)\)/)
assert.match(source, /\/internal\/file\/v1\/business-files\/0\/\{type\}\/\{dispatch\}/)
assert.doesNotMatch(source, /RetryTemplate|Retryer|@Retryable|\.retry\(|fallbackFactory|fallback\s*=/i)
})
test('IAM customer dimension Feign is reachable only from the background relay', async () => {
const references = []
for (const file of await javaFiles(path.join(root, 'iam', 'src', 'main', 'java'))) {