Files
baiyue-firstleg/scripts/admin-test-env-modules/phase3-loading-finalize.mjs
T
2026-08-17 15:55:11 +08:00

298 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const MODULE = 'phase3-loading-finalize'
const OPTIONS = { scenario: 'phase3-loading-hard-gate-finalize', deploymentBlocker: true }
function id(value) { return value == null ? '' : String(value) }
function codeOf(response) { return response?.body?.code == null ? 'missing' : String(response.body.code) }
function evidenceSignature(data) {
return JSON.stringify((data?.evidences || []).map((item) => ({
id: id(item?.id), type: item?.evidenceType || null, fileId: id(item?.fileId),
status: item?.status || null, version: Number(item?.version), sealNo: item?.sealNo || null,
voidReason: item?.voidReason || null, voidedBy: id(item?.voidedBy), voidedAt: item?.voidedAt || null,
})))
}
export function buildFinalizeResource(fixture, finalData, completeResponse) {
const observedStatus = finalData?.summary?.processStatus || fixture?.inboundStatus || 'UNKNOWN'
const observedVersion = Number(finalData?.summary?.version)
const loaded = observedStatus === 'LOADED'
const version = Number.isInteger(observedVersion) ? observedVersion : fixture?.inboundVersion
return {
...fixture,
...(Number.isInteger(version) ? { inboundVersion: version } : {}),
inboundStatus: observedStatus,
finalStatus: observedStatus,
finalizeSucceeded: loaded,
notCleanedReason: loaded ? null
: `装柜完成未成功,保留真实状态 ${observedStatus};completeCode=${codeOf(completeResponse)}`,
}
}
async function request(context, method, path, body, headers = {}) {
try {
const response = await fetch(`${context.options.gateway}${path}`, {
method,
headers: { Accept: 'application/json', Authorization: `Bearer ${context.token}`,
...(body === undefined ? {} : { 'Content-Type': 'application/json; charset=utf-8' }), ...headers },
body: body === undefined ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(60_000),
})
const raw = await response.text(); let payload = null
try { payload = raw ? JSON.parse(raw) : null } catch { /* bounded raw evidence */ }
return { method, path, http: response.status, body: payload,
raw: payload === null ? raw.slice(0, 500) : null,
requestId: payload?.requestId || response.headers.get('x-request-id'),
traceId: payload?.traceId || response.headers.get('x-trace-id') }
} catch (error) {
return { method, path, http: 0, body: null, raw: String(error), requestId: null, traceId: null }
}
}
export async function run(context) {
const fixture = context.createdResources?.phase3Loading
const tenantId = id(fixture?.tenantId); const inboundId = id(fixture?.inboundId)
const orderId = id(fixture?.orderId)
const taskId = id(fixture?.canonicalPdaTaskId); const loadingPlanId = id(fixture?.loadingPlanId)
const containerBookingId = id(fixture?.containerBookingId)
if (!tenantId || !orderId || !inboundId || fixture?.marker !== context.marker
|| !taskId || !loadingPlanId || !containerBookingId
|| fixture?.loadingPlanStatus !== 'LOADING' || fixture?.finalStatus !== 'AWAITING_REVIEW') {
context.addStep(MODULE, '取得下游门禁待完成装柜 fixture', null, false,
`tenantId=${tenantId || 'missing'}, inboundId=${inboundId || 'missing'}, marker=${fixture?.marker || 'missing'}, plan=${fixture?.loadingPlanStatus || 'missing'}`, OPTIONS)
return
}
const detailPath = `/api/platform/inbounds/${inboundId}?tenantId=${encodeURIComponent(tenantId)}`
const beforeResponse = await context.request('GET', detailPath)
const before = context.apiSuccess(MODULE, '读取下游门禁齐备后的装柜状态', beforeResponse, '', OPTIONS)
const version = Number(before?.summary?.version)
context.assert(MODULE, '最终装柜前保持 LOADING 且版本有效', beforeResponse,
before?.summary?.processStatus === 'LOADING' && Number.isInteger(version),
`status=${before?.summary?.processStatus || 'missing'}, version=${before?.summary?.version ?? 'missing'}`, OPTIONS)
if (before?.summary?.processStatus !== 'LOADING' || !Number.isInteger(version)) return
const base = `/api/platform/v1/inbounds/${inboundId}`
context.addCoverageGap(MODULE, OPTIONS.scenario, 'PDA complete 联动拖车并原子推进 LOADED',
'BLOCKED_CONTRACT:用户已确认拖车缺失或未完成不再阻断;等待后端冻结 complete 响应中的拖车投影、LOADING→COMPLETED 历史动作、错柜隔离、重放与失败回滚字段后再执行写 HTTP。当前脚本主动停止,禁止沿用“拖车必须预先 COMPLETED”的旧门禁。',
'contract')
return
const taskPath = `/api/platform/v1/first-leg/pda-loading/tasks/${encodeURIComponent(taskId)}`
const taskBeforeResponse = await context.request('GET', taskPath)
const taskBefore = context.apiSuccess(MODULE, '读取最终完成前 PDA 任务即时版本', taskBeforeResponse, '', OPTIONS)
context.assert(MODULE, '最终完成前四元身份稳定且任务等待人工确认', taskBeforeResponse,
id(taskBefore?.id) === taskId && id(taskBefore?.inboundId) === inboundId
&& id(taskBefore?.loadingPlanId) === loadingPlanId
&& id(taskBefore?.containerBookingId) === containerBookingId
&& taskBefore?.taskStatus === 'LOADING' && taskBefore?.workflowStatus === 'AWAITING_REVIEW',
`taskId=${id(taskBefore?.id) || 'missing'}, workflow=${taskBefore?.workflowStatus || 'missing'}, version=${taskBefore?.version ?? 'missing'}`, OPTIONS)
if (!Number.isInteger(Number(taskBefore?.version))) return
const planPath = `/api/platform/v1/first-leg/smart-loading/plans/${encodeURIComponent(loadingPlanId)}`
const planBeforeRaceResponse = await context.request('GET', planPath)
const planBeforeRace = context.apiSuccess(MODULE, '读取最终完成并发前计划即时版本', planBeforeRaceResponse, '', OPTIONS)
const raceItem = planBeforeRace?.items?.find?.((item) => Number(item?.pickedPackageCount) > 0)
context.assert(MODULE, '最终完成并发使用同一已拣计划明细与即时版本', planBeforeRaceResponse,
Boolean(raceItem?.id) && Number.isInteger(Number(planBeforeRace?.version))
&& Number.isInteger(Number(raceItem?.version)),
`planVersion=${planBeforeRace?.version ?? 'missing'}, itemId=${id(raceItem?.id) || 'missing'}, itemVersion=${raceItem?.version ?? 'missing'}`, OPTIONS)
if (!raceItem?.id || !Number.isInteger(Number(planBeforeRace?.version))) return
let completeBody = {
expectedTaskVersion: Number(taskBefore.version),
operationId: `${context.marker}-pda-final-complete`.slice(0, 128), remark: context.marker,
}
let completeKey = `${context.marker}-p3-complete-after-downstream`.slice(0, 128)
const concurrentAdjustBody = {
packageCount: Number(raceItem.packageCount), expectedPlanVersion: Number(planBeforeRace.version),
expectedItemVersion: Number(raceItem.version), operationId: `${context.marker}-race-adjust`.slice(0, 128),
}
const [initialCompleteResponse, concurrentAdjustResponse] = await Promise.all([
request(context, 'POST', `${taskPath}/complete`, completeBody, { 'Idempotency-Key': completeKey }),
request(context, 'PATCH',
`/api/platform/v1/first-leg/smart-loading/plans/${encodeURIComponent(loadingPlanId)}/items/${encodeURIComponent(id(raceItem.id))}`,
concurrentAdjustBody),
])
const completeWon = initialCompleteResponse.http === 200 && codeOf(initialCompleteResponse) === '200'
const adjustWon = concurrentAdjustResponse.http === 200 && codeOf(concurrentAdjustResponse) === '200'
context.assert(MODULE, 'Admin 增量与 PDA final complete 并发恰好一个事务成功', initialCompleteResponse,
completeWon !== adjustWon
&& (completeWon ? codeOf(concurrentAdjustResponse) === 'A0409' : codeOf(initialCompleteResponse) === 'A0409'),
`completeCode=${codeOf(initialCompleteResponse)}, adjustCode=${codeOf(concurrentAdjustResponse)}, completeRequestId=${initialCompleteResponse.requestId || 'missing'}, adjustRequestId=${concurrentAdjustResponse.requestId || 'missing'}`, OPTIONS)
let completeResponse = initialCompleteResponse
if (adjustWon) {
const taskAfterRaceResponse = await context.request('GET', taskPath)
const taskAfterRace = context.apiSuccess(MODULE, '增量赢得并发后刷新 PDA 任务版本', taskAfterRaceResponse, '', OPTIONS)
const planAfterRaceResponse = await context.request('GET', planPath)
const planAfterRace = context.apiSuccess(MODULE, '增量赢得并发后回查唯一计划事实', planAfterRaceResponse, '', OPTIONS)
context.assert(MODULE, 'final complete 失败方零写入且增量版本成为唯一事实', taskAfterRaceResponse,
taskAfterRace?.taskStatus === 'LOADING' && taskAfterRace?.workflowStatus === 'AWAITING_REVIEW'
&& Number(taskAfterRace?.version) > Number(taskBefore?.version)
&& Number(planAfterRace?.version) > Number(planBeforeRace?.version),
`taskVersion=${taskAfterRace?.version ?? 'missing'}, planVersion=${planAfterRace?.version ?? 'missing'}`, OPTIONS)
completeBody = {
expectedTaskVersion: Number(taskAfterRace?.version),
operationId: `${context.marker}-pda-final-complete-retry`.slice(0, 128), remark: context.marker,
}
completeKey = `${context.marker}-p3-complete-after-race-refresh`.slice(0, 128)
completeResponse = await request(context, 'POST', `${taskPath}/complete`, completeBody,
{ 'Idempotency-Key': completeKey })
}
const completed = context.apiSuccess(MODULE, '订舱拖车报关齐备后完成装柜', completeResponse, '', OPTIONS)
context.assert(MODULE, '统一硬门禁通过后 task 与 workflow 同事务完成且身份不漂移', completeResponse,
completed?.taskStatus === 'COMPLETED' && completed?.workflowStatus === 'COMPLETED'
&& id(completed?.id) === taskId && id(completed?.loadingPlanId) === loadingPlanId
&& id(completed?.containerBookingId) === containerBookingId && id(completed?.inboundId) === inboundId,
`taskStatus=${completed?.taskStatus || 'missing'}, workflow=${completed?.workflowStatus || 'missing'}`, OPTIONS)
const replayResponse = await request(context, 'POST',
`${taskPath}/complete`, completeBody, { 'Idempotency-Key': completeKey })
context.assert(MODULE, 'LOADED 完成请求 Idempotency-Key 重放保持终态', replayResponse,
replayResponse.http === 200 && String(replayResponse.body?.code) === '200'
&& replayResponse.body?.data?.taskStatus === 'COMPLETED'
&& id(replayResponse.body?.data?.id) === taskId,
`HTTP=${replayResponse.http}, code=${replayResponse.body?.code ?? 'missing'}, status=${replayResponse.body?.data?.taskStatus || 'missing'}`, OPTIONS)
const finalResponse = await context.request('GET', detailPath)
const finalData = context.apiSuccess(MODULE, '回查三期装柜完成终态', finalResponse, '', OPTIONS)
context.assert(MODULE, '入库单终态 LOADED、版本递增且审计历史存在', finalResponse,
finalData?.summary?.processStatus === 'LOADED' && Number(finalData?.summary?.version) > version
&& finalData?.statusHistory?.some?.((item) => item.action === 'COMPLETE_LOADING'
&& item.operatorName && item.fromStatus === 'LOADING' && item.toStatus === 'LOADED'),
`status=${finalData?.summary?.processStatus || 'missing'}, version=${finalData?.summary?.version ?? 'missing'}, histories=${finalData?.statusHistory?.length ?? 'n/a'}`, OPTIONS)
const unifiedDetailPath = `/api/platform/v1/orders/${encodeURIComponent(orderId)}?tenantId=${encodeURIComponent(tenantId)}`
const unifiedDetailResponse = await context.request('GET', unifiedDetailPath)
const unifiedDetail = context.apiSuccess(MODULE, 'LOADED 后回查同一统一订单详情', unifiedDetailResponse, '', OPTIONS)
const orderNo = String(unifiedDetail?.summary?.orderNo || '').trim()
context.assert(MODULE, '统一订单详情投影同一订单为 LOADED', unifiedDetailResponse,
id(unifiedDetail?.summary?.orderId) === orderId && unifiedDetail?.summary?.mainStatus === 'LOADED'
&& Boolean(orderNo),
`orderId=${id(unifiedDetail?.summary?.orderId) || 'missing'}, mainStatus=${unifiedDetail?.summary?.mainStatus || 'missing'}, orderNo=${orderNo || 'missing'}`, OPTIONS)
if (id(unifiedDetail?.summary?.orderId) !== orderId || unifiedDetail?.summary?.mainStatus !== 'LOADED' || !orderNo) return
const unifiedListPath = `/api/platform/v1/orders?page=1&size=100&tenantId=${encodeURIComponent(tenantId)}&orderNo=${encodeURIComponent(orderNo)}`
const unifiedListResponse = await context.request('GET', unifiedListPath)
const unifiedPage = context.apiSuccess(MODULE, 'LOADED 后回查同一统一订单列表', unifiedListResponse, '', OPTIONS)
const unifiedRow = unifiedPage?.records?.find?.((row) => id(row?.orderId) === orderId)
context.assert(MODULE, '统一订单列表投影同一订单为 LOADED', unifiedListResponse,
Boolean(unifiedRow) && unifiedRow?.mainStatus === 'LOADED',
`orderId=${orderId}, found=${Boolean(unifiedRow)}, mainStatus=${unifiedRow?.mainStatus || 'missing'}`, OPTIONS)
const evidencePath = `${base}/loading/evidence?tenantId=${encodeURIComponent(tenantId)}`
const immutableEvidenceResponse = await context.request('GET', evidencePath)
const immutableEvidence = context.apiSuccess(MODULE, '读取 LOADED 后不可变证据快照', immutableEvidenceResponse, '', OPTIONS)
context.assert(MODULE, '统一硬门禁通过后五类有效证据仍完整', immutableEvidenceResponse,
fixture.evidenceTypes?.every?.((type) => immutableEvidence?.evidences?.some?.((item) =>
item.evidenceType === type && item.status !== 'VOID')),
`evidences=${immutableEvidence?.evidences?.length ?? 'missing'}`, OPTIONS)
const immutablePlanResponse = await context.request('GET', planPath)
const immutablePlan = context.apiSuccess(MODULE, '读取 LOADED 后装柜计划快照', immutablePlanResponse, '', OPTIONS)
const finalVersion = Number(finalData?.summary?.version)
const historySignature = JSON.stringify((finalData?.statusHistory || []).map((item) => ({
action: item?.action || null, from: item?.fromStatus || null, to: item?.toStatus || null,
operatorId: id(item?.operatorId), occurredAt: item?.occurredAt || item?.createdAt || null,
})))
const immutableEvidenceSignature = evidenceSignature(immutableEvidence)
const immutablePlanSignature = JSON.stringify({
version: Number(immutablePlan?.version), status: immutablePlan?.status || null,
containerNo: immutablePlan?.containerNo || null, voyageNo: immutablePlan?.voyageNo || null,
items: (immutablePlan?.items || []).map((item) => ({ id: id(item?.id), frozenAt: item?.frozenAt || null })),
})
const completeNewKey = await request(context, 'POST',
`${taskPath}/complete`,
{ expectedTaskVersion: Number(completed?.version),
operationId: `${context.marker}-pda-final-complete-new-key`.slice(0, 128), remark: `${context.marker}-loaded-new-key` },
{ 'Idempotency-Key': `${context.marker}-p3-complete-loaded-new-key`.slice(0, 128) })
context.assert(MODULE, 'LOADED 后使用新键重复 complete 幂等保持终态', completeNewKey,
completeNewKey.http === 200 && codeOf(completeNewKey) === '200'
&& completeNewKey.body?.data?.taskStatus === 'COMPLETED',
`HTTP=${completeNewKey.http}, code=${codeOf(completeNewKey)}, status=${completeNewKey.body?.data?.taskStatus || 'missing'}`, OPTIONS)
const completeSameKeyDifferentBody = await request(context, 'POST',
`${taskPath}/complete`,
{ ...completeBody, remark: `${context.marker}-different-complete-body` },
{ 'Idempotency-Key': completeKey })
context.addStep(MODULE, 'LOADED complete 同键异参返回冲突', completeSameKeyDifferentBody,
completeSameKeyDifferentBody.http === 200 && codeOf(completeSameKeyDifferentBody) === 'A0409'
&& Boolean(completeSameKeyDifferentBody.requestId) && Boolean(completeSameKeyDifferentBody.traceId),
`HTTP=${completeSameKeyDifferentBody.http}, code=${codeOf(completeSameKeyDifferentBody)}, requestId=${completeSameKeyDifferentBody.requestId || 'missing'}, traceId=${completeSameKeyDifferentBody.traceId || 'missing'}`, OPTIONS)
const postLoadedEvidence = await request(context, 'POST', evidencePath, {
expectedVersion: 0, evidenceType: 'PHOTO_STEP_2', fileId: fixture.postLoadedFileId,
fileName: 'client-must-not-override.png', sealNo: null, remark: context.marker,
}, { 'Idempotency-Key': `${context.marker}-p3-evidence-after-loaded`.slice(0, 128) })
context.addStep(MODULE, 'LOADED 后新增装柜证据被拒绝', postLoadedEvidence,
postLoadedEvidence.http === 200 && codeOf(postLoadedEvidence) === 'A0409'
&& Boolean(postLoadedEvidence.requestId) && Boolean(postLoadedEvidence.traceId),
`HTTP=${postLoadedEvidence.http}, code=${codeOf(postLoadedEvidence)}, requestId=${postLoadedEvidence.requestId || 'missing'}, traceId=${postLoadedEvidence.traceId || 'missing'}`, OPTIONS)
const postLoadedVoid = await request(context, 'POST',
`${base}/loading/evidence/${encodeURIComponent(id(fixture.immutableEvidenceId))}/void?tenantId=${encodeURIComponent(tenantId)}`,
{ expectedVersion: Number(fixture.immutableEvidenceVersion), reason: `${context.marker}-void-after-loaded` },
{ 'Idempotency-Key': `${context.marker}-p3-evidence-void-after-loaded`.slice(0, 128) })
context.addStep(MODULE, 'LOADED 后作废装柜证据被拒绝', postLoadedVoid,
postLoadedVoid.http === 200 && codeOf(postLoadedVoid) === 'A0409'
&& Boolean(postLoadedVoid.requestId) && Boolean(postLoadedVoid.traceId),
`HTTP=${postLoadedVoid.http}, code=${codeOf(postLoadedVoid)}, requestId=${postLoadedVoid.requestId || 'missing'}, traceId=${postLoadedVoid.traceId || 'missing'}`, OPTIONS)
const immutableTransport = await request(context, 'PATCH',
`/api/platform/v1/first-leg/loading-plans/${encodeURIComponent(id(fixture.loadingPlanId))}/transport?warehouseId=${encodeURIComponent(id(fixture.warehouseId))}`,
{ containerNo: `MUTATE-${context.marker}`.slice(0, 64), voyageNo: 'MUTATE',
expectedVersion: Number(immutablePlan?.version), operationReason: `${context.marker}-after-loaded` },
{ 'Idempotency-Key': `${context.marker}-p3-transport-after-loaded`.slice(0, 128) })
context.addStep(MODULE, 'LOADED 后运输信息修改被拒绝', immutableTransport,
immutableTransport.http === 200 && codeOf(immutableTransport) === 'A0409'
&& Boolean(immutableTransport.requestId) && Boolean(immutableTransport.traceId),
`HTTP=${immutableTransport.http}, code=${codeOf(immutableTransport)}, requestId=${immutableTransport.requestId || 'missing'}, traceId=${immutableTransport.traceId || 'missing'}`, OPTIONS)
const completedPlanItem = immutablePlan?.items?.[0]
const assignmentAfterCompleted = await request(context, 'POST',
`/api/platform/v1/first-leg/smart-loading/plans/${encodeURIComponent(loadingPlanId)}/assignments`, {
inboundId: fixture.incrementalInboundId || inboundId,
inboundDetailId: id(completedPlanItem?.inboundDetailId), packageCount: 1,
expectedVersion: Number(immutablePlan?.version),
operationId: `${context.marker}-assign-after-completed`.slice(0, 128),
})
context.addStep(MODULE, 'COMPLETED 后新增计划明细被拒绝', assignmentAfterCompleted,
assignmentAfterCompleted.http === 200 && codeOf(assignmentAfterCompleted) === 'A0409',
`HTTP=${assignmentAfterCompleted.http}, code=${codeOf(assignmentAfterCompleted)}`, OPTIONS)
const adjustAfterCompleted = await request(context, 'PATCH',
`/api/platform/v1/first-leg/smart-loading/plans/${encodeURIComponent(loadingPlanId)}/items/${encodeURIComponent(id(completedPlanItem?.id))}`, {
packageCount: Number(completedPlanItem?.packageCount || 1), expectedPlanVersion: Number(immutablePlan?.version),
expectedItemVersion: Number(completedPlanItem?.version), operationId: `${context.marker}-adjust-after-completed`.slice(0, 128),
})
context.addStep(MODULE, 'COMPLETED 后调整计划明细被拒绝', adjustAfterCompleted,
adjustAfterCompleted.http === 200 && codeOf(adjustAfterCompleted) === 'A0409',
`HTTP=${adjustAfterCompleted.http}, code=${codeOf(adjustAfterCompleted)}`, OPTIONS)
const deleteAfterCompleted = await request(context, 'DELETE',
`/api/platform/v1/first-leg/smart-loading/plans/${encodeURIComponent(loadingPlanId)}/items/${encodeURIComponent(id(completedPlanItem?.id))}`, {
expectedPlanVersion: Number(immutablePlan?.version), expectedItemVersion: Number(completedPlanItem?.version),
operationId: `${context.marker}-delete-after-completed`.slice(0, 128),
})
context.addStep(MODULE, 'COMPLETED 后删除计划明细被拒绝', deleteAfterCompleted,
deleteAfterCompleted.http === 200 && codeOf(deleteAfterCompleted) === 'A0409',
`HTTP=${deleteAfterCompleted.http}, code=${codeOf(deleteAfterCompleted)}`, OPTIONS)
const afterBoundaryResponse = await context.request('GET', detailPath)
const afterBoundary = context.apiSuccess(MODULE, '回查 LOADED 高风险边界后的入库快照', afterBoundaryResponse, '', OPTIONS)
const afterEvidenceResponse = await context.request('GET', evidencePath)
const afterEvidence = context.apiSuccess(MODULE, '回查 LOADED 高风险边界后的证据快照', afterEvidenceResponse, '', OPTIONS)
const afterPlanResponse = await context.request('GET', planPath)
const afterPlan = context.apiSuccess(MODULE, '回查 LOADED 高风险边界后的计划快照', afterPlanResponse, '', OPTIONS)
const afterHistorySignature = JSON.stringify((afterBoundary?.statusHistory || []).map((item) => ({
action: item?.action || null, from: item?.fromStatus || null, to: item?.toStatus || null,
operatorId: id(item?.operatorId), occurredAt: item?.occurredAt || item?.createdAt || null,
})))
const afterPlanSignature = JSON.stringify({
version: Number(afterPlan?.version), status: afterPlan?.status || null,
containerNo: afterPlan?.containerNo || null, voyageNo: afterPlan?.voyageNo || null,
items: (afterPlan?.items || []).map((item) => ({ id: id(item?.id), frozenAt: item?.frozenAt || null })),
})
context.assert(MODULE, 'LOADED 后拒绝与幂等请求保持状态、版本、证据、计划和审计零漂移', afterBoundaryResponse,
afterBoundary?.summary?.processStatus === 'LOADED'
&& Number(afterBoundary?.summary?.version) === finalVersion
&& afterHistorySignature === historySignature
&& evidenceSignature(afterEvidence) === immutableEvidenceSignature
&& afterPlanSignature === immutablePlanSignature,
`status=${afterBoundary?.summary?.processStatus || 'missing'}, version=${afterBoundary?.summary?.version ?? 'missing'}, histories=${afterBoundary?.statusHistory?.length ?? 'n/a'}, evidences=${afterEvidence?.evidences?.length ?? 'n/a'}, planVersion=${afterPlan?.version ?? 'missing'}`, OPTIONS)
context.createdResources.phase3Loading = buildFinalizeResource(fixture, finalData, completeResponse)
}