- 2575
package(linked.path()); - 2576
fs::hard_link( - 2577
linked.path().join("commands/brief.md"), - 2578
linked.path().join("commands/brief-copy.md"), - 2579
) - 2580
.unwrap(); - 2581
assert!(matches!( - 2582
inspect_package(linked.path()), - 2583
Err(PluginError::UnsafePackage(_)) - 2584
)); - 2585
} - 2586
- 2587
#[test] - 2588
fn enforces_file_and_size_limits() { - 2589
let temp = tempfile::tempdir().unwrap(); - 2590
package(temp.path()); - 2591
let error = inspect_package_with_limits( - 2592
temp.path(), - 2593
InspectLimits { - 2594
max_files: 1, - 2595
..InspectLimits::default() - 2596
}, - 2597
) - 2598
.unwrap_err(); - 2599
assert!(matches!(error, PluginError::LimitExceeded(_))); - 2600
} - 2601
- 2602
#[test] - 2603
fn installs_disabled_verifies_copy_and_removes() { - 2604
let source = tempfile::tempdir().unwrap(); - 2605
let home = tempfile::tempdir().unwrap(); - 2606
package(source.path()); - 2607
let store = PluginStore::new(home.path()); - 2608
let installed = store - 2609
.install_local(source.path(), InstallOptions::default()) - 2610
.unwrap(); - 2611
assert!(!installed.enabled); - 2612
assert!(installed.package_path.is_dir()); - 2613
assert_eq!( - 2614
inspect_package(&installed.package_path).unwrap().digest, - 2615
installed.digest - 2616
); - 2617
assert_eq!(store.list().unwrap().len(), 1); - 2618
let idempotent = store - 2619
.install_local(source.path(), InstallOptions::default()) - 2620
.unwrap(); - 2621
assert_eq!(installed.digest, idempotent.digest); - 2622
let removed = store.remove("daily-brief").unwrap(); - 2623
assert_eq!(removed.digest, installed.digest); - 2624
assert!(!installed.package_path.exists()); - 2625
assert!(store.list().unwrap().is_empty()); - 2626
let registry = store.load().unwrap(); - 2627
assert_eq!(registry.generation, 2); - 2628
assert_eq!(registry.audit.len(), 2); - 2629
assert_eq!(registry.audit[0].trace_id, installed.trace_id); - 2630
assert_eq!(registry.audit[1].trace_id, installed.trace_id); - 2631
} - 2632
- 2633
#[test] - 2634
fn refuses_unlicensed_install_without_explicit_override() { - 2635
let source = tempfile::tempdir().unwrap(); - 2636
let home = tempfile::tempdir().unwrap(); - 2637
write( - 2638
&source.path().join("SKILL.md"), - 2639
"---\nname: local-skill\ndescription: Local.\n---\nLocal.\n", - 2640
); - 2641
let store = PluginStore::new(home.path()); - 2642
assert!(matches!( - 2643
store.install_local(source.path(), InstallOptions::default()), - 2644
Err(PluginError::Unlicensed(_)) - 2645
)); - 2646
let installed = store - 2647
.install_local( - 2648
source.path(), - 2649
InstallOptions { - 2650
allow_unlicensed: true, - 2651
..InstallOptions::default() - 2652
}, - 2653
) - 2654
.unwrap(); - 2655
assert_eq!(installed.format, ManifestFormat::AgentSkill); - 2656
} - 2657
- 2658
#[test] - 2659
fn refuses_replacement_without_remove() { - 2660
let source = tempfile::tempdir().unwrap(); - 2661
let home = tempfile::tempdir().unwrap(); - 2662
package(source.path()); - 2663
let store = PluginStore::new(home.path()); - 2664
store - 2665
.install_local(source.path(), InstallOptions::default()) - 2666
.unwrap(); - 2667
write(&source.path().join("commands/brief.md"), "New content.\n"); - 2668
assert!(matches!( - 2669
store.install_local(source.path(), InstallOptions::default()), - 2670
Err(PluginError::AlreadyInstalled(_)) - 2671
)); - 2672
} - 2673
- 2674
#[test] - 2675
fn lifecycle_keeps_generations_and_audits_every_transition() { - 2676
let source = tempfile::tempdir().unwrap(); - 2677
let home = tempfile::tempdir().unwrap(); - 2678
package(source.path()); - 2679
let store = PluginStore::new(home.path()); - 2680
let first = store - 2681
.install_local(source.path(), InstallOptions::default()) - 2682
.unwrap(); - 2683
let enabled = store.enable("daily-brief").unwrap(); - 2684
assert!(enabled.enabled); - 2685
let disabled = store.disable("daily-brief").unwrap(); - 2686
assert!(!disabled.enabled); - 2687
write(&source.path().join("commands/brief.md"), "New content.\n"); - 2688
let second = store - 2689
.update_local(source.path(), InstallOptions::default()) - 2690
.unwrap(); - 2691
assert_ne!(first.digest, second.digest); - 2692
assert!(!second.enabled); - 2693
assert_eq!(store.versions("daily-brief").unwrap().len(), 2); - 2694
let rolled_back = store.rollback("daily-brief").unwrap(); - 2695
assert_eq!(rolled_back.digest, first.digest); - 2696
assert!(!rolled_back.enabled); - 2697
let actions: Vec<_> = store - 2698
.load() - 2699
.unwrap() - 2700
.audit - 2701
.into_iter() - 2702
.map(|event| event.action) - 2703
.collect(); - 2704
assert_eq!( - 2705
actions, - 2706
vec![ - 2707
PluginAuditAction::Installed, - 2708
PluginAuditAction::Enabled, - 2709
PluginAuditAction::Disabled, - 2710
PluginAuditAction::Updated, - 2711
PluginAuditAction::RolledBack, - 2712
] - 2713
); - 2714
} - 2715
- 2716
#[test] - 2717
fn catalog_sources_are_registered_disabled_with_snapshot_provenance() { - 2718
let catalog = tempfile::tempdir().unwrap(); - 2719
write( - 2720
&catalog.path().join("marketplace.json"), - 2721
r#"{"name":"team","plugins":[{"name":"tool","source":"./tool"}]}"#, - 2722
); - 2723
let home = tempfile::tempdir().unwrap(); - 2724
let store = PluginStore::new(home.path()); - 2725
let source = store - 2726
.register_catalog_source( - 2727
catalog.path(), - 2728
"Team catalog", - 2729
MarketplaceTrust::ManualReview, - 2730
) - 2731
.unwrap(); - 2732
assert!(!source.enabled); - 2733
assert_eq!(source.format, MarketplaceFormat::Copilot); - 2734
assert!(source.trace_id.starts_with("catalog:team:")); - 2735
assert_eq!(store.list_sources().unwrap(), vec![source.clone()]); - 2736
assert!(store.set_source_enabled(&source.id, true).unwrap().enabled); - 2737
assert!(store.load_sources().unwrap().sources[&source.id].enabled); - 2738
write( - 2739
&catalog.path().join("marketplace.json"), - 2740
r#"{"name":"team","plugins":[]}"#, - 2741
); - 2742
assert!(matches!( - 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.