Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions docs/architecture/detached-task-dispatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,11 @@ checksum sidecar, its publisher signature when present, and the mandatory
archive signature, pins the SHA-256 passed to the installer, waits with a
bounded deadline, and probes the installed binary again before continuing.

A source build is different: it uploads the controller's repository and
compiles it on the target. It therefore remains a separate user-confirmed
operation and uses only the clean, confirmed controller revision. Automatic
prebuilt installation never escalates to a source build.
The signed prebuilt release is the only install path. The controller never
compiles BitFun on a target, and exposes no command to do so: when no published
binary can run there — an unsupported platform, a libc floor, a missing `tar`,
an unreachable release, or a release that predates a required capability — the
probe reports why and the target cannot be selected.

Account-device transport wraps target verbs in names reserved for detached
dispatch, such as `dispatch_target_submit`. They are handled before the
Expand Down
2 changes: 0 additions & 2 deletions src/apps/cli/src/peer_host/deny.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ static LOCAL_ONLY_COMMANDS: &[&str] = &[
"dispatch_list_targets",
"dispatch_probe_target",
"dispatch_install_cli_start",
"dispatch_install_cli_source_start",
"dispatch_install_cli_poll",
"dispatch_install_cli_cancel",
"dispatch_sync_model_config",
Expand Down Expand Up @@ -137,7 +136,6 @@ mod tests {
"dispatch_list_targets",
"dispatch_probe_target",
"dispatch_install_cli_start",
"dispatch_install_cli_source_start",
"dispatch_install_cli_poll",
"dispatch_install_cli_cancel",
"dispatch_sync_model_config",
Expand Down
118 changes: 4 additions & 114 deletions src/apps/desktop/src/api/dispatch_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
//! thin host adapters around the platform-neutral dispatch controller and its
//! observer-only outbound index.

use std::{
path::{Path, PathBuf},
sync::Arc,
};
use std::sync::Arc;

use async_trait::async_trait;
use bitfun_core::infrastructure::PathManager;
Expand All @@ -18,7 +15,7 @@ use bitfun_core::service::dispatch::{
get_dispatch_status, list_device_dispatch_jobs, list_dispatch_jobs, list_dispatch_targets,
poll_dispatch_cli_install, probe_device_dispatch_target, probe_dispatch_target,
query_device_dispatch_job, query_dispatch_job, start_dispatch_cli_install,
start_dispatch_cli_source_build, submit_device_dispatch, submit_dispatch,
submit_device_dispatch, submit_dispatch,
sync_device_dispatch_result, sync_dispatch_model_config, sync_dispatch_result,
DeviceDispatchRpc, DispatchAnswerRequest, DispatchAppendRequest, DispatchConnectionRequest,
DispatchContinueRequest, DispatchInstallPollRequest, DispatchInstallStartRequest,
Expand All @@ -30,85 +27,13 @@ use bitfun_core::service::dispatch::{
use bitfun_core::service::remote_ssh::dispatch_ssh::{
DispatchInstallPoll, DispatchInstallStart, DispatchSshProbe,
};
use bitfun_services_integrations::remote_ssh::dispatch_ssh::install_cli_source_archive_start;
use serde_json::Value;
use tauri::State;

use super::app_state::AppState;

struct AccountDeviceDispatchRpc;

const MAX_CONTROLLER_SOURCE_ARCHIVE_BYTES: usize = 512 * 1024 * 1024;

#[cfg(debug_assertions)]
fn controller_source_root() -> Option<PathBuf> {
let root = Path::new(env!("CARGO_MANIFEST_DIR"))
.ancestors()
.nth(3)?
.to_path_buf();
(root.join("Cargo.toml").is_file() && root.join(".git").exists()).then_some(root)
}

#[cfg(not(debug_assertions))]
fn controller_source_root() -> Option<PathBuf> {
None
}

async fn archive_controller_source(root: PathBuf) -> anyhow::Result<(Vec<u8>, String)> {
tokio::task::spawn_blocking(move || {
let status = std::process::Command::new("git")
.args(["status", "--porcelain"])
.current_dir(&root)
.output()
.map_err(|error| anyhow::anyhow!("inspect controller source checkout: {error}"))?;
if !status.status.success() {
anyhow::bail!(
"inspect controller source checkout: {}",
String::from_utf8_lossy(&status.stderr).trim()
);
}
if !status.stdout.is_empty() {
anyhow::bail!(
"the controller source checkout has uncommitted changes; commit them and restart Desktop before updating the target CLI"
);
}

let revision = std::process::Command::new("git")
.args(["rev-parse", "HEAD"])
.current_dir(&root)
.output()
.map_err(|error| anyhow::anyhow!("resolve controller source revision: {error}"))?;
if !revision.status.success() {
anyhow::bail!(
"resolve controller source revision: {}",
String::from_utf8_lossy(&revision.stderr).trim()
);
}
let revision = String::from_utf8(revision.stdout)
.map_err(|error| anyhow::anyhow!("controller source revision is not UTF-8: {error}"))?
.trim()
.to_string();

let archive = std::process::Command::new("git")
.args(["archive", "--format=tar.gz", "HEAD"])
.current_dir(&root)
.output()
.map_err(|error| anyhow::anyhow!("archive controller source: {error}"))?;
if !archive.status.success() {
anyhow::bail!(
"archive controller source: {}",
String::from_utf8_lossy(&archive.stderr).trim()
);
}
if archive.stdout.is_empty() || archive.stdout.len() > MAX_CONTROLLER_SOURCE_ARCHIVE_BYTES {
anyhow::bail!("controller source archive is empty or exceeds the 512 MB limit");
}
Ok((archive.stdout, revision))
})
.await
.map_err(|error| anyhow::anyhow!("controller source archive task failed: {error}"))?
}

#[async_trait]
impl DeviceDispatchRpc for AccountDeviceDispatchRpc {
async fn invoke(&self, device_id: &str, command: &str, args: Value) -> anyhow::Result<Value> {
Expand Down Expand Up @@ -203,15 +128,9 @@ pub async fn dispatch_probe_target(
.get_ssh_manager_async()
.await
.map_err(|error| error.to_string())?;
let mut probe = probe_dispatch_target(&manager, request)
probe_dispatch_target(&manager, request)
.await
.map_err(|error| error.to_string())?;
if controller_source_root().is_some() {
if let Some(source_build) = probe.source_build.as_mut() {
source_build.git_ref = "current-controller-checkout".to_string();
}
}
Ok(probe)
.map_err(|error| error.to_string())
}

#[tauri::command]
Expand All @@ -228,35 +147,6 @@ pub async fn dispatch_install_cli_start(
.map_err(|error| error.to_string())
}

/// Build the CLI from source on the target. Offered when no published binary
/// can run there.
#[tauri::command]
pub async fn dispatch_install_cli_source_start(
state: State<'_, AppState>,
request: DispatchConnectionRequest,
) -> Result<DispatchInstallStart, String> {
let manager = state
.get_ssh_manager_async()
.await
.map_err(|error| error.to_string())?;
if let Some(root) = controller_source_root() {
let (archive, revision) = archive_controller_source(root)
.await
.map_err(|error| error.to_string())?;
return install_cli_source_archive_start(
&manager,
request.connection_id.trim(),
&archive,
&revision,
)
.await
.map_err(|error| error.to_string());
}
start_dispatch_cli_source_build(&manager, request)
.await
.map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn dispatch_install_cli_poll(
state: State<'_, AppState>,
Expand Down
1 change: 0 additions & 1 deletion src/apps/desktop/src/api/peer_host_invoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ static LOCAL_ONLY_COMMANDS: &[&str] = &[
"dispatch_list_targets",
"dispatch_probe_target",
"dispatch_install_cli_start",
"dispatch_install_cli_source_start",
"dispatch_install_cli_poll",
"dispatch_install_cli_cancel",
"dispatch_sync_model_config",
Expand Down
4 changes: 0 additions & 4 deletions src/apps/desktop/src/api/remote_workspace_policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,10 +349,6 @@ pub const REMOTE_WORKSPACE_COMMAND_POLICIES: &[(&str, RemoteWorkspacePolicy)] =
"dispatch_install_cli_poll",
RemoteWorkspacePolicy::WorkspaceAgnostic,
),
(
"dispatch_install_cli_source_start",
RemoteWorkspacePolicy::WorkspaceAgnostic,
),
(
"dispatch_install_cli_start",
RemoteWorkspacePolicy::WorkspaceAgnostic,
Expand Down
1 change: 0 additions & 1 deletion src/apps/desktop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1749,7 +1749,6 @@ pub async fn run() {
api::dispatch_api::dispatch_list_targets,
api::dispatch_api::dispatch_probe_target,
api::dispatch_api::dispatch_install_cli_start,
api::dispatch_api::dispatch_install_cli_source_start,
api::dispatch_api::dispatch_install_cli_poll,
api::dispatch_api::dispatch_install_cli_cancel,
api::dispatch_api::dispatch_sync_model_config,
Expand Down
16 changes: 3 additions & 13 deletions src/apps/server/src/routes/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use bitfun_core::external_sources::{
use bitfun_core::service::dispatch::{
answer_dispatch, append_dispatch, cancel_dispatch, cancel_dispatch_cli_install,
get_dispatch_status, list_dispatch_jobs, list_dispatch_targets, poll_dispatch_cli_install,
probe_dispatch_target, start_dispatch_cli_install, start_dispatch_cli_source_build,
submit_dispatch, sync_dispatch_model_config, sync_dispatch_result, DispatchAnswerRequest,
probe_dispatch_target, start_dispatch_cli_install, submit_dispatch,
sync_dispatch_model_config, sync_dispatch_result, DispatchAnswerRequest,
DispatchAppendRequest, DispatchConnectionRequest, DispatchInstallPollRequest,
DispatchInstallStartRequest, DispatchJobRequest, DispatchListJobsRequest,
DispatchListTargetsRequest, DispatchProbeTargetRequest, DispatchStatusRequest,
Expand All @@ -27,7 +27,6 @@ pub(crate) fn supports(method: &str) -> bool {
"dispatch_list_targets"
| "dispatch_probe_target"
| "dispatch_install_cli_start"
| "dispatch_install_cli_source_start"
| "dispatch_install_cli_poll"
| "dispatch_install_cli_cancel"
| "dispatch_sync_model_config"
Expand Down Expand Up @@ -79,14 +78,6 @@ pub(crate) async fn dispatch(
.map_err(operation_error)?,
)
}
"dispatch_install_cli_source_start" => {
let request = parse_request::<DispatchConnectionRequest>(&params)?;
encode(
start_dispatch_cli_source_build(&host.ssh_manager, request)
.await
.map_err(operation_error)?,
)
}
"dispatch_install_cli_poll" => {
let request = parse_request::<DispatchInstallPollRequest>(&params)?;
encode(
Expand Down Expand Up @@ -202,7 +193,6 @@ mod tests {
"dispatch_list_targets",
"dispatch_probe_target",
"dispatch_install_cli_start",
"dispatch_install_cli_source_start",
"dispatch_install_cli_poll",
"dispatch_install_cli_cancel",
"dispatch_sync_model_config",
Expand All @@ -227,7 +217,7 @@ mod tests {
}

#[test]
fn source_build_route_accepts_a_structured_connection_request() {
fn connection_scoped_routes_accept_a_structured_connection_request() {
let request = parse_request::<DispatchConnectionRequest>(&serde_json::json!({
"request": { "connectionId": "ssh-target" }
}))
Expand Down
9 changes: 0 additions & 9 deletions src/crates/assembly/core/src/service/dispatch/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,14 +274,6 @@ pub async fn install_cli_start(
dispatch_ssh::install_cli_start(manager, request.connection_id.trim(), &request.release).await
}

/// Build and install the CLI from source, for targets no published binary fits.
pub async fn install_cli_source_start(
manager: &SSHConnectionManager,
request: DispatchConnectionRequest,
) -> anyhow::Result<DispatchInstallStart> {
dispatch_ssh::install_cli_source_start(manager, request.connection_id.trim()).await
}

pub async fn install_cli_poll(
manager: &SSHConnectionManager,
request: DispatchInstallPollRequest,
Expand Down Expand Up @@ -1500,7 +1492,6 @@ mod tests {
release: None,
protocol: Some(json!({ "cliVersion": "1.2.3" })),
prebuilt_incompatible: None,
source_build: None,
};

recover_interrupted_cli_install_audit(&store, "job-install-recovery", &probe)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,9 @@ pub async fn probe_device(
protocol_error,
release: None,
protocol: Some(protocol),
// An account device runs its own already-installed CLI; this controller
// neither installs nor builds anything for it.
// An account device runs its own already-installed CLI; this
// controller installs nothing for it.
prebuilt_incompatible: None,
source_build: None,
})
}

Expand Down
1 change: 0 additions & 1 deletion src/crates/assembly/core/src/service/dispatch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ pub use controller::{
answer as answer_dispatch, append as append_dispatch, cancel as cancel_dispatch,
continue_job as continue_dispatch_job, install_cli_cancel as cancel_dispatch_cli_install,
install_cli_poll as poll_dispatch_cli_install,
install_cli_source_start as start_dispatch_cli_source_build,
install_cli_start as start_dispatch_cli_install, list_jobs as list_dispatch_jobs,
list_targets as list_dispatch_targets, probe_target as probe_dispatch_target,
query_job as query_dispatch_job, status as get_dispatch_status, submit as submit_dispatch,
Expand Down
Loading
Loading