Merge remote-tracking branch 'origin/master' into feat/agc-canvas-resource-workbench-v3
This commit is contained in:
@@ -3640,7 +3640,38 @@ pub(crate) fn merge_game_creator_config_file(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_game_creator_config_file(path: &Path) -> Result<Option<String>, String> {
|
||||
/// Only files inside the managed runtime config directory (real credentials)
|
||||
/// may use the private-read channel: it hardens the owner/DACL on every read.
|
||||
/// Repository-adjacent fallback templates and overlay files are shared inputs
|
||||
/// that can be git-tracked; privatizing one on read silently locks the
|
||||
/// worktree template to whichever account happened to run the dev CLI, so
|
||||
/// they must go through the non-mutating snapshot channel instead.
|
||||
pub(crate) fn game_creator_config_path_is_runtime_managed(path: &Path) -> bool {
|
||||
game_creator_runtime_config_dir().is_some_and(|directory| path.starts_with(directory))
|
||||
}
|
||||
|
||||
fn read_game_creator_snapshot_file_to_string(
|
||||
path: &Path,
|
||||
label: &str,
|
||||
max_bytes: u64,
|
||||
) -> Result<String, String> {
|
||||
let (mut file, metadata) = open_project_snapshot_regular_file(path, label)?;
|
||||
if metadata.len() > max_bytes {
|
||||
return Err(format!("{label}过大,已拒绝读取:{}", path.display()));
|
||||
}
|
||||
let mut content = String::with_capacity(metadata.len() as usize);
|
||||
file.read_to_string(&mut content)
|
||||
.map_err(|error| format!("读取{label}失败:{}: {error}", path.display()))?;
|
||||
let final_metadata = file
|
||||
.metadata()
|
||||
.map_err(|error| format!("复核{label}失败:{}: {error}", path.display()))?;
|
||||
if final_metadata.len() != metadata.len() {
|
||||
return Err(format!("{label}读取期间文件发生漂移:{}", path.display()));
|
||||
}
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
pub(crate) fn read_game_creator_config_file(path: &Path) -> Result<Option<String>, String> {
|
||||
let backup_path = game_creator_config_backup_path(path);
|
||||
let path_exists = validate_game_creator_config_file_entry(path)?;
|
||||
let read_path = if path_exists {
|
||||
@@ -3650,7 +3681,11 @@ fn read_game_creator_config_file(path: &Path) -> Result<Option<String>, String>
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
let content = read_game_creator_private_file_to_string(read_path, "客户端配置", 256 * 1024)?;
|
||||
let content = if game_creator_config_path_is_runtime_managed(read_path) {
|
||||
read_game_creator_private_file_to_string(read_path, "客户端配置", 256 * 1024)?
|
||||
} else {
|
||||
read_game_creator_snapshot_file_to_string(read_path, "客户端配置", 256 * 1024)?
|
||||
};
|
||||
Ok(Some(content))
|
||||
}
|
||||
|
||||
|
||||
@@ -1184,6 +1184,56 @@ fn llm_config_check_reports_per_agent_status_without_leaking_keys() {
|
||||
fs::remove_dir_all(root).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fallback_template_read_stays_on_snapshot_channel_outside_runtime_dir() {
|
||||
let root = unique_project_path();
|
||||
let template_dir = root.join("apps").join("ai-game-creator-shell");
|
||||
fs::create_dir_all(&template_dir).expect("fallback template dir");
|
||||
let template = template_dir.join(GAME_CREATOR_CONFIG_FILE_NAME);
|
||||
fs::write(
|
||||
&template,
|
||||
"{\n \"llm\": { \"model\": \"fallback-template-model\" }\n}\n",
|
||||
)
|
||||
.expect("write fallback template");
|
||||
// 设置一个与模板无关的 runtime dir,让分类断言真正覆盖"按路径归属"而非
|
||||
// "runtime dir 为 None 时恒 false"的全局开关。
|
||||
let runtime_root = unique_project_path();
|
||||
fs::create_dir_all(&runtime_root).expect("unrelated runtime config dir");
|
||||
let _guard = use_test_runtime_config_dir(runtime_root.clone());
|
||||
|
||||
// 仓库旁边的回退模板是共享输入,读取绝不能走会收紧 owner/DACL 的私有通道。
|
||||
assert!(!game_creator_config_path_is_runtime_managed(&template));
|
||||
assert!(game_creator_config_path_is_runtime_managed(
|
||||
&runtime_root.join(GAME_CREATOR_CONFIG_FILE_NAME)
|
||||
));
|
||||
let content = read_game_creator_config_file(&template).expect("read fallback template");
|
||||
assert!(content
|
||||
.expect("fallback template content")
|
||||
.contains("fallback-template-model"));
|
||||
fs::remove_dir_all(root).ok();
|
||||
fs::remove_dir_all(runtime_root).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_config_read_stays_on_private_channel_inside_runtime_dir() {
|
||||
let root = unique_project_path();
|
||||
fs::create_dir_all(&root).expect("runtime config dir");
|
||||
let config_path = root.join(GAME_CREATOR_CONFIG_FILE_NAME);
|
||||
fs::write(
|
||||
&config_path,
|
||||
"{\n \"llm\": { \"model\": \"managed-config-model\" }\n}\n",
|
||||
)
|
||||
.expect("write runtime config");
|
||||
let _guard = use_test_runtime_config_dir(root.clone());
|
||||
|
||||
assert!(game_creator_config_path_is_runtime_managed(&config_path));
|
||||
let content = read_game_creator_config_file(&config_path).expect("read runtime config");
|
||||
assert!(content
|
||||
.expect("runtime config content")
|
||||
.contains("managed-config-model"));
|
||||
fs::remove_dir_all(root).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn llm_config_check_reports_agent_specific_config_paths() {
|
||||
let root = unique_project_path();
|
||||
|
||||
Reference in New Issue
Block a user