Files
baiyue-firstleg/scripts/admin-test-env-modules/inbound-hold.mjs
T

347 lines
23 KiB
JavaScript

const MODULE = 'inbound-hold'
const OPTIONS = { scenario: 'inbound-hold-lifecycle', deploymentBlocker: true }
const SMART = '/api/platform/v1/first-leg/smart-loading/inbound-candidates'
function id(value) { return value == null ? '' : String(value) }
function code(response) { return id(response?.body?.code) || 'missing' }
function succeeded(response) { return response?.http === 200 && code(response) === '200' }
function rows(page) { return Array.isArray(page?.records) ? page.records : [] }
function historyRows(detail) { return Array.isArray(detail?.history) ? detail.history : [] }
function historyFactsFrom(items) {
return JSON.stringify(items.map((item) => ({
id: id(item?.id), action: item?.action || null, reason: item?.reason || null,
evidenceFileId: id(item?.evidenceFileId) || null,
operatorName: item?.operatorName || null, operatedAt: item?.operatedAt || null,
})))
}
function historyFacts(detail) { return historyFactsFrom(historyRows(detail)) }
function channelGroup(channelCode) {
const value = String(channelCode || '').toUpperCase()
if (value.startsWith('EXX_')) return 'EXX'
if (value.startsWith('MATSON_CLX_')) return 'MATSON_CLX'
if (value.startsWith('MATSON_MAX_')) return 'MATSON_MAX'
if (value.startsWith('HEDE_')) return 'HEDE'
if (value.startsWith('OA_LA_') || value === 'OA_PARCEL') return 'OA_LA'
if (value.startsWith('OA_CHICAGO_')) return 'OA_CHICAGO'
if (value.startsWith('OA_HOUSTON_')) return 'OA_HOUSTON'
if (value.startsWith('OA_SAVANNAH_')) return 'OA_SAVANNAH'
if (value === 'OA_NEW_YORK_TRUCK') return value
throw new Error(`unsupported smart-loading channel group: ${channelCode}`)
}
function evidence(response, extra = '') {
return `HTTP=${response?.http ?? 'missing'}, code=${code(response)}, requestId=${response?.requestId || 'missing'}, traceId=${response?.traceId || 'missing'}${extra ? `, ${extra}` : ''}`
}
function expectCode(context, label, response, expected) {
context.assert(MODULE, label, response, response?.http === 200 && code(response) === expected,
evidence(response), OPTIONS)
}
function write(context, path, body, key) {
return context.request('POST', path, body, context.token, { 'Idempotency-Key': key })
}
function sameSnapshot(actual, expected) {
return actual?.holdFlag === expected?.holdFlag
&& actual?.currentHoldReason === expected?.currentHoldReason
&& Number(actual?.version) === Number(expected?.version)
&& historyFacts(actual) === historyFacts(expected)
}
export async function run(context) {
const fixture = context.createdResources.orders || {}
const tenantId = id(fixture.tenantId)
const orderId = id(fixture.orderId)
const orderNo = id(fixture.orderNo)
const inboundId = id(fixture.inboundId)
const inboundCode = id(fixture.inboundCode)
context.assert(MODULE, '复用本轮订单入库单', null,
Boolean(tenantId && orderId && orderNo && inboundId && inboundCode),
`tenantId=${tenantId || 'missing'}, orderId=${orderId || 'missing'}, orderNo=${orderNo || 'missing'}, inboundId=${inboundId || 'missing'}, inboundCode=${inboundCode || 'missing'}`,
OPTIONS)
if (!tenantId || !orderId || !orderNo || !inboundId || !inboundCode) {
throw new Error('orders fixture missing inbound hold identity')
}
const unifiedPath = `/api/platform/v1/orders/${encodeURIComponent(orderId)}?tenantId=${encodeURIComponent(tenantId)}`
const initialUnifiedResponse = await context.request('GET', unifiedPath)
const initialUnified = context.apiSuccess(MODULE, '扣货前读取统一订单', initialUnifiedResponse,
evidence(initialUnifiedResponse), OPTIONS)
const mainStatus = initialUnified?.summary?.mainStatus
const channelCode = id(initialUnified?.summary?.channelCode)
const warehouseId = id(initialUnified?.summary?.receivingWarehouseId || fixture.receivingWarehouseId)
context.assert(MODULE, '统一订单提供候选筛选事实', initialUnifiedResponse,
Boolean(mainStatus && channelCode && warehouseId),
`mainStatus=${mainStatus || 'missing'}, channelCode=${channelCode || 'missing'}, warehouseId=${warehouseId || 'missing'}`,
OPTIONS)
if (!mainStatus || !channelCode || !warehouseId) throw new Error('unified order missing hold acceptance filters')
const holdPath = `/api/platform/inbounds/${encodeURIComponent(inboundId)}/hold?tenantId=${encodeURIComponent(tenantId)}`
const initialHoldResponse = await context.request('GET', holdPath)
const initialHold = context.apiSuccess(MODULE, '读取初始扣货快照', initialHoldResponse,
evidence(initialHoldResponse), OPTIONS)
context.assert(MODULE, '初始入库单允许扣货', initialHoldResponse,
initialHold?.holdFlag === false && initialHold?.canHold === true && initialHold?.canRelease === false
&& historyRows(initialHold).length === 0,
`holdFlag=${initialHold?.holdFlag}, canHold=${initialHold?.canHold}, canRelease=${initialHold?.canRelease}, history=${initialHold?.history?.length ?? 'missing'}`,
OPTIONS)
const initialVersion = Number(initialHold?.version)
if (!Number.isInteger(initialVersion)) throw new Error('initial inbound hold version missing')
const candidatePath = `${SMART}?channelGroupCodes=${encodeURIComponent(channelGroup(channelCode))}`
+ `&warehouseId=${encodeURIComponent(warehouseId)}&customerId=${encodeURIComponent(tenantId)}`
+ `&inboundCode=${encodeURIComponent(inboundCode)}&page=1&size=20`
const beforeCandidatesResponse = await context.request('GET', candidatePath)
const beforeCandidates = context.apiSuccess(MODULE, '扣货前查询智能装柜候选', beforeCandidatesResponse,
evidence(beforeCandidatesResponse), OPTIONS)
context.assert(MODULE, '扣货前入库单可进入智能装柜候选', beforeCandidatesResponse,
rows(beforeCandidates).some((row) => id(row?.inboundId) === inboundId),
`inboundId=${inboundId}, candidates=${rows(beforeCandidates).map((row) => id(row?.inboundId)).join(',') || 'empty'}`,
OPTIONS)
const foreignTenantId = (BigInt(tenantId) + 1n).toString()
const foreignResponse = await context.request('GET',
`/api/platform/inbounds/${encodeURIComponent(inboundId)}/hold?tenantId=${encodeURIComponent(foreignTenantId)}`)
expectCode(context, '错误客户不能读取扣货快照', foreignResponse, 'A0404')
const invalidCommands = [
['扣货缺少幂等键返回A0400', context.request('POST', holdPath,
{ expectedVersion: initialVersion, reason: '缺少幂等键' }, context.token), 'A0400'],
['扣货空白原因返回A0422', write(context, holdPath,
{ expectedVersion: initialVersion, reason: ' ' }, `${context.marker}-hold-blank`), 'A0422'],
['扣货超长原因返回A0422', write(context, holdPath,
{ expectedVersion: initialVersion, reason: '测'.repeat(1001) }, `${context.marker}-hold-long-reason`), 'A0422'],
['扣货负版本返回A0422', write(context, holdPath,
{ expectedVersion: -1, reason: '负版本' }, `${context.marker}-hold-negative-version`), 'A0422'],
['扣货无效凭证ID返回A0422', write(context, holdPath,
{ expectedVersion: initialVersion, reason: '无效凭证', evidenceFileId: 0 }, `${context.marker}-hold-bad-evidence`), 'A0422'],
['错误客户不能执行扣货', write(context,
`/api/platform/inbounds/${encodeURIComponent(inboundId)}/hold?tenantId=${encodeURIComponent(foreignTenantId)}`,
{ expectedVersion: initialVersion, reason: '跨客户扣货' }, `${context.marker}-hold-foreign`), 'A0404'],
]
for (const [label, pending, expectedCode] of invalidCommands) {
expectCode(context, label, await pending, expectedCode)
}
const afterInvalidResponse = await context.request('GET', holdPath)
const afterInvalid = context.apiSuccess(MODULE, '回查非法命令后的初始快照', afterInvalidResponse,
evidence(afterInvalidResponse), OPTIONS)
context.assert(MODULE, '非法命令不改变版本、状态或历史', afterInvalidResponse,
sameSnapshot(afterInvalid, initialHold),
`version=${afterInvalid?.version}, history=${afterInvalid?.history?.length ?? 'missing'}`, OPTIONS)
const evidenceBytes = new TextEncoder().encode(`%PDF-1.4\n${context.marker}\n%%EOF`)
const evidencePath = `/api/platform/inbounds/${encodeURIComponent(inboundId)}/hold/evidence?tenantId=${encodeURIComponent(tenantId)}`
const evidenceResponse = await context.requestMultipart('POST', evidencePath, {
content: evidenceBytes, type: 'application/pdf', name: `${context.marker}-hold.pdf`,
})
const evidenceFile = context.apiSuccess(MODULE, '上传本轮扣货关联凭证', evidenceResponse,
evidence(evidenceResponse), OPTIONS)
const evidenceFileId = id(evidenceFile?.fileId)
context.assert(MODULE, '扣货凭证返回安全文件元数据和字符串长整型ID', evidenceResponse,
Boolean(evidenceFileId) && typeof evidenceFile?.fileId === 'string'
&& evidenceFile?.fileName === `${context.marker}-hold.pdf`
&& evidenceFile?.contentType === 'application/pdf'
&& Number(evidenceFile?.sizeBytes) === evidenceBytes.length,
`fileId=${evidenceFileId || 'missing'}, fileName=${evidenceFile?.fileName || 'missing'}, contentType=${evidenceFile?.contentType || 'missing'}`,
OPTIONS)
if (!evidenceFileId) throw new Error('hold evidence upload missing fileId')
const evidenceDownloadPath = `/api/platform/inbounds/${encodeURIComponent(inboundId)}/hold/evidence/${encodeURIComponent(evidenceFileId)}/download?tenantId=${encodeURIComponent(tenantId)}`
const unreferencedDownload = await context.requestBinary('GET', evidenceDownloadPath)
expectCode(context, '尚未进入不可变历史的凭证禁止下载', unreferencedDownload, 'A0404')
const reason = `${context.marker} 财务复核扣货`.slice(0, 1000)
const holdKey = `${context.marker}-inbound-hold`.slice(0, 128)
const holdBody = { expectedVersion: initialVersion, reason, evidenceFileId }
const holdResponse = await write(context, holdPath, holdBody, holdKey)
const held = context.apiSuccess(MODULE, '手工扣货入库单', holdResponse, evidence(holdResponse), OPTIONS)
context.assert(MODULE, '扣货写入当前快照和不可变历史', holdResponse,
held?.holdFlag === true && held?.currentHoldReason === reason && held?.canHold === false
&& held?.canRelease === true && Number(held?.version) === initialVersion + 1
&& held?.history?.[0]?.action === 'HOLD' && held?.history?.[0]?.reason === reason
&& id(held?.history?.[0]?.evidenceFileId) === evidenceFileId,
`holdFlag=${held?.holdFlag}, version=${held?.version}, history=${held?.history?.length ?? 'missing'}`,
OPTIONS)
const heldVersion = Number(held?.version)
const heldHistorySize = held?.history?.length
const heldHistoryFacts = historyFacts(held)
const evidenceDownload = await context.requestBinary('GET', evidenceDownloadPath)
context.assert(MODULE, '进入不可变历史后的扣货凭证可下载且内容一致', evidenceDownload,
evidenceDownload?.http === 200 && evidenceDownload?.body == null
&& evidenceDownload?.contentType?.includes('application/pdf')
&& Buffer.from(evidenceDownload?.bytes || []).equals(Buffer.from(evidenceBytes)),
`HTTP=${evidenceDownload?.http ?? 'missing'}, contentType=${evidenceDownload?.contentType || 'missing'}, bytes=${evidenceDownload?.bytes?.length ?? 'missing'}`,
OPTIONS)
const duplicateStateResponse = await write(context, holdPath,
{ expectedVersion: heldVersion, reason: `${context.marker} 重复状态` },
`${context.marker}-inbound-hold-again`.slice(0, 128))
expectCode(context, '已扣货时使用新键再次扣货返回A0409', duplicateStateResponse, 'A0409')
const heldAfterDuplicateResponse = await context.request('GET', holdPath)
const heldAfterDuplicate = context.apiSuccess(MODULE, '回查重复扣货后的当前快照', heldAfterDuplicateResponse,
evidence(heldAfterDuplicateResponse), OPTIONS)
context.assert(MODULE, '重复扣货不改变版本、原因或历史', heldAfterDuplicateResponse,
sameSnapshot(heldAfterDuplicate, held),
`version=${heldAfterDuplicate?.version}, history=${heldAfterDuplicate?.history?.length ?? 'missing'}`, OPTIONS)
const replayResponse = await write(context, holdPath, holdBody, holdKey)
const replayed = context.apiSuccess(MODULE, '扣货请求同键同载荷重放', replayResponse,
evidence(replayResponse), OPTIONS)
context.assert(MODULE, '扣货幂等重放不增加版本或历史', replayResponse,
Number(replayed?.version) === heldVersion && replayed?.history?.length === heldHistorySize
&& historyFacts(replayed) === heldHistoryFacts,
`version=${replayed?.version}, history=${replayed?.history?.length ?? 'missing'}`, OPTIONS)
const conflictResponse = await write(context, holdPath,
{ ...holdBody, reason: `${reason}-冲突` }, holdKey)
expectCode(context, '扣货同键异载荷返回A0409', conflictResponse, 'A0409')
const heldAfterConflictResponse = await context.request('GET', holdPath)
const heldAfterConflict = context.apiSuccess(MODULE, '回查扣货冲突后的当前快照', heldAfterConflictResponse,
evidence(heldAfterConflictResponse), OPTIONS)
context.assert(MODULE, '扣货冲突后版本、原因和历史零漂移', heldAfterConflictResponse,
heldAfterConflict?.holdFlag === true && heldAfterConflict?.currentHoldReason === reason
&& Number(heldAfterConflict?.version) === heldVersion && historyFacts(heldAfterConflict) === heldHistoryFacts,
`version=${heldAfterConflict?.version}, history=${heldAfterConflict?.history?.length ?? 'missing'}`, OPTIONS)
const heldUnifiedResponse = await context.request('GET', unifiedPath)
const heldUnified = context.apiSuccess(MODULE, '扣货后读取统一订单', heldUnifiedResponse,
evidence(heldUnifiedResponse), OPTIONS)
context.assert(MODULE, '统一订单同步展示扣货且主状态不变', heldUnifiedResponse,
heldUnified?.summary?.holdFlag === true && heldUnified?.summary?.currentHoldReason === reason
&& heldUnified?.summary?.mainStatus === mainStatus,
`holdFlag=${heldUnified?.summary?.holdFlag}, mainStatus=${heldUnified?.summary?.mainStatus}`,
OPTIONS)
const heldUnifiedListResponse = await context.request('GET',
`/api/platform/v1/orders?tenantId=${encodeURIComponent(tenantId)}&orderNo=${encodeURIComponent(orderNo)}&page=1&size=20`)
const heldUnifiedPage = context.apiSuccess(MODULE, '扣货后读取统一订单列表投影', heldUnifiedListResponse,
evidence(heldUnifiedListResponse), OPTIONS)
const heldUnifiedRow = rows(heldUnifiedPage).find((row) => id(row?.orderId) === orderId)
context.assert(MODULE, '统一订单列表同步展示扣货字段', heldUnifiedListResponse,
heldUnifiedRow?.holdFlag === true && heldUnifiedRow?.currentHoldReason === reason
&& heldUnifiedRow?.mainStatus === mainStatus,
`orderId=${orderId}, holdFlag=${heldUnifiedRow?.holdFlag}, mainStatus=${heldUnifiedRow?.mainStatus || 'missing'}`,
OPTIONS)
const heldCandidatesResponse = await context.request('GET', candidatePath)
const heldCandidates = context.apiSuccess(MODULE, '扣货后查询智能装柜候选', heldCandidatesResponse,
evidence(heldCandidatesResponse), OPTIONS)
context.assert(MODULE, '扣货入库单从智能装柜候选排除', heldCandidatesResponse,
!rows(heldCandidates).some((row) => id(row?.inboundId) === inboundId),
`inboundId=${inboundId}, candidates=${rows(heldCandidates).map((row) => id(row?.inboundId)).join(',') || 'empty'}`,
OPTIONS)
const releasePath = `/api/platform/inbounds/${encodeURIComponent(inboundId)}/hold/release?tenantId=${encodeURIComponent(tenantId)}`
const releaseReason = `${context.marker} 财务复核完成`.slice(0, 1000)
const releaseKey = `${context.marker}-inbound-release`.slice(0, 128)
const releaseBody = { expectedVersion: heldVersion, reason: releaseReason }
const releaseResponse = await write(context, releasePath, releaseBody, releaseKey)
const released = context.apiSuccess(MODULE, '解除入库单扣货', releaseResponse,
evidence(releaseResponse), OPTIONS)
context.assert(MODULE, '解除写入当前快照和不可变历史', releaseResponse,
released?.holdFlag === false && released?.currentHoldReason == null && released?.canHold === true
&& released?.canRelease === false && Number(released?.version) === heldVersion + 1
&& released?.history?.[0]?.action === 'RELEASE' && released?.history?.[0]?.reason === releaseReason
&& released?.history?.some?.((item) => item.action === 'HOLD' && item.reason === reason),
`holdFlag=${released?.holdFlag}, version=${released?.version}, history=${released?.history?.length ?? 'missing'}`,
OPTIONS)
const releasedVersion = Number(released?.version)
const releasedHistorySize = released?.history?.length
const releasedHistoryFacts = historyFacts(released)
context.assert(MODULE, '解除后既有扣货历史身份和内容保持不可变', releaseResponse,
releasedHistorySize === heldHistorySize + 1
&& historyFactsFrom(historyRows(released).slice(1)) === heldHistoryFacts,
`before=${heldHistorySize}, after=${releasedHistorySize}`, OPTIONS)
const duplicateReleaseResponse = await write(context, releasePath,
{ expectedVersion: releasedVersion, reason: `${context.marker} 重复解除` },
`${context.marker}-inbound-release-again`.slice(0, 128))
expectCode(context, '未扣货时使用新键再次解除返回A0409', duplicateReleaseResponse, 'A0409')
const releasedAfterDuplicateResponse = await context.request('GET', holdPath)
const releasedAfterDuplicate = context.apiSuccess(MODULE, '回查重复解除后的当前快照',
releasedAfterDuplicateResponse, evidence(releasedAfterDuplicateResponse), OPTIONS)
context.assert(MODULE, '重复解除不改变版本、状态或历史', releasedAfterDuplicateResponse,
sameSnapshot(releasedAfterDuplicate, released),
`version=${releasedAfterDuplicate?.version}, history=${releasedAfterDuplicate?.history?.length ?? 'missing'}`,
OPTIONS)
const releaseReplayResponse = await write(context, releasePath, releaseBody, releaseKey)
const releaseReplay = context.apiSuccess(MODULE, '解除请求同键同载荷重放', releaseReplayResponse,
evidence(releaseReplayResponse), OPTIONS)
context.assert(MODULE, '解除幂等重放不增加版本或历史', releaseReplayResponse,
Number(releaseReplay?.version) === releasedVersion && releaseReplay?.history?.length === releasedHistorySize
&& historyFacts(releaseReplay) === releasedHistoryFacts,
`version=${releaseReplay?.version}, history=${releaseReplay?.history?.length ?? 'missing'}`, OPTIONS)
const staleResponse = await write(context, holdPath,
{ expectedVersion: heldVersion, reason: `${context.marker} 过期版本` },
`${context.marker}-inbound-hold-stale`.slice(0, 128))
expectCode(context, '过期版本再次扣货返回A0409', staleResponse, 'A0409')
const finalHoldResponse = await context.request('GET', holdPath)
const finalHold = context.apiSuccess(MODULE, '回查过期版本冲突后的扣货快照', finalHoldResponse,
evidence(finalHoldResponse), OPTIONS)
context.assert(MODULE, '过期版本冲突后解除快照和不可变历史零漂移', finalHoldResponse,
finalHold?.holdFlag === false && finalHold?.currentHoldReason == null
&& Number(finalHold?.version) === releasedVersion && historyFacts(finalHold) === releasedHistoryFacts,
`version=${finalHold?.version}, history=${finalHold?.history?.length ?? 'missing'}`, OPTIONS)
const finalUnifiedResponse = await context.request('GET', unifiedPath)
const finalUnified = context.apiSuccess(MODULE, '解除后读取统一订单', finalUnifiedResponse,
evidence(finalUnifiedResponse), OPTIONS)
context.assert(MODULE, '解除后统一订单恢复且主状态不变', finalUnifiedResponse,
finalUnified?.summary?.holdFlag === false && finalUnified?.summary?.currentHoldReason == null
&& finalUnified?.summary?.mainStatus === mainStatus,
`holdFlag=${finalUnified?.summary?.holdFlag}, mainStatus=${finalUnified?.summary?.mainStatus}`,
OPTIONS)
const finalUnifiedListResponse = await context.request('GET',
`/api/platform/v1/orders?tenantId=${encodeURIComponent(tenantId)}&orderNo=${encodeURIComponent(orderNo)}&page=1&size=20`)
const finalUnifiedPage = context.apiSuccess(MODULE, '解除后读取统一订单列表投影', finalUnifiedListResponse,
evidence(finalUnifiedListResponse), OPTIONS)
const finalUnifiedRow = rows(finalUnifiedPage).find((row) => id(row?.orderId) === orderId)
context.assert(MODULE, '统一订单列表同步恢复扣货字段', finalUnifiedListResponse,
finalUnifiedRow?.holdFlag === false && finalUnifiedRow?.currentHoldReason == null
&& finalUnifiedRow?.mainStatus === mainStatus,
`orderId=${orderId}, holdFlag=${finalUnifiedRow?.holdFlag}, mainStatus=${finalUnifiedRow?.mainStatus || 'missing'}`,
OPTIONS)
const finalCandidatesResponse = await context.request('GET', candidatePath)
const finalCandidates = context.apiSuccess(MODULE, '解除后查询智能装柜候选', finalCandidatesResponse,
evidence(finalCandidatesResponse), OPTIONS)
context.assert(MODULE, '解除后入库单恢复智能装柜候选', finalCandidatesResponse,
rows(finalCandidates).some((row) => id(row?.inboundId) === inboundId),
`inboundId=${inboundId}, candidates=${rows(finalCandidates).map((row) => id(row?.inboundId)).join(',') || 'empty'}`,
OPTIONS)
const concurrentReasons = [`${context.marker} 并发扣货A`, `${context.marker} 并发扣货B`]
const concurrentResponses = await Promise.all(concurrentReasons.map((concurrentReason, index) =>
write(context, holdPath, { expectedVersion: releasedVersion, reason: concurrentReason },
`${context.marker}-inbound-hold-race-${index}`.slice(0, 128))))
const concurrentCodes = concurrentResponses.map(code).sort()
context.assert(MODULE, '同版本并发扣货恰好一个成功一个冲突', null,
JSON.stringify(concurrentCodes) === JSON.stringify(['200', 'A0409']),
`codes=${concurrentCodes.join(',')}`, OPTIONS)
const concurrentSnapshotResponse = await context.request('GET', holdPath)
const concurrentSnapshot = context.apiSuccess(MODULE, '回查并发扣货后的唯一快照',
concurrentSnapshotResponse, evidence(concurrentSnapshotResponse), OPTIONS)
context.assert(MODULE, '并发扣货只递增一次版本并新增一条历史', concurrentSnapshotResponse,
concurrentSnapshot?.holdFlag === true
&& concurrentReasons.includes(concurrentSnapshot?.currentHoldReason)
&& Number(concurrentSnapshot?.version) === releasedVersion + 1
&& historyRows(concurrentSnapshot).length === releasedHistorySize + 1,
`version=${concurrentSnapshot?.version}, history=${concurrentSnapshot?.history?.length ?? 'missing'}, reason=${concurrentSnapshot?.currentHoldReason || 'missing'}`,
OPTIONS)
const cleanupResponse = await write(context, releasePath,
{ expectedVersion: Number(concurrentSnapshot?.version), reason: `${context.marker} 并发验收恢复` },
`${context.marker}-inbound-race-cleanup`.slice(0, 128))
const cleanup = context.apiSuccess(MODULE, '并发验收后解除扣货恢复订单', cleanupResponse,
evidence(cleanupResponse), OPTIONS)
context.assert(MODULE, '并发验收最终恢复未扣货且历史完整', cleanupResponse,
cleanup?.holdFlag === false && cleanup?.currentHoldReason == null
&& Number(cleanup?.version) === releasedVersion + 2
&& historyRows(cleanup).length === releasedHistorySize + 2,
`version=${cleanup?.version}, history=${cleanup?.history?.length ?? 'missing'}`, OPTIONS)
}