Compare commits

...

2 Commits

Author SHA1 Message Date
kdletters 563dac9238 Merge branch 'master' into codex/project-client-lock-single
Project CI / Repository checks (pull_request) Failing after 2m46s
Project CI / Frontend tests (pull_request) Successful in 3m25s
Project CI / Backend tests (pull_request) Successful in 6m7s
Project CI / Native shell tests (pull_request) Failing after 15m13s
2026-09-14 12:31:00 +08:00
kdletters fbd493029d 收敛项目客户端占用锁语义
Project CI / Repository checks (pull_request) Failing after 2m40s
Project CI / Frontend tests (pull_request) Successful in 3m20s
Project CI / Backend tests (pull_request) Successful in 8m14s
Project CI / Native shell tests (pull_request) Failing after 15m5s
统一同进程项目锁重入行为,避免客户端自身等待项目锁

补充锁收敛里程碑、实施计划与回归测试
2026-09-14 11:59:00 +08:00
5 changed files with 77 additions and 11 deletions
@@ -860,14 +860,11 @@ pub(crate) fn acquire_project_write_lock_failure(
} }
} }
} }
if crate::agent::autonomous_game_build_root_run_active_at(root) if project_write_lock_is_owned_by_current_process(&path) {
&& project_write_lock_is_owned_by_current_process(&path) // A project lock is the client-use lock. Nested calls in
{ // the same client process must reuse that ownership instead
// The autonomous game-build lane intentionally permits // of waiting on their own durable marker. Cross-process
// parallel specialist actions. If the durable lock belongs // contenders still take the normal retryable path.
// to this very process, contention is an in-process overlap,
// not another application editing the project. Return an
// advisory guard and leave the real lock untouched.
return Ok(ProjectWriteLock { return Ok(ProjectWriteLock {
path, path,
content: String::new(), content: String::new(),
@@ -5611,12 +5611,14 @@ fn local_project_checkpoint_diff_restore_and_index_are_recorded() {
} }
#[test] #[test]
fn project_write_lock_rejects_parallel_writer_and_releases_on_drop() { fn project_write_lock_reuses_same_process_owner_and_releases_on_drop() {
let root = unique_project_path(); let root = unique_project_path();
let first = acquire_project_write_lock(&root, "file.write").expect("first lock"); let first = acquire_project_write_lock(&root, "file.write").expect("first lock");
let error = acquire_project_write_lock(&root, "file.delete").expect_err("second lock fails"); let nested = acquire_project_write_lock(&root, "file.delete")
assert!(error.contains("项目正在被其他写操作占用")); .expect("same process must reuse the client project lock");
drop(nested);
assert!(root.join(PROJECT_WRITE_LOCK_PATH).exists());
drop(first); drop(first);
acquire_project_write_lock(&root, "file.delete").expect("lock released"); acquire_project_write_lock(&root, "file.delete").expect("lock released");
@@ -0,0 +1,34 @@
# 【实施计划】项目客户端占用锁收敛-2026-09-14
Version: 1
Status: in-progress
Date: 2026-09-14
Milestone: `【里程碑】项目客户端占用锁收敛-2026-09-14.md`
## 代码边界
- `apps/ai-game-creator-shell/src-tauri/src/project/write_lock.rs`
- `apps/ai-game-creator-shell/src-tauri/src/runner/project_owner.rs`
- `apps/ai-game-creator-shell/src-tauri/src/runner/protocol.rs`
- 锁恢复、Runtime 写入和 Runner owner 定向测试
## 修改顺序
1. 统一同进程嵌套调用的项目锁语义,禁止自等待。
2. 盘点并迁移 Runner 的项目级 owner 文件到统一锁,保留诊断投影与跨 boot 恢复。
3. 删除重复项目级锁路径及其专属调用,保留底层原子写和 Git 锁。
4. 补齐同进程重入、跨进程占用、崩溃恢复和锁释放测试。
## 验证命令
- `cargo fmt --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml -- --check`
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml project_write_lock_reuses_same_process_owner_and_releases_on_drop --no-default-features`
- Runner owner 与 response stream 相关定向测试
- `npm run check:encoding`
- `git diff --check`
## 风险与回滚点
- Runner 与 GUI 可能是不同进程;统一锁前必须验证同一客户端不会互相阻塞。
-`.agent/runtime/execution-owner.lock` 残留需要按 PID/启动身份安全回收,不能直接删除。
- 若跨 boot 恢复或 GUI/Runner 联动回归,回滚统一路径迁移,保留已验证的同进程重入修复。
@@ -0,0 +1,28 @@
# 【里程碑】项目客户端占用锁收敛-2026-09-14
Version: 1
Status: in-progress
Date: 2026-09-14
Parent Spec: `docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`
## 目标
项目只保留一个面向客户端占用的项目级跨进程锁,防止多个客户端同时打开同一项目;同一客户端进程内的嵌套调用复用既有项目锁,不因自身持锁进入等待。
## 边界
- 项目客户端占用锁与项目写入调用的职责统一,跨进程竞争仍返回占用语义。
- Agent DB、session lane、manifest 原子写和 Git 自身的底层一致性机制不在本里程碑删除范围内。
- 不改变项目 revision、权限、幂等、恢复和数据格式合同。
## 验收标准
- 同一进程内嵌套取得项目锁立即返回 advisory guard,不等待、不删除真实持有者锁。
- 不同进程持有项目锁时仍保持占用失败与残留回收判据。
- 客户端项目占用入口与 Runtime 写入入口不会各自维护第二个项目级锁文件。
- 锁释放后下一客户端可重新取得锁。
- 定向 Rust 锁测试、`cargo fmt --check``npm run check:encoding``git diff --check` 通过。
## 未决事项
- Runner 的 `execution-owner.lock` 如何迁移到统一客户端占用锁,需要补充跨进程启动、恢复和诊断测试后再落地。
@@ -1363,3 +1363,8 @@ DirectProject 使用 `approvalPolicy=never`,避免每次原生调用再经过
- **成功后行为**:重读 manifest,**不切换版本**(没有新版本可切),**不自动重载 / 重启运行中的预览**(PRD §3.2 末条),不做运行时资源重映射。可见变化只有资源卡「当前使用」高亮移到替换素材、`@` 面板「当前版本素材」更新。 - **成功后行为**:重读 manifest,**不切换版本**(没有新版本可切),**不自动重载 / 重启运行中的预览**(PRD §3.2 末条),不做运行时资源重映射。可见变化只有资源卡「当前使用」高亮移到替换素材、`@` 面板「当前版本素材」更新。
- **已知代价(用户已确认接受)**:**替换历史不可回溯**——替换前身份只剩那条审计与 manifest 的 `.previous` 副本;需要"某版本历史上换过什么"时要另立切片(PRD §3.2 / §5.3 保留为未来合同正是为此)。 - **已知代价(用户已确认接受)**:**替换历史不可回溯**——替换前身份只剩那条审计与 manifest 的 `.previous` 副本;需要"某版本历史上换过什么"时要另立切片(PRD §3.2 / §5.3 保留为未来合同正是为此)。
- **验证**:绑定改写通道定向 7 条(`project/manifest/version_binding_rewrite_tests.rs`+ 替换定向 8 条 + `shared-contracts` 20 条。**变异验证**:① 去掉「未放行版本整条相等」→ 两周转红;② 放行集合改成整个版本数组 → 「未放行版本」转红;③ 去掉长度检查 → 「不增不删」转红;④ 准入删掉 category → 硬门禁与候选两条转红;另有 ⑤ 改回"原地改既有版本但绕过放行口"→ 被「项目版本记录写入后不可修改、删除或重排」拦下。前端 13 条(模型 9 + 真链路 4)。AGC 全量 1231 passed / 4 skipped / 0 failed;共享美术画布组件 1385 passed`npm run ai-game-creator-shell:typecheck`(含 check-config)、`cargo check --locked --all-targets``npm run check:encoding``git diff --check` 全绿。`/api/external/v1`、SpacetimeDB schema、`packages/shared``shared-contracts` 的 wire DTO、manifest 结构、布局 sidecar schema 均未改动。 - **验证**:绑定改写通道定向 7 条(`project/manifest/version_binding_rewrite_tests.rs`+ 替换定向 8 条 + `shared-contracts` 20 条。**变异验证**:① 去掉「未放行版本整条相等」→ 两周转红;② 放行集合改成整个版本数组 → 「未放行版本」转红;③ 去掉长度检查 → 「不增不删」转红;④ 准入删掉 category → 硬门禁与候选两条转红;另有 ⑤ 改回"原地改既有版本但绕过放行口"→ 被「项目版本记录写入后不可修改、删除或重排」拦下。前端 13 条(模型 9 + 真链路 4)。AGC 全量 1231 passed / 4 skipped / 0 failed;共享美术画布组件 1385 passed`npm run ai-game-creator-shell:typecheck`(含 check-config)、`cargo check --locked --all-targets``npm run check:encoding``git diff --check` 全绿。`/api/external/v1`、SpacetimeDB schema、`packages/shared``shared-contracts` 的 wire DTO、manifest 结构、布局 sidecar schema 均未改动。
## 2026-09-14 项目客户端占用锁收敛
项目锁职责收敛为“客户端占用项目”这一事实:同一客户端进程内的嵌套项目写入调用复用已有项目锁并返回 advisory guard,不再等待自身持有的 `.agent/project.lock`;跨进程竞争继续沿用现有占用、残留回收和权限分类。Runner 的 `.agent/runtime/execution-owner.lock` 迁移到统一项目占用锁仍属于进行中的里程碑,完成前不改变其恢复诊断合同。