fix: isolate standalone target resolution
This commit is contained in:
+117
-22
@@ -217,6 +217,24 @@ fn resolve_tui_target<R: CliConnectionResolver + ?Sized>(
|
|||||||
selection: &TargetSelection,
|
selection: &TargetSelection,
|
||||||
workspace_root: &Path,
|
workspace_root: &Path,
|
||||||
) -> Result<Box<dyn Target>, ParseError> {
|
) -> Result<Box<dyn Target>, ParseError> {
|
||||||
|
if selection.explicit_local {
|
||||||
|
return resolve_connection_aware_cli_connection(
|
||||||
|
connection_resolver,
|
||||||
|
command,
|
||||||
|
true,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection.backend_url.is_none()
|
||||||
|
&& let Ok(target) =
|
||||||
|
resolve_connection_aware_cli_connection(connection_resolver, command, false, None, None)
|
||||||
|
&& target.kind() == TargetKind::Standalone
|
||||||
|
{
|
||||||
|
return Ok(target);
|
||||||
|
}
|
||||||
|
|
||||||
let workspace_id = match selection.workspace_id.clone() {
|
let workspace_id = match selection.workspace_id.clone() {
|
||||||
Some(workspace_id) => Some(workspace_id),
|
Some(workspace_id) => Some(workspace_id),
|
||||||
None => resolve_workspace_id_from_root(workspace_root)?,
|
None => resolve_workspace_id_from_root(workspace_root)?,
|
||||||
@@ -1160,23 +1178,22 @@ fn read_client_default_connection() -> Result<ClientDefaultConnection, ParseErro
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn read_client_config() -> Result<Option<ClientConfigFile>, ParseError> {
|
fn read_client_config() -> Result<Option<ClientConfigFile>, ParseError> {
|
||||||
|
let path = client_global_config_path();
|
||||||
|
read_client_config_from_global_path(path.as_deref())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_client_config_from_global_path(
|
||||||
|
path: Option<&Path>,
|
||||||
|
) -> Result<Option<ClientConfigFile>, ParseError> {
|
||||||
|
let Some(path) = path else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
let Some(overlay) = read_client_config_overlay(path)? else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
let mut config = ClientConfigFile::default();
|
let mut config = ClientConfigFile::default();
|
||||||
let mut found = false;
|
config.apply_overlay(overlay);
|
||||||
|
Ok(Some(config))
|
||||||
if let Some(path) = client_global_config_path() {
|
|
||||||
if let Some(overlay) = read_client_config_overlay(&path)? {
|
|
||||||
config.apply_overlay(overlay);
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let cwd_path = client_cwd_config_path()?;
|
|
||||||
if let Some(overlay) = read_client_config_overlay(&cwd_path)? {
|
|
||||||
config.apply_overlay(overlay);
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(found.then_some(config))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_client_config_overlay(path: &Path) -> Result<Option<ClientConfigOverlay>, ParseError> {
|
fn read_client_config_overlay(path: &Path) -> Result<Option<ClientConfigOverlay>, ParseError> {
|
||||||
@@ -1194,14 +1211,10 @@ fn client_global_config_path() -> Option<PathBuf> {
|
|||||||
manifest::paths::data_dir().map(|dir| dir.join("client").join("config.toml"))
|
manifest::paths::data_dir().map(|dir| dir.join("client").join("config.toml"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn client_cwd_config_path() -> Result<PathBuf, ParseError> {
|
|
||||||
Ok(current_dir()?.join(".yoi").join("client.config.toml"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn client_config_location_message() -> String {
|
fn client_config_location_message() -> String {
|
||||||
match client_global_config_path() {
|
match client_global_config_path() {
|
||||||
Some(path) => format!("{} or <cwd>/.yoi/client.config.toml", path.display()),
|
Some(path) => path.display().to_string(),
|
||||||
None => "<data_dir>/client/config.toml or <cwd>/.yoi/client.config.toml".to_string(),
|
None => "<data_dir>/client/config.toml".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2249,6 +2262,88 @@ backend = "shared"
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn explicit_standalone_ignores_malformed_repository_workspace_identity() {
|
||||||
|
let repository = tempfile::tempdir().unwrap();
|
||||||
|
std::fs::create_dir_all(repository.path().join(".yoi")).unwrap();
|
||||||
|
std::fs::write(
|
||||||
|
repository.path().join(".yoi/workspace.toml"),
|
||||||
|
"this is not valid toml = [",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let resolver = FixedCliConnectionResolver {
|
||||||
|
backend_url: "http://fake-backend.example",
|
||||||
|
};
|
||||||
|
|
||||||
|
let target = resolve_tui_target(
|
||||||
|
&resolver,
|
||||||
|
CliCommand::DefaultTui,
|
||||||
|
&TargetSelection {
|
||||||
|
explicit_local: true,
|
||||||
|
..TargetSelection::default()
|
||||||
|
},
|
||||||
|
repository.path(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(target.kind(), TargetKind::Standalone);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_standalone_ignores_malformed_repository_workspace_identity() {
|
||||||
|
let repository = tempfile::tempdir().unwrap();
|
||||||
|
std::fs::create_dir_all(repository.path().join(".yoi")).unwrap();
|
||||||
|
std::fs::write(
|
||||||
|
repository.path().join(".yoi/workspace.toml"),
|
||||||
|
"this is not valid toml = [",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let resolver = FixedCliConnectionResolver {
|
||||||
|
backend_url: "http://fake-backend.example",
|
||||||
|
};
|
||||||
|
|
||||||
|
let target = resolve_tui_target(
|
||||||
|
&resolver,
|
||||||
|
CliCommand::DefaultTui,
|
||||||
|
&TargetSelection::default(),
|
||||||
|
repository.path(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(target.kind(), TargetKind::Standalone);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn client_config_reader_uses_only_the_supplied_global_path() {
|
||||||
|
let root = tempfile::tempdir().unwrap();
|
||||||
|
let global = root.path().join("client/config.toml");
|
||||||
|
std::fs::create_dir_all(global.parent().unwrap()).unwrap();
|
||||||
|
std::fs::write(
|
||||||
|
&global,
|
||||||
|
"default_connection = \"local\"\n[backends.main]\nurl = \"http://backend.example\"\n",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
std::fs::create_dir_all(root.path().join("repository/.yoi")).unwrap();
|
||||||
|
std::fs::write(
|
||||||
|
root.path().join("repository/.yoi/client.config.toml"),
|
||||||
|
"this is not valid toml = [",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let config = read_client_config_from_global_path(Some(&global))
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
config.default_connection,
|
||||||
|
ClientDefaultConnection::Standalone
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
config.backends["main"].url.as_deref(),
|
||||||
|
Some("http://backend.example")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn default_backend_target_inherits_workspace_identity_from_workspace_root() {
|
fn default_backend_target_inherits_workspace_identity_from_workspace_root() {
|
||||||
let workspace = tempfile::tempdir().unwrap();
|
let workspace = tempfile::tempdir().unwrap();
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ worker
|
|||||||
|
|
||||||
## CLI / TUI routing
|
## CLI / TUI routing
|
||||||
|
|
||||||
- `yoi` の connection-aware command は `TargetKind::Standalone | Backend` の二択で dispatch する。`--local` と client config の `default_connection = "local"` は Standalone を選ぶ入力であり、旧 LocalBackend を有効化しない。
|
- `yoi` の connection-aware command は `TargetKind::Standalone | Backend` の二択で dispatch する。`--local` と client config の `default_connection = "local"` は Standalone を選ぶ入力であり、旧 LocalBackend を有効化しない。 Client config は repository `.yoi/client.config.toml` を読まず、repository `.yoi/workspace.toml` は Backend Workspace identity が必要な場合だけ参照する。
|
||||||
- Standalone の通常起動は `StandaloneHost`、restore は専用 `StandaloneStore` の session picker を使う。Workspace Worker list、PID、Unix socket、subprocess は探索しない。
|
- Standalone の通常起動は `StandaloneHost`、restore は専用 `StandaloneStore` の session picker を使う。Workspace Worker list、PID、Unix socket、subprocess は探索しない。
|
||||||
- `workers`、Backend Worker restore、Workspace panel、Ticket、Objective は Backend authority を要求する。Standalone から repository-local filesystem backend へ fallback しない。
|
- `workers`、Backend Worker restore、Workspace panel、Ticket、Objective は Backend authority を要求する。Standalone から repository-local filesystem backend へ fallback しない。
|
||||||
- `yoi worker` は Runtime や明示的な process-owned integration が使う direct Worker entrypoint として残るが、通常の `yoi` / TUI 起動経路からは呼び出さない。
|
- `yoi worker` は Runtime や明示的な process-owned integration が使う direct Worker entrypoint として残るが、通常の `yoi` / TUI 起動経路からは呼び出さない。
|
||||||
|
|||||||
Reference in New Issue
Block a user