mirror of
https://github.com/block/buzz.git
synced 2026-11-04 08:21:48 +00:00
fix(desktop): enforce the native Bestie capability boundary
On-behalf-of: mahanti <arjun.mahanti@gmail.com> Signed-off-by: Fizz <fizz@buzz.local> Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
6
Justfile
6
Justfile
@@ -245,6 +245,9 @@ desktop-tauri-test-compiled-flags: _ensure-sidecar-stubs
|
||||
env -u BUZZ_BUILD_AGENT_ACCESS_OWNER_ONLY \
|
||||
BUZZ_TEST_EXPECTED_AGENT_ACCESS_OWNER_ONLY=false \
|
||||
cargo test compiled_policy_matches_expected -- --ignored --nocapture
|
||||
env -u VITE_BUZZ_BESTIE \
|
||||
BUZZ_TEST_EXPECTED_BESTIE=false \
|
||||
cargo test compiled_bestie_flag_matches_expected -- --ignored --nocapture
|
||||
echo "=== Internal build (flags set) → expect true ==="
|
||||
BUZZ_BUILD_AUTO_CONNECT_DEFAULT_RELAY=1 \
|
||||
BUZZ_TEST_EXPECTED_AUTO_CONNECT_DEFAULT_RELAY=true \
|
||||
@@ -255,6 +258,9 @@ desktop-tauri-test-compiled-flags: _ensure-sidecar-stubs
|
||||
BUZZ_BUILD_AGENT_ACCESS_OWNER_ONLY=1 \
|
||||
BUZZ_TEST_EXPECTED_AGENT_ACCESS_OWNER_ONLY=true \
|
||||
cargo test compiled_policy_matches_expected -- --ignored --nocapture
|
||||
VITE_BUZZ_BESTIE=1 \
|
||||
BUZZ_TEST_EXPECTED_BESTIE=true \
|
||||
cargo test compiled_bestie_flag_matches_expected -- --ignored --nocapture
|
||||
echo "Both compiled states verified."
|
||||
|
||||
# Build the full desktop Tauri app locally (unsigned, for testing)
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::managed_agents::{
|
||||
AgentSnapshot, AgentSnapshotDefinition, AgentSnapshotMemory, AgentSnapshotMemoryEntry,
|
||||
AgentSnapshotProfile, FORMAT_DISCRIMINATOR, FORMAT_VERSION,
|
||||
},
|
||||
storage::{filter_agent_definitions_for_build, filter_managed_agents_for_build},
|
||||
BackendKind, ManagedAgentRecord, RespondTo,
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
@@ -180,6 +181,25 @@ fn resolve_unknown_id_returns_error() {
|
||||
assert!(result.unwrap_err().contains("ghost"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn capability_off_shared_snapshot_and_card_lookup_rejects_known_bestie_ids() {
|
||||
let mut instances = vec![
|
||||
make_instance("bestie-pubkey", "builtin:bestie"),
|
||||
make_instance("fizz-pubkey", "builtin:fizz"),
|
||||
];
|
||||
let mut definitions = vec![
|
||||
make_definition("builtin:bestie"),
|
||||
make_definition("builtin:fizz"),
|
||||
];
|
||||
filter_managed_agents_for_build(&mut instances, false);
|
||||
filter_agent_definitions_for_build(&mut definitions, false);
|
||||
|
||||
assert!(resolve_from_lists("bestie-pubkey", &instances, &definitions).is_err());
|
||||
assert!(resolve_from_lists("builtin:bestie", &instances, &definitions).is_err());
|
||||
assert!(resolve_from_lists("fizz-pubkey", &instances, &definitions).is_ok());
|
||||
assert!(resolve_from_lists("builtin:fizz", &instances, &definitions).is_ok());
|
||||
}
|
||||
|
||||
// ── Validator fail-closed cases ───────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -94,7 +94,7 @@ const BUILT_IN_PERSONAS: &[BuiltInPersona] = &[
|
||||
},
|
||||
];
|
||||
|
||||
fn bestie_build_enabled() -> bool {
|
||||
pub(crate) fn bestie_build_enabled() -> bool {
|
||||
option_env!("VITE_BUZZ_BESTIE") == Some("1")
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ fn built_in_order(id: &str) -> Option<usize> {
|
||||
.position(|persona| persona.id == id)
|
||||
}
|
||||
|
||||
fn persona_available_in_build(id: &str, include_bestie: bool) -> bool {
|
||||
pub(crate) fn persona_available_in_build(id: &str, include_bestie: bool) -> bool {
|
||||
include_bestie || id != BESTIE_PERSONA_ID
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::{
|
||||
built_in_persona_records, built_in_persona_records_for_build, definitions_for_save,
|
||||
ensure_persona_ids_are_active, ensure_persona_is_active, merge_personas,
|
||||
bestie_build_enabled, built_in_persona_records, built_in_persona_records_for_build,
|
||||
definitions_for_save, ensure_persona_ids_are_active, ensure_persona_is_active, merge_personas,
|
||||
merge_personas_for_build, migrate_retired_personas, validate_persona_activation_change,
|
||||
validate_persona_deletion, visible_personas_for_build, BUILT_IN_PERSONAS, RETIRED_PERSONAS,
|
||||
};
|
||||
@@ -35,7 +35,7 @@ fn custom_persona(id: &str, display_name: &str) -> AgentDefinition {
|
||||
|
||||
#[test]
|
||||
fn merge_personas_adds_missing_built_ins() {
|
||||
let (records, changed) = merge_personas(Vec::new(), "2026-03-19T00:00:00Z");
|
||||
let (records, changed) = merge_personas_for_build(Vec::new(), "2026-03-19T00:00:00Z", false);
|
||||
|
||||
assert!(changed);
|
||||
assert_eq!(records.len(), BUILT_IN_PERSONAS.len() - 1);
|
||||
@@ -76,6 +76,16 @@ fn bestie_persona_requires_the_internal_build_capability() {
|
||||
assert!(bestie.is_active);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "run explicitly in both compiled feature states"]
|
||||
fn compiled_bestie_flag_matches_expected() {
|
||||
let expected = std::env::var("BUZZ_TEST_EXPECTED_BESTIE")
|
||||
.expect("BUZZ_TEST_EXPECTED_BESTIE must be set")
|
||||
.parse::<bool>()
|
||||
.expect("BUZZ_TEST_EXPECTED_BESTIE must be true or false");
|
||||
assert_eq!(bestie_build_enabled(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ineligible_build_hides_bestie_without_demoting_it() {
|
||||
let now = "2026-03-19T00:00:00Z";
|
||||
|
||||
@@ -271,16 +271,57 @@ pub fn load_managed_agents<R: tauri::Runtime>(
|
||||
app: &AppHandle<R>,
|
||||
) -> Result<Vec<ManagedAgentRecord>, String> {
|
||||
let mut records = load_agent_store(app)?;
|
||||
records.retain(|record| !record.pubkey.is_empty());
|
||||
filter_managed_agents_for_build(
|
||||
&mut records,
|
||||
crate::managed_agents::personas::bestie_build_enabled(),
|
||||
);
|
||||
hydrate_keys(&mut records);
|
||||
Ok(records)
|
||||
}
|
||||
|
||||
pub(crate) fn filter_managed_agents_for_build(
|
||||
records: &mut Vec<ManagedAgentRecord>,
|
||||
include_bestie: bool,
|
||||
) {
|
||||
records.retain(|record| {
|
||||
!record.pubkey.is_empty()
|
||||
&& record.persona_id.as_deref().is_none_or(|persona_id| {
|
||||
crate::managed_agents::personas::persona_available_in_build(
|
||||
persona_id,
|
||||
include_bestie,
|
||||
)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/// Load the key-less agent *definitions* (former personas) from the unified
|
||||
/// store. The persona compatibility shim (`load_personas`) presents these in
|
||||
/// the legacy shape via `to_definition_view`.
|
||||
pub(crate) fn load_agent_definitions<R: tauri::Runtime>(
|
||||
app: &AppHandle<R>,
|
||||
) -> Result<Vec<ManagedAgentRecord>, String> {
|
||||
let mut records = load_agent_definitions_unfiltered(app)?;
|
||||
filter_agent_definitions_for_build(
|
||||
&mut records,
|
||||
crate::managed_agents::personas::bestie_build_enabled(),
|
||||
);
|
||||
Ok(records)
|
||||
}
|
||||
|
||||
pub(crate) fn filter_agent_definitions_for_build(
|
||||
records: &mut Vec<ManagedAgentRecord>,
|
||||
include_bestie: bool,
|
||||
) {
|
||||
records.retain(|record| {
|
||||
record.pubkey.is_empty()
|
||||
&& record.slug.as_deref().is_none_or(|slug| {
|
||||
crate::managed_agents::personas::persona_available_in_build(slug, include_bestie)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn load_agent_definitions_unfiltered<R: tauri::Runtime>(
|
||||
app: &AppHandle<R>,
|
||||
) -> Result<Vec<ManagedAgentRecord>, String> {
|
||||
let mut records = load_agent_store(app)?;
|
||||
records.retain(|record| record.pubkey.is_empty());
|
||||
@@ -376,8 +417,14 @@ pub fn save_managed_agents<R: tauri::Runtime>(
|
||||
app: &AppHandle<R>,
|
||||
records: &[ManagedAgentRecord],
|
||||
) -> Result<(), String> {
|
||||
let definitions = load_agent_definitions(app).unwrap_or_default();
|
||||
let mut sorted = records.to_vec();
|
||||
let existing = load_agent_store(app).unwrap_or_default();
|
||||
let definitions = existing
|
||||
.iter()
|
||||
.filter(|record| record.pubkey.is_empty())
|
||||
.cloned()
|
||||
.collect();
|
||||
let include_bestie = crate::managed_agents::personas::bestie_build_enabled();
|
||||
let mut sorted = instances_for_save(records, &existing, include_bestie);
|
||||
// A caller-supplied key-less record would collide with the definition
|
||||
// half re-read below; instances always carry a pubkey.
|
||||
sorted.retain(|record| !record.pubkey.is_empty());
|
||||
@@ -396,6 +443,48 @@ pub fn save_managed_agents<R: tauri::Runtime>(
|
||||
write_agent_store(app, definitions, sorted)
|
||||
}
|
||||
|
||||
fn instances_for_save(
|
||||
records: &[ManagedAgentRecord],
|
||||
existing: &[ManagedAgentRecord],
|
||||
include_bestie: bool,
|
||||
) -> Vec<ManagedAgentRecord> {
|
||||
let mut complete: Vec<_> = records
|
||||
.iter()
|
||||
.filter(|record| {
|
||||
!record.pubkey.is_empty()
|
||||
&& record.persona_id.as_deref().is_none_or(|persona_id| {
|
||||
crate::managed_agents::personas::persona_available_in_build(
|
||||
persona_id,
|
||||
include_bestie,
|
||||
)
|
||||
})
|
||||
})
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
if !include_bestie {
|
||||
let hidden: Vec<_> = existing
|
||||
.iter()
|
||||
.filter(|record| {
|
||||
!record.pubkey.is_empty()
|
||||
&& record.persona_id.as_deref().is_some_and(|persona_id| {
|
||||
!crate::managed_agents::personas::persona_available_in_build(
|
||||
persona_id,
|
||||
include_bestie,
|
||||
)
|
||||
})
|
||||
&& !complete
|
||||
.iter()
|
||||
.any(|candidate| candidate.pubkey == record.pubkey)
|
||||
})
|
||||
.cloned()
|
||||
.collect();
|
||||
complete.extend(hidden);
|
||||
}
|
||||
|
||||
complete
|
||||
}
|
||||
|
||||
/// Save the key-less agent *definitions*, preserving the keyed instances —
|
||||
/// the definition-side mirror of [`save_managed_agents`].
|
||||
pub(crate) fn save_agent_definitions<R: tauri::Runtime>(
|
||||
|
||||
@@ -12,7 +12,8 @@ use std::path::Path;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
use super::{
|
||||
agent_keyring_name, hydrate_keys_with, migrate_inline_key, persist_agent_keys_with,
|
||||
agent_keyring_name, filter_agent_definitions_for_build, filter_managed_agents_for_build,
|
||||
hydrate_keys_with, instances_for_save, migrate_inline_key, persist_agent_keys_with,
|
||||
KeyMigration, KeyStore, KeyringProbe, ManagedAgentRecord,
|
||||
};
|
||||
|
||||
@@ -143,6 +144,59 @@ fn record_with_pubkey_and_key(pubkey: &str, nsec: &str) -> ManagedAgentRecord {
|
||||
.expect("sample record")
|
||||
}
|
||||
|
||||
fn linked_record(pubkey: &str, persona_id: &str) -> ManagedAgentRecord {
|
||||
let mut record = record_with_pubkey_and_key(pubkey, "");
|
||||
record.persona_id = Some(persona_id.to_string());
|
||||
record
|
||||
}
|
||||
|
||||
fn definition_record(slug: &str) -> ManagedAgentRecord {
|
||||
let mut record = record_with_pubkey_and_key("", "");
|
||||
record.slug = Some(slug.to_string());
|
||||
record
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn capability_off_filters_bestie_instances_and_definitions() {
|
||||
let bestie = linked_record("bestie-pubkey", "builtin:bestie");
|
||||
let fizz = linked_record("fizz-pubkey", "builtin:fizz");
|
||||
let custom = linked_record("custom-pubkey", "custom:helper");
|
||||
let mut instances = vec![bestie.clone(), fizz.clone(), custom.clone()];
|
||||
|
||||
filter_managed_agents_for_build(&mut instances, false);
|
||||
|
||||
assert_eq!(instances, vec![fizz, custom]);
|
||||
|
||||
let bestie_definition = definition_record("builtin:bestie");
|
||||
let fizz_definition = definition_record("builtin:fizz");
|
||||
let mut definitions = vec![bestie_definition, fizz_definition.clone()];
|
||||
filter_agent_definitions_for_build(&mut definitions, false);
|
||||
assert_eq!(definitions, vec![fizz_definition]);
|
||||
|
||||
let mut eligible_instances = vec![bestie];
|
||||
filter_managed_agents_for_build(&mut eligible_instances, true);
|
||||
assert_eq!(eligible_instances.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn capability_off_save_preserves_hidden_bestie_instance_unchanged() {
|
||||
let original_bestie = linked_record("bestie-pubkey", "builtin:bestie");
|
||||
let fizz = linked_record("fizz-pubkey", "builtin:fizz");
|
||||
let mut forged_bestie = original_bestie.clone();
|
||||
forged_bestie.name = "forged edit".to_string();
|
||||
|
||||
let saved = instances_for_save(
|
||||
&[fizz.clone(), forged_bestie],
|
||||
&[fizz.clone(), original_bestie.clone()],
|
||||
false,
|
||||
);
|
||||
|
||||
assert_eq!(saved.len(), 2);
|
||||
assert!(saved.contains(&fizz));
|
||||
assert!(saved.contains(&original_bestie));
|
||||
assert!(!saved.iter().any(|record| record.name == "forged edit"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn migrate_persists_and_signals_stripping_when_keyring_reachable() {
|
||||
// Item 2: an inline key (residue from a prior keyring-unreachable save)
|
||||
|
||||
Reference in New Issue
Block a user