修复旧精修事务稳定入口迁移
从已校验旧事务快照推导原稳定入口路径 允许稳定入口缺失时在下一次提交重建 补充旧事务连续精修回归测试与文档
This commit is contained in:
@@ -3017,7 +3017,24 @@ fn asset_canvas_runtime_entry_path(
|
||||
if journal.asset_id != asset_id {
|
||||
continue;
|
||||
}
|
||||
let Some(path) = journal.runtime_entry_relative_path else {
|
||||
let candidate_path = match journal.runtime_entry_relative_path.clone() {
|
||||
Some(path) => Some(path),
|
||||
None => {
|
||||
let journal_manifest_before: GameCreationAppManifest = read_asset_canvas_snapshot(
|
||||
root,
|
||||
&journal.commit_id,
|
||||
"manifest.before.json",
|
||||
&journal.manifest_before_sha256,
|
||||
)?;
|
||||
journal_manifest_before
|
||||
.assets
|
||||
.iter()
|
||||
.find(|asset| asset.id == asset_id)
|
||||
.map(|asset| asset.local_path.clone())
|
||||
.filter(|path| !path.starts_with("assets/canvas/"))
|
||||
}
|
||||
};
|
||||
let Some(path) = candidate_path else {
|
||||
continue;
|
||||
};
|
||||
let is_newer = historical
|
||||
@@ -3043,10 +3060,7 @@ fn asset_canvas_runtime_entry_path(
|
||||
if source.local_path.starts_with("assets/canvas/") {
|
||||
return Ok(None);
|
||||
}
|
||||
let path = resolve_local_project_path(root, &source.local_path)?;
|
||||
if !path.is_file() {
|
||||
return Ok(None);
|
||||
}
|
||||
resolve_local_project_path(root, &source.local_path)?;
|
||||
Ok(Some(source.local_path.clone()))
|
||||
}
|
||||
|
||||
|
||||
@@ -584,6 +584,118 @@ fn refine_preserves_source_and_records_non_destructive_lineage() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_refine_transaction_preserves_runtime_entry_for_next_commit() {
|
||||
let directory = tempfile::tempdir().expect("create legacy runtime entry fixture");
|
||||
init_local_game_project_at(directory.path(), PROJECT_ID, "旧事务精修项目")
|
||||
.expect("initialize project");
|
||||
fs::write(
|
||||
directory.path().join("assets/source.png"),
|
||||
png_bytes([190, 40, 90, 255]),
|
||||
)
|
||||
.expect("write source asset");
|
||||
let manifest_path = directory.path().join(".agent/manifest.json");
|
||||
let mut manifest = read_manifest(&manifest_path).expect("read manifest");
|
||||
manifest.assets.push(GameCreationAppAssetManifestEntry {
|
||||
id: "source-asset".to_string(),
|
||||
kind: "illustration".to_string(),
|
||||
media_type: "image/png".to_string(),
|
||||
local_path: "assets/source.png".to_string(),
|
||||
source: GameCreationAppAssetSource {
|
||||
kind: GameCreationAppAssetSourceKind::Uploaded,
|
||||
canvas_project_id: None,
|
||||
resource_id: None,
|
||||
asset_object_id: None,
|
||||
task_id: None,
|
||||
prompt: None,
|
||||
model: None,
|
||||
generation_route: None,
|
||||
generation_kind: None,
|
||||
reference_resource_ids: Vec::new(),
|
||||
},
|
||||
});
|
||||
write_manifest(&manifest_path, &manifest).expect("register source asset");
|
||||
let draft = create_asset_canvas_draft_at(
|
||||
directory.path(),
|
||||
&create_input(
|
||||
directory.path(),
|
||||
&Uuid::new_v4().to_string(),
|
||||
AssetCanvasIntent::Refine,
|
||||
Some("source-asset".to_string()),
|
||||
),
|
||||
)
|
||||
.expect("create refine draft")
|
||||
.draft;
|
||||
let fixture = Fixture {
|
||||
directory,
|
||||
draft: draft.clone(),
|
||||
png: png_bytes([30, 90, 210, 255]),
|
||||
};
|
||||
let staged = stage_image(&fixture, &draft);
|
||||
let first_input = commit_input(
|
||||
&fixture,
|
||||
&draft,
|
||||
&staged,
|
||||
Uuid::new_v4().to_string(),
|
||||
Uuid::new_v4().to_string(),
|
||||
);
|
||||
commit_asset_canvas_at(fixture.root(), &first_input)
|
||||
.expect("commit legacy runtime entry image");
|
||||
|
||||
let mut legacy_journal = read_asset_canvas_journal(fixture.root(), &first_input.commit_id)
|
||||
.expect("read legacy journal")
|
||||
.expect("legacy journal retained");
|
||||
legacy_journal.runtime_entry_relative_path = None;
|
||||
legacy_journal.runtime_entry_refreshed = false;
|
||||
write_asset_canvas_journal(fixture.root(), &legacy_journal)
|
||||
.expect("persist legacy journal shape");
|
||||
fs::write(
|
||||
fixture.root().join("assets/source.png"),
|
||||
png_bytes([1, 2, 3, 255]),
|
||||
)
|
||||
.expect("corrupt legacy runtime entry");
|
||||
|
||||
let draft_after_first =
|
||||
read_asset_canvas_draft_locked(fixture.root(), PROJECT_ID, &draft.draft_id)
|
||||
.expect("read draft after legacy commit")
|
||||
.expect("draft retained after legacy commit");
|
||||
let second_png = png_bytes([210, 180, 40, 255]);
|
||||
let staged_second = stage_asset_canvas_image_at(
|
||||
fixture.root(),
|
||||
&StageAssetCanvasImageInput {
|
||||
project_path: project_path(fixture.root()),
|
||||
expected_project_id: PROJECT_ID.to_string(),
|
||||
draft_id: draft.draft_id.clone(),
|
||||
expected_draft_revision: draft_after_first.revision,
|
||||
media_type: "image/png".to_string(),
|
||||
bytes: second_png.clone(),
|
||||
},
|
||||
)
|
||||
.expect("stage second legacy image");
|
||||
let second_input = commit_input(
|
||||
&fixture,
|
||||
&draft_after_first,
|
||||
&staged_second,
|
||||
Uuid::new_v4().to_string(),
|
||||
Uuid::new_v4().to_string(),
|
||||
);
|
||||
commit_asset_canvas_at(fixture.root(), &second_input)
|
||||
.expect("commit after legacy runtime entry migration");
|
||||
|
||||
assert_eq!(
|
||||
fs::read(fixture.root().join("assets/source.png")).expect("read migrated runtime entry"),
|
||||
second_png
|
||||
);
|
||||
let second_journal = read_asset_canvas_journal(fixture.root(), &second_input.commit_id)
|
||||
.expect("read migrated journal")
|
||||
.expect("migrated journal retained");
|
||||
assert_eq!(
|
||||
second_journal.runtime_entry_relative_path.as_deref(),
|
||||
Some("assets/source.png")
|
||||
);
|
||||
assert!(second_journal.runtime_entry_refreshed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn candidate_commit_freezes_layer_and_media_identity_and_keeps_draft_editable() {
|
||||
let fixture = initialize_refine_fixture();
|
||||
|
||||
@@ -14353,6 +14353,7 @@
|
||||
|
||||
- 图片精修的 manifest 继续指向不可变正式版本 `assets/canvas/<name>--<commitId>.png`;游戏源码已引用的原路径(例如 `assets/direct-game-background.png`)是稳定运行入口,不要求代码改写。
|
||||
- “设为最终图”提交、幂等重放和事务恢复都会校验不可变版本的字节、SHA-256、尺寸后刷新稳定入口。稳定入口缺失或损坏时,以不可变版本为恢复来源。
|
||||
- 旧事务 journal 尚无稳定入口字段时,后续精修必须从该事务已校验的 `manifest.before.json` 迁移原入口路径,不能因为当前 manifest 已指向 `assets/canvas/**` 而丢失游戏运行入口。
|
||||
|
||||
## 2026-08-21 JavaScript 工程统一为 npm workspaces
|
||||
|
||||
|
||||
@@ -911,7 +911,7 @@ cancelling
|
||||
| A35 | 精修文件名包含历史提交后缀 | 后续精修重新打开当前 `localPath`,或再次生成 / 设为最终图 | 统一剥离文件名末尾一个或多个 `--<uuid>` 后缀并规范化为合法 1..=80 字符显示名;生成与最终提交使用同一结果 |
|
||||
| A36 | 确定性提交参数无效 | 候选提交名称或用途在校验阶段失败 | 在读取候选、staging、transaction 或 ledger 写入前零副作用失败;UI 作为输入校验错误允许继续编辑,不触发安全恢复 |
|
||||
| A37 | 候选首次确认 | 生成完成后与旧 autosave 并发 | 前端先同步 authoritative layers 并保存确认;确认前后端把未确认候选层合回旧保存,重启恢复可从私有 ledger 重建候选层,确认后的显式删除仍允许 |
|
||||
| A38 | 稳定运行入口 | 精修替换已在游戏源码中引用的图片 | manifest 指向不可变正式版本,同时原稳定入口路径不变并刷新为新版本字节;幂等重放和事务恢复会修复缺失或不匹配入口,游戏源码不需要改路径 |
|
||||
| A38 | 稳定运行入口 | 精修替换已在游戏源码中引用的图片,或继续精修旧版本事务创建的资源 | manifest 指向不可变正式版本,同时原稳定入口路径不变并刷新为新版本字节;新事务可从旧事务 `manifest.before.json` 迁移稳定入口身份,幂等重放和事务恢复会修复缺失或不匹配入口,游戏源码不需要改路径 |
|
||||
|
||||
阶段一至五最终审计只有在矩阵对应的纯模型、共享 React、Web adapter、Tauri adapter、Rust 持久化与 AppSurface 测试全部通过后,才可宣称图片素材创作正式闭环完成。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user