- 2743
store.set_source_enabled(&source.id, false), - 2744
Err(PluginError::UnsafePackage(_)) - 2745
)); - 2746
store - 2747
.record_invocation(&source.trace_id, "tool", "mcp:plugin.tool.lookup", true) - 2748
.unwrap(); - 2749
assert_eq!(store.invocations().unwrap().len(), 1); - 2750
} - 2751
- 2752
#[test] - 2753
fn signed_catalog_source_verifies_and_can_be_enabled() { - 2754
use ring::rand::SystemRandom; - 2755
use ring::signature::{Ed25519KeyPair, KeyPair}; - 2756
- 2757
let catalog = tempfile::tempdir().unwrap(); - 2758
write( - 2759
&catalog.path().join("marketplace.json"), - 2760
r#"{"name":"signed-team","plugins":[{"name":"tool","source":"./tool"}]}"#, - 2761
); - 2762
let inspection = inspect_catalog(catalog.path()).unwrap(); - 2763
let rng = SystemRandom::new(); - 2764
let pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng).unwrap(); - 2765
let key_pair = Ed25519KeyPair::from_pkcs8(pkcs8.as_ref()).unwrap(); - 2766
let public_key = - 2767
base64::engine::general_purpose::STANDARD.encode(key_pair.public_key().as_ref()); - 2768
let signature = base64::engine::general_purpose::STANDARD - 2769
.encode(key_pair.sign(inspection.digest.as_bytes()).as_ref()); - 2770
let home = tempfile::tempdir().unwrap(); - 2771
let store = PluginStore::new(home.path()); - 2772
let source = store - 2773
.register_catalog_source_with_signature( - 2774
catalog.path(), - 2775
"Signed catalog", - 2776
MarketplaceTrust::PinnedCommit, - 2777
Some(SignatureEvidence { - 2778
algorithm: "ed25519".into(), - 2779
key_id: "team-key".into(), - 2780
public_key, - 2781
signature, - 2782
verified: false, - 2783
revoked: false, - 2784
}), - 2785
) - 2786
.unwrap(); - 2787
assert!(source.signature.as_ref().unwrap().verified); - 2788
assert!(store.set_source_enabled(&source.id, true).unwrap().enabled); - 2789
store.set_key_revoked("team-key", true).unwrap(); - 2790
assert!(matches!( - 2791
store.set_source_enabled(&source.id, false), - 2792
Err(PluginError::UnsafePackage(_)) - 2793
)); - 2794
} - 2795
- 2796
#[test] - 2797
fn enabled_plugin_hooks_are_loaded_from_real_manifest_files() { - 2798
let source = tempfile::tempdir().unwrap(); - 2799
package(source.path()); - 2800
write( - 2801
&source.path().join("vak-plugin.json"), - 2802
r#"{"schema":1,"name":"daily-brief","version":"1.2.3","description":"Prepare a daily brief.","license":"MIT","components":{"skills":["skills"],"commands":["commands"],"hooks":["hooks.json"]}}"#, - 2803
); - 2804
write( - 2805
&source.path().join("hooks.json"), - 2806
r#"{"hooks":[{"event":"pre_tool_use","match":"bash","command":"printf hook","timeout_ms":2500}]}"#, - 2807
); - 2808
let home = tempfile::tempdir().unwrap(); - 2809
let store = PluginStore::new(home.path()); - 2810
store - 2811
.install_local(source.path(), InstallOptions::default()) - 2812
.unwrap(); - 2813
store.enable("daily-brief").unwrap(); - 2814
let hooks = store.enabled_hooks().unwrap(); - 2815
assert_eq!(hooks.len(), 1); - 2816
assert_eq!(hooks[0].1.event, "pre_tool_use"); - 2817
assert_eq!(hooks[0].1.matcher.as_deref(), Some("bash")); - 2818
assert_eq!(hooks[0].1.command, "printf hook"); - 2819
} - 2820
- 2821
#[test] - 2822
fn materializes_local_catalog_entry_and_rejects_unpinned_remote() { - 2823
let catalog = tempfile::tempdir().unwrap(); - 2824
write( - 2825
&catalog.path().join("marketplace.json"), - 2826
r#"{"name":"team","plugins":[{"name":"tool","source":"./tool"}]}"#, - 2827
); - 2828
write( - 2829
&catalog.path().join("tool/vak-plugin.json"), - 2830
r#"{"schema":1,"name":"tool","version":"1.0.0","description":"Tool","license":"MIT"}"#, - 2831
); - 2832
let inspection = inspect_catalog(catalog.path()).unwrap(); - 2833
let local = materialize_catalog_entry( - 2834
catalog.path(), - 2835
&inspection.entries[0], - 2836
&catalog.path().join("downloads"), - 2837
) - 2838
.unwrap(); - 2839
assert!(local.ends_with("tool")); - 2840
let remote = CatalogEntry { - 2841
name: "remote".into(), - 2842
source: serde_json::json!("https://github.com/acme/tool.git"), - 2843
version: None, - 2844
description: None, - 2845
license: None, - 2846
}; - 2847
assert!(matches!( - 2848
materialize_catalog_entry(catalog.path(), &remote, &catalog.path().join("downloads")), - 2849
Err(PluginError::UnsafePackage(_)) - 2850
)); - 2851
} - 2852
- 2853
#[test] - 2854
fn signature_verification_rejects_malformed_or_wrong_evidence() { - 2855
assert!(matches!( - 2856
verify_ed25519_signature(b"catalog", "not-base64", "not-base64"), - 2857
Err(PluginError::UnsafePackage(_)) - 2858
)); - 2859
let key = base64::engine::general_purpose::STANDARD.encode([0u8; 32]); - 2860
let sig = base64::engine::general_purpose::STANDARD.encode([0u8; 64]); - 2861
assert!(matches!( - 2862
verify_ed25519_signature(b"catalog", &key, &sig), - 2863
Err(PluginError::UnsafePackage(_)) - 2864
)); - 2865
} - 2866
- 2867
#[test] - 2868
fn retired_plugin_scan_detects_references_to_retired_tools() { - 2869
let temp = tempfile::tempdir().unwrap(); - 2870
// Build a plugin package that references `python_eval` in its skill - 2871
// description — the exact pattern that caused the hallucination. - 2872
write( - 2873
&temp.path().join("vak-plugin.json"), - 2874
r#"{ - 2875
"schema": 1, - 2876
"name": "legacy-python", - 2877
"version": "1.0.0", - 2878
"description": "Legacy", - 2879
"license": "MIT", - 2880
"components": {"skills": ["skills"]} - 2881
}"#, - 2882
); - 2883
write( - 2884
&temp.path().join("skills/python-exec/SKILL.md"), - 2885
"---\nname: python-exec\ndescription: Execute Python using the `python_eval` tool.\n---\nAlways use `python_eval`.\n", - 2886
); - 2887
let store = PluginStore::new(temp.path()); - 2888
store - 2889
.install_local( - 2890
temp.path(), - 2891
InstallOptions { - 2892
scope: InstallScope::Workspace, - 2893
allow_unlicensed: false, - 2894
}, - 2895
) - 2896
.unwrap(); - 2897
store.enable("legacy-python").unwrap(); - 2898
let flagged = store.retired_plugins().unwrap(); - 2899
assert_eq!(flagged.len(), 1); - 2900
assert_eq!(flagged[0].0, "legacy-python"); - 2901
assert!(flagged[0].1.contains(&"python_eval".to_string())); - 2902
} - 2903
- 2904
#[test] - 2905
fn clean_plugin_is_not_flagged_as_retired() { - 2906
let temp = tempfile::tempdir().unwrap(); - 2907
package(temp.path()); - 2908
let store = PluginStore::new(temp.path()); - 2909
store - 2910
.install_local( - 2911
temp.path(), - 2912
InstallOptions { - 2913
scope: InstallScope::Workspace, - 2914
allow_unlicensed: false, - 2915
}, - 2916
) - 2917
.unwrap(); - 2918
store.enable("daily-brief").unwrap(); - 2919
let flagged = store.retired_plugins().unwrap(); - 2920
assert!(flagged.is_empty()); - 2921
} - 2922
} - 2923
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.