- 5515
let servers = mcp["servers"].as_table().unwrap(); - 5516
assert!( - 5517
!servers.contains_key("gone"), - 5518
"a server left out is removed" - 5519
); - 5520
let search = servers["search"].as_table().unwrap(); - 5521
assert_eq!(search["command"].as_str(), Some("new")); - 5522
assert_eq!(search["args"].as_array().unwrap().len(), 1); - 5523
assert!(!search.contains_key("env"), "an emptied env is cleared"); - 5524
assert!(!search.contains_key("network"), "network off is cleared"); - 5525
assert_eq!(search["serves"].as_array().unwrap().len(), 1); - 5526
assert_eq!(search["future_server_key"].as_integer(), Some(3)); - 5527
- 5528
persist_mcp_server(&path, "other", Some(&server("other"))).unwrap(); - 5529
persist_mcp_server(&path, "search", Some(&server("newer"))).unwrap(); - 5530
let document = read(); - 5531
let servers = document["mcp"]["servers"].as_table().unwrap(); - 5532
assert_eq!(servers["other"]["command"].as_str(), Some("other")); - 5533
assert_eq!(servers["search"]["command"].as_str(), Some("newer")); - 5534
assert_eq!(servers["search"]["future_server_key"].as_integer(), Some(3)); - 5535
- 5536
persist_mcp_server(&path, "other", None).unwrap(); - 5537
let document = read(); - 5538
let servers = document["mcp"]["servers"].as_table().unwrap(); - 5539
assert!(!servers.contains_key("other")); - 5540
assert!(servers.contains_key("search")); - 5541
assert_eq!(document["mcp"]["future_mcp_key"].as_bool(), Some(true)); - 5542
} - 5543
- 5544
/// Saving a value the file already holds is not a change, so the file, - 5545
/// comments included, is left exactly as the operator wrote it. - 5546
#[test] - 5547
fn saving_an_unchanged_setting_leaves_the_file_untouched() { - 5548
let dir = tempfile::tempdir().unwrap(); - 5549
let text = "# chosen by hand\nmodel = \"kept\" # keep this\n"; - 5550
write_project_config(dir.path(), text); - 5551
persist_project_preferences(dir.path(), None, Some("kept"), None, None, None, None) - 5552
.unwrap(); - 5553
assert_eq!( - 5554
std::fs::read_to_string(project_path(dir.path())).unwrap(), - 5555
text - 5556
); - 5557
persist_project_preferences(dir.path(), None, Some("changed"), None, None, None, None) - 5558
.unwrap(); - 5559
let cfg = load_with_trust(dir.path(), true).unwrap(); - 5560
assert_eq!(cfg.model, "changed"); - 5561
} - 5562
} - 5563
- 5564
#[cfg(test)] - 5565
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] - 5566
mod channel_autonomy_tests { - 5567
use super::*; - 5568
- 5569
fn policy(ceiling: Option<&str>) -> ChannelPolicy { - 5570
ChannelPolicy { - 5571
autonomy_ceiling: ceiling.map(str::to_string), - 5572
..ChannelPolicy::default() - 5573
} - 5574
} - 5575
- 5576
/// Restrictive only, like every other key on this type. A chat may say - 5577
/// "propose only, in here"; it may never say "act freely" on a workspace - 5578
/// whose operator did not. - 5579
#[test] - 5580
fn a_channel_can_only_lower_autonomy_never_raise_it() { - 5581
let composed = ChannelPolicy::merge(&policy(Some("delegated")), &policy(Some("manual"))); - 5582
assert_eq!(composed.autonomy_ceiling.as_deref(), Some("manual")); - 5583
- 5584
// The narrower tier asking for MORE does not get it. - 5585
let composed = ChannelPolicy::merge(&policy(Some("manual")), &policy(Some("autonomous"))); - 5586
assert_eq!(composed.autonomy_ceiling.as_deref(), Some("manual")); - 5587
} - 5588
- 5589
#[test] - 5590
fn silence_on_one_tier_inherits_rather_than_permitting_everything() { - 5591
assert_eq!( - 5592
ChannelPolicy::merge(&policy(Some("assisted")), &policy(None)) - 5593
.autonomy_ceiling - 5594
.as_deref(), - 5595
Some("assisted") - 5596
); - 5597
assert_eq!( - 5598
ChannelPolicy::merge(&policy(None), &policy(Some("manual"))) - 5599
.autonomy_ceiling - 5600
.as_deref(), - 5601
Some("manual") - 5602
); - 5603
assert!( - 5604
ChannelPolicy::merge(&policy(None), &policy(None)) - 5605
.autonomy_ceiling - 5606
.is_none() - 5607
); - 5608
} - 5609
- 5610
/// An unparseable ceiling must not read as maximum delegation. - 5611
#[test] - 5612
fn an_unknown_ceiling_is_treated_as_assisted_not_autonomous() { - 5613
let composed = ChannelPolicy::merge(&policy(Some("banana")), &policy(Some("autonomous"))); - 5614
assert_eq!(composed.autonomy_ceiling.as_deref(), Some("banana")); - 5615
assert!(autonomy_rank("banana") < autonomy_rank("autonomous")); - 5616
} - 5617
- 5618
#[test] - 5619
fn plugin_resolved_allow_deny_network_matrix() { - 5620
let mut resolved = PluginResolved::default(); - 5621
assert!(resolved.is_enabled("test-plugin")); - 5622
assert!(!resolved.is_network_allowed("test-plugin")); - 5623
- 5624
// Enable network - 5625
resolved.network_allow = Some(vec!["test-plugin".into()]); - 5626
assert!(resolved.is_network_allowed("test-plugin")); - 5627
- 5628
// Global or channel deny takes priority - 5629
resolved.network_deny.push("test-plugin".into()); - 5630
assert!(!resolved.is_network_allowed("test-plugin")); - 5631
assert!(resolved.is_enabled("test-plugin")); - 5632
- 5633
// Disabling or denying plugin shuts it off completely - 5634
resolved.disabled.push("test-plugin".into()); - 5635
assert!(!resolved.is_enabled("test-plugin")); - 5636
assert!(!resolved.is_network_allowed("test-plugin")); - 5637
} - 5638
- 5639
#[test] - 5640
fn channel_policy_merges_plugin_network_and_allow_deny() { - 5641
let bot = ChannelPolicy { - 5642
plugins_allow: Some(vec!["test-plugin-a".into(), "test-plugin-b".into()]), - 5643
plugins_deny: vec!["untrusted-plugin".into()], - 5644
plugins_network_deny: vec!["test-plugin-b".into()], - 5645
..ChannelPolicy::default() - 5646
}; - 5647
let chat = ChannelPolicy { - 5648
plugins_allow: Some(vec!["test-plugin-a".into()]), - 5649
plugins_deny: vec!["banned-plugin".into()], - 5650
plugins_network_deny: vec!["test-plugin-a".into()], - 5651
..ChannelPolicy::default() - 5652
}; - 5653
let merged = ChannelPolicy::merge(&bot, &chat); - 5654
assert_eq!(merged.plugins_allow, Some(vec!["test-plugin-a".into()])); - 5655
assert!( - 5656
merged - 5657
.plugins_deny - 5658
.contains(&"untrusted-plugin".to_string()) - 5659
); - 5660
assert!(merged.plugins_deny.contains(&"banned-plugin".to_string())); - 5661
assert!( - 5662
merged - 5663
.plugins_network_deny - 5664
.contains(&"test-plugin-b".to_string()) - 5665
); - 5666
assert!( - 5667
merged - 5668
.plugins_network_deny - 5669
.contains(&"test-plugin-a".to_string()) - 5670
); - 5671
} - 5672
- 5673
/// The persisted TOML field was renamed from `subagents` to `workers`. - 5674
/// An existing `vak.toml` with the old key (and no `workers` key) must - 5675
/// still be honored rather than silently falling back to the default. - 5676
#[test] - 5677
fn legacy_subagents_key_is_honored_as_workers() { - 5678
let dir = tempfile::tempdir().unwrap(); - 5679
let path = project_path(dir.path()); - 5680
std::fs::create_dir_all(path.parent().unwrap()).unwrap(); - 5681
std::fs::write(&path, "subagents = false\n").unwrap(); - 5682
- 5683
let (fc, warnings) = parse_file(&path).unwrap(); - 5684
assert_eq!(fc.workers, Some(false)); - 5685
assert!( - 5686
warnings.is_empty(), - 5687
"legacy `subagents` key should not warn as unknown: {warnings:?}" - 5688
); - 5689
- 5690
let cfg = load_with_trust(dir.path(), true).unwrap(); - 5691
assert!(!cfg.workers); - 5692
} - 5693
} - 5694
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.