Skip to content
Open
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
16 changes: 14 additions & 2 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ desktop-tauri-check: _ensure-sidecar-stubs
desktop-tauri-test: _ensure-sidecar-stubs
cd desktop/src-tauri && cargo test

# Verify compiled-flag behavior under both compile states (clean + internal).
# Verify compiled-flag behavior under both compile states (clean + capability set).
# Runs the observer_archive focused test twice with independently supplied
# expected values; build.rs rerun-if-env-changed triggers recompilation.
desktop-tauri-test-compiled-flags: _ensure-sidecar-stubs
Expand All @@ -221,13 +221,25 @@ desktop-tauri-test-compiled-flags: _ensure-sidecar-stubs
env -u BUZZ_BUILD_AUTO_CONNECT_DEFAULT_RELAY \
BUZZ_TEST_EXPECTED_AUTO_CONNECT_DEFAULT_RELAY=false \
cargo test compiled_flag_matches_expected -- --ignored --nocapture
echo "=== Internal build (flags set) → expect true ==="
env -u BUZZ_BUILD_AGENT_ACCESS_OWNER_ONLY \
BUZZ_TEST_EXPECTED_AGENT_ACCESS_OWNER_ONLY=false \
cargo test --lib
env -u BUZZ_BUILD_AGENT_ACCESS_OWNER_ONLY \
BUZZ_TEST_EXPECTED_AGENT_ACCESS_OWNER_ONLY=false \
cargo test compiled_policy_matches_expected -- --ignored --nocapture
echo "=== Owner-only access capability set → expect true ==="
BUZZ_BUILD_OBSERVER_ARCHIVE_DEFAULT=1 \
BUZZ_TEST_EXPECTED_OBSERVER_ARCHIVE_DEFAULT=true \
cargo test observer_archive_default_enabled_matches_expected -- --ignored --nocapture
BUZZ_BUILD_AUTO_CONNECT_DEFAULT_RELAY=1 \
BUZZ_TEST_EXPECTED_AUTO_CONNECT_DEFAULT_RELAY=true \
cargo test compiled_flag_matches_expected -- --ignored --nocapture
BUZZ_BUILD_AGENT_ACCESS_OWNER_ONLY=1 \
BUZZ_TEST_EXPECTED_AGENT_ACCESS_OWNER_ONLY=true \
cargo test --lib
BUZZ_BUILD_AGENT_ACCESS_OWNER_ONLY=1 \
BUZZ_TEST_EXPECTED_AGENT_ACCESS_OWNER_ONLY=true \
cargo test compiled_policy_matches_expected -- --ignored --nocapture
echo "Both compiled states verified."

# Build the full desktop Tauri app locally (unsigned, for testing)
Expand Down
7 changes: 7 additions & 0 deletions desktop/src-tauri/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,16 @@ fn main() {
println!("cargo:rerun-if-env-changed=BUZZ_BUILD_RELAY_RECONNECT_CMD");
println!("cargo:rerun-if-env-changed=BUZZ_BUILD_OBSERVER_ARCHIVE_DEFAULT");
println!("cargo:rerun-if-env-changed=BUZZ_BUILD_AGENT_METRIC_ARCHIVE_DEFAULT");
println!("cargo:rerun-if-env-changed=BUZZ_BUILD_AGENT_ACCESS_OWNER_ONLY");
println!("cargo:rerun-if-env-changed=BUZZ_BUILD_AUTO_CONNECT_DEFAULT_RELAY");
println!("cargo:rustc-check-cfg=cfg(buzz_updater_enabled)");

// Explicit owner-only agent-access capability. Release packaging sets this
// presence-only marker; OSS/custom builds leave agent access configurable.
if std::env::var("BUZZ_BUILD_AGENT_ACCESS_OWNER_ONLY").is_ok() {
println!("cargo:rustc-env=BUZZ_DESKTOP_BUILD_AGENT_ACCESS_OWNER_ONLY=1");
}

if let Ok(relay_url) = std::env::var("BUZZ_RELAY_URL") {
println!("cargo:rustc-env=BUZZ_DESKTOP_BUILD_RELAY_URL={relay_url}");
}
Expand Down
18 changes: 18 additions & 0 deletions desktop/src-tauri/src/commands/agent_access.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// Return whether this build enforces owner-only managed-agent access.
#[tauri::command]
pub fn agent_access_owner_only() -> bool {
crate::managed_agents::owner_only_access_build()
}

#[cfg(test)]
mod tests {
#[test]
#[ignore = "requires BUZZ_TEST_EXPECTED_AGENT_ACCESS_OWNER_ONLY"]
fn compiled_policy_matches_expected() {
let expected = std::env::var("BUZZ_TEST_EXPECTED_AGENT_ACCESS_OWNER_ONLY")
.expect("BUZZ_TEST_EXPECTED_AGENT_ACCESS_OWNER_ONLY must be set")
.parse::<bool>()
.expect("BUZZ_TEST_EXPECTED_AGENT_ACCESS_OWNER_ONLY must be true or false");
assert_eq!(super::agent_access_owner_only(), expected);
}
}
2 changes: 1 addition & 1 deletion desktop/src-tauri/src/commands/agents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1360,7 +1360,7 @@ pub async fn delete_managed_agent(
mod deploy;
use deploy::build_deploy_payload;
#[cfg(test)]
use deploy::deploy_payload_json;
use deploy::{deploy_payload_json, deploy_payload_json_for_current_build};
#[cfg(test)]
use deploy::{ensure_remote_provider_supported, resolve_deploy_model_provider};

Expand Down
32 changes: 29 additions & 3 deletions desktop/src-tauri/src/commands/agents_deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ pub(super) fn build_deploy_payload(
&owner_pubkey,
);

Ok(deploy_payload_json(
Ok(deploy_payload_json_for_current_build(
record,
crate::relay::effective_agent_relay_url(
&record.relay_url,
Expand All @@ -152,8 +152,31 @@ pub(super) fn build_deploy_payload(
))
}

/// Serialize a deploy payload using this build's managed-agent access policy.
pub(super) fn deploy_payload_json_for_current_build(
record: &ManagedAgentRecord,
relay_url: String,
effective_model: Option<String>,
effective_provider: Option<String>,
effective_prompt: Option<String>,
merged_env: BTreeMap<String, String>,
launch: serde_json::Value,
) -> serde_json::Value {
deploy_payload_json(
record,
relay_url,
effective_model,
effective_provider,
effective_prompt,
merged_env,
launch,
crate::managed_agents::owner_only_access_build(),
)
}

/// Pure serialization half of [`build_deploy_payload`]. Legacy top-level fields
/// remain for display/bookkeeping; providers execute the resolved `launch` block.
#[allow(clippy::too_many_arguments)]
pub(super) fn deploy_payload_json(
record: &ManagedAgentRecord,
relay_url: String,
Expand All @@ -162,7 +185,10 @@ pub(super) fn deploy_payload_json(
effective_prompt: Option<String>,
merged_env: BTreeMap<String, String>,
launch: serde_json::Value,
owner_only_access: bool,
) -> serde_json::Value {
let (respond_to, respond_to_allowlist) =
crate::managed_agents::projected_access_with_policy(record, owner_only_access);
serde_json::json!({
"name": &record.name,
"relay_url": relay_url,
Expand All @@ -177,8 +203,8 @@ pub(super) fn deploy_payload_json(
"idle_timeout_seconds": record.idle_timeout_seconds,
"max_turn_duration_seconds": record.max_turn_duration_seconds,
"parallelism": record.parallelism,
"respond_to": record.respond_to,
"respond_to_allowlist": &record.respond_to_allowlist,
"respond_to": respond_to,
"respond_to_allowlist": respond_to_allowlist,
"env_vars": merged_env,
"launch": launch,
})
Expand Down
108 changes: 108 additions & 0 deletions desktop/src-tauri/src/commands/agents_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,24 @@ fn legacy_avatar_empty_when_nothing_resolves() {

// ── Provider deploy payload completeness ─────────────────────────────────────

fn deploy_payload_for_policy(
record: &ManagedAgentRecord,
owner_only_access: bool,
) -> serde_json::Value {
deploy_payload_json(
record,
"wss://relay.example".to_string(),
Some("gpt-x".to_string()),
Some("openai".to_string()),
None,
std::collections::BTreeMap::new(),
// Access projection is the subject here; the launch block is exercised
// by the shared provider fixture test below.
serde_json::Value::Null,
owner_only_access,
)
}

/// The shared provider fixture is the contract arbiter: it must be the exact
/// richest deploy request produced by the real desktop serializers.
#[test]
Expand Down Expand Up @@ -470,6 +488,9 @@ fn deploy_payload_matches_the_shared_full_launch_fixture() {
None,
std::collections::BTreeMap::from([("USER_KEY".into(), "user-value".into())]),
launch,
// Fixture asserts the record's own access fields survive, so the
// owner-only projection must be off for this comparison.
false,
);

assert_eq!(
Expand Down Expand Up @@ -501,3 +522,90 @@ fn tauri_platform_configs_bundle_kubernetes_only_on_supported_hosts() {
);
}
}

#[test]
fn current_build_deploy_payload_forwards_compiled_policy() {
use crate::managed_agents::{BackendKind, RespondTo};

let expected_owner_only = match std::env::var("BUZZ_TEST_EXPECTED_AGENT_ACCESS_OWNER_ONLY") {
Ok(value) => value
.parse::<bool>()
.expect("BUZZ_TEST_EXPECTED_AGENT_ACCESS_OWNER_ONLY must be true or false"),
Err(std::env::VarError::NotPresent)
if !crate::managed_agents::owner_only_access_build() =>
{
false
}
Err(std::env::VarError::NotPresent) => {
panic!(
"BUZZ_TEST_EXPECTED_AGENT_ACCESS_OWNER_ONLY must be set for owner-only-access-build tests"
)
}
Err(std::env::VarError::NotUnicode(_)) => {
panic!("BUZZ_TEST_EXPECTED_AGENT_ACCESS_OWNER_ONLY must be valid UTF-8")
}
};
let mut record = bare_agent_record(None, None, None);
record.backend = BackendKind::Provider {
id: "provider".to_string(),
config: serde_json::json!({}),
};
record.respond_to = RespondTo::Anyone;
record.respond_to_allowlist = vec!["a".repeat(64)];

let payload = deploy_payload_json_for_current_build(
&record,
"wss://relay.example".to_string(),
None,
None,
None,
std::collections::BTreeMap::new(),
// The compiled access policy is the subject here; the launch block is
// exercised by the shared provider fixture test above.
serde_json::Value::Null,
);
let expected_mode = if expected_owner_only {
"owner-only"
} else {
"anyone"
};

assert_eq!(
payload["respond_to"], expected_mode,
"current-build deploy payload did not forward the compiled policy",
);
let expected_allowlist = if expected_owner_only {
serde_json::json!([])
} else {
serde_json::json!(["a".repeat(64)])
};
assert_eq!(
payload["respond_to_allowlist"], expected_allowlist,
"current-build deploy payload did not apply the compiled policy to the stale allowlist",
);
}

#[test]
fn owner_only_access_deploy_payload_clamps_stale_access() {
use crate::managed_agents::{BackendKind, RespondTo};

let mut record = bare_agent_record(None, None, None);
record.backend = BackendKind::Provider {
id: "provider".to_string(),
config: serde_json::json!({}),
};
record.respond_to = RespondTo::Anyone;
record.respond_to_allowlist = vec!["a".repeat(64)];

let payload = deploy_payload_for_policy(&record, true);

assert_eq!(
payload["respond_to"], "owner-only",
"owner-only-access deploy payload widened stale access"
);
assert_eq!(
payload["respond_to_allowlist"],
serde_json::json!([]),
"owner-only-access deploy payload retained a stale allowlist"
);
}
2 changes: 2 additions & 0 deletions desktop/src-tauri/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod agent_access;
mod agent_auth;
mod agent_config;
mod agent_discovery;
Expand Down Expand Up @@ -61,6 +62,7 @@ mod window_vibrancy;
mod workflows;
mod workspace;

pub use agent_access::*;
pub use agent_auth::*;
pub use agent_config::*;
pub use agent_discovery::*;
Expand Down
2 changes: 1 addition & 1 deletion desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ async fn clear_initial_window_backing<R: tauri::Runtime>(window: &tauri::Window<
async fn wait_for_stable_initial_window_geometry<R: tauri::Runtime>(window: &tauri::Window<R>) {
const MAX_POLLS: usize = 120;
const REQUIRED_STABLE_POLLS: usize = 4;

let mut previous_bounds = None;
let mut stable_polls = 0;

Expand Down Expand Up @@ -814,6 +813,7 @@ pub fn run() {
get_managed_agent_log,
get_agent_models,
discover_agent_models,
agent_access_owner_only,
get_agent_config_surface,
get_runtime_file_config,
get_baked_build_env_keys,
Expand Down
Loading
Loading