素材上传不再每次触发一次拒收提示:上传与清单配对读放进同一个函数
- 原缺陷:上传路径在上传**前**读 get_local_game_project_revision,上传完把那个**旧 revision** 交给 reloadManifestAfterAssetCommand;后者重新读盘拿**新清单**再打上旧 revision。上传本身会推进项目 revision(Rust advance_agent_runtime_project_revision_locked),于是这一对是撕裂的,「同 revision 不同指纹」被 projectResourceLiveUpdateModel 判成 revision-conflict —— 用户每上传一次素材就先看到一条「资源清单更新被拒收…」。 - projectResourceLiveUpdateModel.ts 新增 uploadProjectAssetFilesAndReadSnapshot:上传动作与"配对读"由同一个函数拥有,配对读复用既有 rereadAuthoritativeProjectManifestSnapshot(同一次读里配对 revision + 清单,并再读一次 revision 确认没被写盘插队)。调用方拿不到中间那个 revision,也就没有机会把它贴错。 - index.tsx 的 uploadResourcePanelFiles 改用它,删掉上传前那次 revision 读取与旧 revision 的传递。 - 附带文案误报:describeProjectManifestMergeRejection 的 unresolved 文案由「重新读取磁盘清单失败,请重新打开项目」改为「未能按磁盘清单重新对齐,请重新打开项目」。unresolved 有三条进入路径,其中两条**重读是成功的**(读到的一对比手上旧、或读到的是撕裂的一对),写成"读盘失败"是误报;新文案对所有路径都成立。WorkspaceLauncher.tsx 在"读到了但不采用"那一支补注释说明 stage 不是读失败。 - 断言:projectResourceLiveUpdateModel.test.ts 新增「素材上传后的清单快照必须配对」——假后端在上传时推进 revision,断言 merge 判 accepted 且 projectManifestMergeRejectionDecision 为 null(即"上传一次不产生拒收提示"),并附一条对照钉子显式写出旧形状、断言它必被判 revision-conflict;另新增 unresolved 文案不得包含"读取磁盘清单失败"的用例。 - 变异验证(提交前已跑):把 uploadProjectAssetFilesAndReadSnapshot 还原成"上传前读 revision + 上传后读清单"→ 配对用例变红(并给出 revision-conflict 的失败信息),还原后复跑 15 passed。 - 门禁:projectResourceLiveUpdateModel 15 passed、workspaceLauncherManifestMerge 5 passed。
This commit is contained in:
+58
-1
@@ -172,7 +172,11 @@ export function describeProjectManifestMergeRejection(
|
||||
if (stage === 'recovered') {
|
||||
return `资源清单更新被拒收(${cause}),已按磁盘清单重新对齐`;
|
||||
}
|
||||
return `资源清单更新被拒收(${cause}),重新读取磁盘清单失败,请重新打开项目`;
|
||||
// `unresolved` 不等于"读盘失败":重读**可能已经成功**,只是拿到的那一对
|
||||
// `(revision, 清单)` 按 CAS 判据不能被采用(版本号比手上的旧,或读到的是撕裂的一对)。
|
||||
// 所以这里说"未能按磁盘清单重新对齐" —— 它对所有进入 `unresolved` 的路径都成立,
|
||||
// 而"重新读取磁盘清单失败"只对其中一条成立,是一条会误导用户的假陈述。
|
||||
return `资源清单更新被拒收(${cause}),未能按磁盘清单重新对齐,请重新打开项目`;
|
||||
}
|
||||
|
||||
export type ProjectManifestRereadInput = {
|
||||
@@ -217,6 +221,59 @@ export async function rereadAuthoritativeProjectManifestSnapshot(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 素材上传之后的清单快照:**上传动作与"配对读"必须由同一个函数拥有**。
|
||||
*
|
||||
* ⚠️ 不许把「上传**前**读到的 revision」贴在「上传**后**读到的清单」上。上传本身会推进
|
||||
* 项目 revision(Rust 侧 `advance_agent_runtime_project_revision_locked`),于是这一对是
|
||||
* 撕裂的:`mergeProjectManifestSnapshot` 看到"同一版本号、内容不同" ⇒ 判 `revision-conflict`
|
||||
* ⇒ 用户每上传一次素材就先看到一条「资源清单更新被拒收(同一版本号上的清单内容不一致)」。
|
||||
*
|
||||
* 唯一正确的形状是 [`rereadAuthoritativeProjectManifestSnapshot`]:它**在同一次读里配对**
|
||||
* revision 与清单,并再读一次 revision 确认没被写盘插队。把上传与这次配对读放在同一个
|
||||
* 函数里,是为了让"先读 revision 再上传"这种错配**不可能**再被写出来 —— 调用方拿不到
|
||||
* 中间那个 revision,也就没有机会把它贴错。
|
||||
*/
|
||||
export async function uploadProjectAssetFilesAndReadSnapshot(input: {
|
||||
projectPath: string;
|
||||
projectId: string;
|
||||
commitId: string;
|
||||
files: readonly {
|
||||
fileName: string;
|
||||
mediaType: string;
|
||||
bytes: number[];
|
||||
}[];
|
||||
invoke<T>(command: string, args: Record<string, unknown>): Promise<T>;
|
||||
}): Promise<ProjectManifestSnapshot | null> {
|
||||
for (const file of input.files) {
|
||||
await input.invoke('upload_local_asset', {
|
||||
projectPath: input.projectPath,
|
||||
fileName: file.fileName,
|
||||
mediaType: file.mediaType,
|
||||
bytes: file.bytes,
|
||||
});
|
||||
}
|
||||
const fresh = await rereadAuthoritativeProjectManifestSnapshot({
|
||||
projectPath: input.projectPath,
|
||||
projectId: input.projectId,
|
||||
readRevision: async () => {
|
||||
const status = await input.invoke<{ revision: number }>(
|
||||
'get_local_game_project_revision',
|
||||
{ projectPath: input.projectPath },
|
||||
);
|
||||
return status.revision;
|
||||
},
|
||||
readManifest: () =>
|
||||
input.invoke<GameCreationAppManifest>('get_local_game_manifest', {
|
||||
projectPath: input.projectPath,
|
||||
commandId: 'asset.list',
|
||||
}),
|
||||
});
|
||||
return fresh
|
||||
? { ...fresh, source: 'asset-command', commitId: input.commitId }
|
||||
: null;
|
||||
}
|
||||
|
||||
export type ResourceFocusIntent = {
|
||||
flowId: string;
|
||||
saveAttemptId: string;
|
||||
|
||||
Reference in New Issue
Block a user