This commit is contained in:
2026-10-21 14:30:41 +08:00
parent 0af977bdef
commit 40a03978cd
53 changed files with 1004 additions and 1291 deletions
@@ -30,8 +30,8 @@ export const fixtureContract = Object.freeze({
export const afterLoadingContinuationContract = Object.freeze({
...fixtureContract,
stage: 'TWO_OWNED_CUSTOMERS_ONE_DISPATCH_AFTER_LOADING_NOT_ARRANGED',
preparation: 'Explicit continuation only: prior failed proof with verified gate and complete AFTER_PICK_1; original successful loadingCommand/confirmedLoad; current AFTER_LOADING with no arrangement or execution documents.',
stage: 'TWO_OWNED_CUSTOMERS_ONE_DISPATCH_DELIVERING',
preparation: 'Explicit continuation only: prior failed proof with verified gate and complete AFTER_PICK_1; original successful loadingCommand/confirmedLoad; current DELIVERING with automatically created execution documents.',
boundary: 'Reconcile original identities, complete picked inventory and original loading receipt through current reads before any new write. Never repeat picking/loading or rewrite the prior proof.',
})
@@ -42,7 +42,7 @@ export async function verifyFleetCrossCustomer(context, fixture, { execute = fal
export async function continueFleetCrossCustomerAfterLoading(context, fixture, { execute = false, readInventory, verifyDepartureRollback, priorProof, completionPolling } = {}) {
if (!execute) return afterLoadingContinuationContract
if (!priorProof) throw new Error('Explicit prior cross-customer proof is required for AFTER_LOADING continuation')
if (!priorProof) throw new Error('Explicit prior cross-customer proof is required for completed-loading continuation')
return runCrossCustomerCore(context, { ...fixture, members: structuredClone(fixture?.members) },
{ readInventory, verifyDepartureRollback, priorProof, completionPolling })
}
@@ -161,84 +161,29 @@ async function runCrossCustomerCore(context, fixture, { readInventory, verifyDep
equal(after, await snapshot(`PICK_REPLAY_${index}`), 'Pick replay changes neither customer nor any execution record')
}
try {
let cargoBasis, originalLoadingHash
let cargoBasis
if (continuation) {
const prior = priorProof.snapshots?.AFTER_PICK_1, command = fixture.tripProof.loadingCommand
const confirmed = command?.result?.result?.confirmedLoad
check('Continuation has the original failed owned proof and verified prerequisite gate', priorProof.status === 'FAILED'
&& priorProof.marker === context.marker && priorProof.dispatchNo === fixture.dispatchNo && priorProof.tripNo === fixture.tripNo
&& id(priorProof.warehouseId) === id(fixture.warehouseId) && priorProof.gateProof === 'PREREQUISITE_REJECTION_VERIFIED'
&& priorProof.members?.length === 2 && !priorProof.arrangeRequest && !priorProof.departure
&& !priorProof.members.some(member => member.outboundNo || member.deliveryNo)
&& Array.isArray(priorProof.requests) && priorProof.requests.every(entry => ['GET','HEAD','OPTIONS'].includes(entry.method) || entry.status === 'RESPONDED'))
const identityFields = ['marker','customerId','incomingNo','dispatchNo','dispatchItemId','balanceId','positionId','palletId','palletNo']
check('Continuation preserves each original customer, source, item, balance, position and physical pallet', fixture.members.every((member,index) =>
identityFields.every(field => id(member[field]) === id(priorProof.members[index]?.[field]))))
check('Prior AFTER_PICK_1 is a complete reviewed picked but not shipped native snapshot', prior?.dispatch?.dispatchNo === fixture.dispatchNo
&& prior.dispatch.status === 'ACTIVE' && prior.dispatch.stage === 'LOADING' && prior.dispatch.reviewState === 'APPROVED'
&& prior.dispatch.reviews?.dispatchReview?.status === 'APPROVED' && prior.dispatch.reviews?.loadingReview?.status === 'APPROVED'
&& integer(prior.dispatch.counts?.packageCount) === 2 && integer(prior.dispatch.counts?.pickedPackageCount) === 2
&& integer(prior.dispatch.counts?.shippedPackageCount) === 0 && prior.task?.pickingReconciled === true
&& prior.task.dispatchNo === fixture.dispatchNo && id(prior.task.warehouseId) === id(fixture.warehouseId)
&& prior.task.stage === 'LOADING' && id(prior.task.recordVersion) === id(prior.dispatch.recordVersion)
&& prior.items?.dispatchNo === fixture.dispatchNo && id(prior.items.recordVersion) === id(prior.dispatch.recordVersion)
&& prior.items.items?.length === 2 && prior.task.items?.length === 2
&& prior.members?.length === 2 && prior.members.every(row => row.stock && !row.outbound && !row.delivery)
&& prior.dispatch.documents?.outbounds?.length === 0 && prior.dispatch.documents?.deliveries?.length === 0)
check('Original loading command has a successful explicit receipt for this main and both exact physical pallets', command?.path === `${pdaPath}/loading-complete`
&& command.body?.idempotencyKey && command.body.manifestHash && command.result?.dispatchNo === fixture.dispatchNo
&& command.result.action === 'LOADING_COMPLETE' && command.result.idempotencyKey === command.body.idempotencyKey
&& command.result.status === 'ACTIVE' && command.result.stage === 'AFTER_LOADING' && command.result.result?.loadingCompletionHash
&& id(command.body.recordVersion) === id(prior.dispatch.recordVersion)
&& /^(0|[1-9]\d*)$/.test(id(command.result.recordVersion))
&& /^(0|[1-9]\d*)$/.test(id(command.body.recordVersion))
&& BigInt(id(command.result.recordVersion)) > BigInt(id(command.body.recordVersion))
&& integer(command.result.counts?.packageCount) === 2 && integer(command.result.counts?.pickedPackageCount) === 2
&& integer(command.result.counts?.shippedPackageCount) === 0
&& confirmed?.manifestHash === command.body.manifestHash && confirmed.operatorId && Number.isFinite(Date.parse(confirmed.confirmedAt))
&& canonical(confirmed) === canonical(fixture.tripProof.confirmedLoad)
&& Array.isArray(command.body.measurements) && command.body.measurements.length === 2
&& canonical(command.body.measurements) === canonical(confirmed.pallets)
&& new Set(confirmed.pallets.map(row => id(row.warehousePalletId))).size === 2
&& fixture.members.every(member => confirmed.pallets.some(row => id(row.warehousePalletId) === member.palletId
&& Number.isSafeInteger(row.palletSpaces) && row.palletSpaces > 0
&& ['weightKg','volumeCbm'].every(field => ['number','string'].includes(typeof row[field])
&& /^(?:0|[1-9]\d*)(?:\.\d+)?$/.test(String(row[field])) && Number.isFinite(Number(row[field])) && Number(row[field]) > 0)
&& Number.isSafeInteger(row.deliveryIndex) && row.deliveryIndex > 0 && fixture.tripProof.stops?.[row.deliveryIndex]?.destinationKey)))
proof.continued = true
proof.continuation = { mode: 'EXPLICIT_AFTER_LOADING', priorProofSHA256: createHash('sha256').update(canonical(priorProof)).digest('hex'),
priorStatus: priorProof.status, gateProofSource: 'PRIOR_PROOF', originalAfterPickSnapshot: structuredClone(prior),
originalLoadingCommand: structuredClone(command), originalConfirmedLoad: structuredClone(confirmed) }
proof.gateProof = priorProof.gateProof
await context.checkpoint?.()
const initial = await snapshot('CONTINUATION_INITIAL')
check('Current main is exactly the unarranged confirmed loading version with two picked and zero shipped packages', initial.dispatch.status === 'ACTIVE'
&& initial.dispatch.stage === 'AFTER_LOADING' && initial.task.stage === 'AFTER_LOADING' && initial.trip.trip.status === 'PLANNED'
&& id(initial.dispatch.recordVersion) === id(command.result.recordVersion) && initial.task.pickingReconciled === true
&& initial.dispatch.reviewState === 'APPROVED' && initial.dispatch.reviews?.dispatchReview?.status === 'APPROVED'
&& initial.dispatch.reviews?.loadingReview?.status === 'APPROVED' && integer(initial.dispatch.counts.packageCount) === 2
&& integer(initial.dispatch.counts.pickedPackageCount) === 2 && integer(initial.dispatch.counts.shippedPackageCount) === 0
&& initial.dispatch.documents?.outbounds?.length === 0 && initial.dispatch.documents?.deliveries?.length === 0
&& id(initial.trip.trip.vehicleId) === id(fixture.tripProof.vehicleId) && id(initial.trip.trip.driverId) === id(fixture.tripProof.driverId)
&& id(prior.trip?.trip?.vehicleId) === id(fixture.tripProof.vehicleId) && id(prior.trip?.trip?.driverId) === id(fixture.tripProof.driverId)
&& canonical(initial.trip.trip.stops) === canonical(fixture.tripProof.stops)
&& initial.items.items.every(item => integer(item.pickedPackages) === 1) && initial.task.items.every(item => integer(item.pickedPackages) === 1))
equal(prior.members.map(row => row.stock), initial.members.map(row => row.stock), 'Continuation current complete inventory equals original AFTER_PICK_1 inventory')
const sourceItems = items => items.map(item => Object.fromEntries(['dispatchItemId','customerId','sourceKey','sourceVersion','sourceType',
'planPalletId','returnReceiptId','relabelNo','warehousePalletId','palletNo','orderNo','inboundCode','destination','scheduledPackages','pickedPackages']
.map(field => [field,item[field]]))).sort((a,b) => id(a.dispatchItemId).localeCompare(id(b.dispatchItemId)))
equal(sourceItems(prior.items.items), sourceItems(initial.items.items), 'Continuation preserves both picked source items and destinations')
const loading = await request('GET', `${pdaPath}/loading`, undefined, 'Reconcile current native manifest against original successful loading receipt', true)
check('Current loading preview preserves the original manifest, confirmed receipt and two reconciled physical pallets', loading.dispatchNo === fixture.dispatchNo
&& loading.status === 'ACTIVE' && loading.stage === 'AFTER_LOADING' && id(loading.recordVersion) === id(command.result.recordVersion)
&& loading.pickingReconciled === true && loading.manifestHash === confirmed.manifestHash
&& canonical(loading.confirmedLoad) === canonical(confirmed) && loading.pallets?.length === 2
&& new Set(loading.pallets.map(row => id(row.warehousePalletId))).size === 2
&& fixture.members.every(member => loading.pallets.some(row => id(row.warehousePalletId) === member.palletId
&& row.palletNo === member.palletNo && integer(row.scheduledPackages) === 1 && integer(row.pickedPackages) === 1)))
proof.continuation.currentLoadingPreview = loading
cargoBasis = initial
originalLoadingHash = command.result.result.loadingCompletionHash
const command = fixture.tripProof.loadingCommand
const prior = priorProof.snapshots?.AFTER_PICK_1
check('Continuation preserves original successful loading command and current-run picked inventory', priorProof.dispatchNo === fixture.dispatchNo && priorProof.marker === context.marker && priorProof.gateProof === 'PREREQUISITE_REJECTION_VERIFIED'
&& canonical(priorProof.members) === canonical(proof.members)
&& priorProof.requests.every(row => row.method === 'GET' || row.status === 'RESPONDED')
&& Number(prior?.dispatch?.counts?.pickedPackageCount) === 2 && id(command?.body?.recordVersion) === id(prior?.dispatch?.recordVersion)
&& canonical(command?.body?.measurements) === canonical(command?.result?.result?.confirmedLoad?.pallets)
&& prior?.dispatch?.dispatchNo === fixture.dispatchNo && prior.dispatch.stage === 'LOADING'
&& command?.body?.idempotencyKey && command.result?.action === 'LOADING_COMPLETE' && command.result.stage === 'DELIVERING'
&& command.result.dispatchNo === fixture.dispatchNo && command.result.idempotencyKey === command.body.idempotencyKey
&& command.result.result?.confirmedLoad?.manifestHash === command.body.manifestHash)
const current = await snapshot('CONTINUATION_INITIAL')
check('Continuation is already delivering with the original automatic documents', current.dispatch.stage === 'DELIVERING'
&& current.task.stage === 'DELIVERING' && current.trip.trip.status === 'IN_TRANSIT'
&& id(current.dispatch.recordVersion) === id(command.result.recordVersion) && Number(current.dispatch.counts.shippedPackageCount) === 2
&& canonical(current.items.items) === canonical(prior.items.items)
&& current.dispatch.documents?.outbounds?.length === 2 && current.dispatch.documents?.deliveries?.length === 2)
equal(prior.members.map(row => row.stock), current.members.map(row => row.stock), 'Continuation preserves complete picked inventory')
proof.continued = true; proof.gateProof = priorProof.gateProof
proof.continuation = { mode: 'EXPLICIT_COMPLETED_LOADING', gateProofSource:'PRIOR_PROOF', originalAfterPickSnapshot:structuredClone(prior), priorProofSHA256: createHash('sha256').update(canonical(priorProof)).digest('hex') }
cargoBasis = prior
} else {
const initial = await snapshot('INITIAL')
check('Fresh native fixture is reviewed LOADING with both customer receipts untouched', initial.trip.trip.status === 'PLANNED'
@@ -257,67 +202,46 @@ async function runCrossCustomerCore(context, fixture, { readInventory, verifyDep
await request('POST', `${pdaPath}/loading-complete`, { recordVersion: rejectedBefore.task.recordVersion, idempotencyKey: key('UNREADY-LOAD'),
manifestHash: preview.manifestHash, measurements: [], remark: context.marker }, 'Unready second customer blocks explicit loading', true,
message => /拣货.*核平|尚未核平/.test(message))
await request('POST', `${pdaPath}/depart`, { recordVersion: rejectedBefore.task.recordVersion, idempotencyKey: key('UNREADY-DEPART'), remark: context.marker },
'Unready second customer blocks whole trip before execution', true, message => /装车|派送/.test(message))
equal(rejectedBefore, await snapshot('GATE_AFTER'), 'Rejected departure leaves both complete inventories/outbounds/deliveries/trip unchanged')
proof.gateProof = 'PREREQUISITE_REJECTION_VERIFIED'
await pick(proof.members[1], 1)
await confirmFleetLoading(context, MODULE, fixture.tripProof, fixture.pdaToken, fixture.loadingMeasurements)
cargoBasis = proof.snapshots.AFTER_PICK_1
await confirmFleetLoading(context, MODULE, fixture.tripProof, fixture.pdaToken, fixture.loadingMeasurements, async command => {
if (verifyDepartureRollback === undefined) return
check('Explicit rollback verifier is callable', typeof verifyDepartureRollback === 'function')
const ready = await snapshot('READY')
const rollback = await verifyDepartureRollback({ context, fixture, ready, departPath: command.path, departBody: command.body, snapshot })
check('Mid-transaction rollback has exact owned verification', rollback?.verified === true && rollback.scope === 'OWNED_TWO_CUSTOMER_DEPARTURE_ROLLBACK')
equal(ready, await snapshot('ROLLBACK_RESTORED'), 'Rollback restores both complete inventory snapshots before loading completion')
proof.rollbackEvidence = rollback; proof.midTransactionRollback = 'VERIFIED'
})
}
const arrangement = await request('GET', `${path}/delivery-arrangement`, undefined, 'Read native loaded per-customer delivery arrangement')
if (continuation) check('Continuation has no prior arrangement and retains the original loading completion hash', arrangement.arrangement == null
&& arrangement.loadingCompletionHash === originalLoadingHash && id(arrangement.recordVersion) === id(fixture.tripProof.loadingCommand.result.recordVersion))
check('Loaded arrangement groups cover exactly both real customers', arrangement.dispatchNo === fixture.dispatchNo && arrangement.status === 'ACTIVE'
&& arrangement.stage === 'AFTER_LOADING' && arrangement.arrange?.allowed === true && arrangement.cargoHash && arrangement.loadingCompletionHash
&& arrangement.groups?.length === 2 && proof.members.every(m => arrangement.groups.some(g => id(g.customerId) === m.customerId && g.cargo?.length
&& g.cargo.every(c => id(c.customerId) === m.customerId && id(c.warehousePalletId) === m.palletId) && g.blockReasons?.length === 0
&& g.destination?.key === cargoBasis.items.items.find(i => id(i.customerId) === m.customerId)?.destination?.key)))
const arrangeBody = { recordVersion: arrangement.recordVersion, idempotencyKey: key('ARRANGE'), cargoHash: arrangement.cargoHash,
loadingCompletionHash: arrangement.loadingCompletionHash, groups: arrangement.groups.map(g => ({ groupKey: g.groupKey, groupCargoHash: g.groupCargoHash,
plannedDeliveryAt: new Date(Date.now() + 21600000).toISOString(), deliveryMethod: 'TRUCK_PALLET', pickupMethod: 'WAREHOUSE_PICKUP',
loadingType: 'PALLET', vehicleType: 'TRUCK', vehicleSize: '本轮实际装载', appointmentAccount: 'SELF', remark: `${context.marker} TEST合成资料`,
externalFreightHash: g.externalFreightHash, feeCoverageCargoHash: g.feeCoverageCargoHash, feeCoverage: [],
fees: (g.requiredServices || []).map(feeType => ({ feeType, feeName: 'TEST本轮派送费', direction: 'RECEIVABLE', currency: 'CNY', amount: 30 })) })) }
proof.arrangeRequest = arrangeBody
const arranged = await request('POST', `${path}/transfer-delivery`, arrangeBody, 'Arrange both customers after real loading before departure')
check('Native arrangement creates customer-isolated outbounds and deliveries', arranged.dispatchNo === fixture.dispatchNo && arranged.stage === 'AFTER_LOADING'
&& arranged.result?.deliveries?.length === 2 && arranged.result.outbounds?.length === 2
&& new Set(arranged.result.deliveries.map(d => d.deliveryNo)).size === 2 && new Set(arranged.result.outbounds.map(o => o.outboundNo)).size === 2)
const detail = await request('GET', path, undefined, 'Read documents created by the one loading-complete command')
const documents = detail.documents
check('Automatic loading creates both customer-isolated outbounds and deliveries', detail.dispatchNo === fixture.dispatchNo && detail.stage === 'DELIVERING'
&& documents?.deliveries?.length === 2 && documents.outbounds?.length === 2
&& new Set(documents.deliveries.map(d => d.deliveryNo)).size === 2 && new Set(documents.outbounds.map(o => o.outboundNo)).size === 2)
for (const [index, member] of proof.members.entries()) {
const deliveries = arranged.result.deliveries.filter(d => id(d.customerId) === member.customerId)
check('Each customer has exactly its own source-specific delivery and outbound', deliveries.length === 1 && integer(deliveries[0].packageCount) === 1
&& arranged.result.outbounds.some(o => id(o.customerId) === member.customerId && o.outboundNo === deliveries[0].outboundNo && integer(o.packageCount) === 1))
const deliveries = documents.deliveries.filter(d => id(d.customerId) === member.customerId)
check('Each customer has its own source destination and quantity', deliveries.length === 1 && deliveries[0].status === 'DELIVERING'
&& integer(deliveries[0].packageCount) === 1 && deliveries[0].destination?.key === cargoBasis.items.items.find(i => id(i.customerId) === member.customerId)?.destination?.key
&& documents.outbounds.some(o => id(o.customerId) === member.customerId && o.status === 'OUTBOUNDED' && o.outboundNo === deliveries[0].outboundNo && integer(o.packageCount) === 1))
Object.assign(member, { outboundNo: deliveries[0].outboundNo, deliveryNo: deliveries[0].deliveryNo })
Object.assign(fixture.members[index], { outboundNo: member.outboundNo, deliveryNo: member.deliveryNo })
}
const arrangedReplay = await request('POST', `${path}/transfer-delivery`, arrangeBody, 'Replay exact native customer arrangement')
check('Native arrangement replay keeps original documents and main version', arrangedReplay.replayed === true && id(arrangedReplay.recordVersion) === id(arranged.recordVersion)
&& canonical(arrangedReplay.result) === canonical(arranged.result))
const ready = await snapshot('READY'), departBody = { recordVersion: ready.task.recordVersion, idempotencyKey: key('DEPART'), remark: context.marker }
const departPath = `${pdaPath}/depart`
if (verifyDepartureRollback !== undefined) {
check('Explicit rollback verifier is callable', typeof verifyDepartureRollback === 'function')
const rollback = await verifyDepartureRollback({ context, fixture, ready, departPath, departBody, snapshot })
check('Mid-transaction rollback has exact owned verification', rollback?.verified === true && rollback.scope === 'OWNED_TWO_CUSTOMER_DEPARTURE_ROLLBACK')
equal(ready, await snapshot('ROLLBACK_RESTORED'), 'Mid-transaction rollback restores both complete customer execution and inventory snapshots')
proof.rollbackEvidence = rollback; proof.midTransactionRollback = 'VERIFIED'
}
const departed = await request('POST', departPath, departBody, 'Depart one cross-customer native dispatch once', true)
check('Departure includes both customers in the one actual native main', departed.dispatchNo === fixture.dispatchNo && departed.status === 'ACTIVE'
&& departed.stage === 'DELIVERING' && integer(departed.result?.shippedPackages) === 2 && departed.result.deliveries?.length === 2)
const command = fixture.tripProof.loadingCommand, departed = command.result
proof.departure = departed
const after = await snapshot('DEPARTED')
check('Both customer outbounds completed and main delivering', after.trip.trip.status === 'IN_TRANSIT' && after.dispatch.stage === 'DELIVERING'
&& after.members.every(row => row.outbound.outbound.status === 'OUTBOUNDED' && row.delivery.order.status === 'DELIVERING'))
equal(ready.members.map(row => row.stock), after.members.map(row => row.stock), 'Departure does not deduct either inventory again')
const replay = await request('POST', departPath, departBody, 'Original PDA actor exact replay of native departure', true)
check('Shared departure replay is explicit and returns original result', replay.replayed === true && id(replay.recordVersion) === id(departed.recordVersion)
equal(cargoBasis.members.map(row => row.stock), after.members.map(row => row.stock), 'Loading completion does not deduct either inventory again')
const replay = await request('POST', command.path, command.body, 'Original PDA actor exact replay of loading completion', true)
check('Loading replay returns the original complete result', replay.replayed === true && id(replay.recordVersion) === id(departed.recordVersion)
&& canonical(replay.result) === canonical(departed.result))
const replayed = await snapshot('DEPART_REPLAY')
equal(after, replayed, 'Departure replay preserves both complete inventories and execution facts')
await request('POST', `${path}/depart`, departBody, 'Different platform actor cannot reuse the PDA departure command key', false, 'ACTOR_FORBIDDEN')
equal(replayed, await snapshot('FOREIGN_ACTOR_DEPART_REJECTED'), 'Different actor rejection preserves both complete inventories and execution facts')
equal(after, replayed, 'Loading replay preserves both complete inventories and execution documents')
await request('POST', `${path}/loading-complete`, command.body, 'Different platform actor cannot reuse the PDA loading key', false, 'ACTOR_FORBIDDEN')
equal(replayed, await snapshot('FOREIGN_ACTOR_DEPART_REJECTED'), 'Actor rejection preserves both complete inventories and execution facts')
for (const [index, member] of proof.members.entries()) {
const before = await snapshot(`BEFORE_SIGN_${index}`), documentPath = scoped(`${ROOT}/delivery-orders/${encode(member.deliveryNo)}/documents`, member.customerId)
const docs = await request('GET', documentPath, undefined, 'Read this customer delivery document version')