Skip to main content

runx_runtime/effects/
mod.rs

1mod error;
2mod metadata;
3mod provider_permission;
4mod registry;
5mod types;
6
7pub use error::RuntimeEffectError;
8pub(crate) use metadata::effect_verification_refs;
9pub use metadata::{EFFECT_VERIFICATION_REFS_METADATA, insert_effect_verification_ref};
10pub use provider_permission::{
11    PROVIDER_PERMISSION_EFFECT_FAMILY, PROVIDER_PERMISSION_GRANT_ID_ENV,
12    PROVIDER_PERMISSION_GRANTED_SCOPES_ENV, ProviderPermissionAdmission, ProviderPermissionEffect,
13};
14pub use registry::RuntimeEffectRegistry;
15pub use types::{
16    EffectAdmission, EffectMetadataRefreshRequest, EffectOutputRequest, EffectReceiptRequest,
17    EffectReplay, EffectReplayOutputRequest, EffectReplayReceiptRequest, EffectStepRequest,
18    RuntimeEffect,
19};
20
21#[cfg(test)]
22mod tests {
23    use std::collections::BTreeMap;
24    use std::path::Path;
25
26    use runx_contracts::{AuthorityVerb, JsonObject, Reference};
27    use runx_core::state_machine::AuthorityAdmissionWitness;
28    use runx_parser::GraphStep;
29
30    use super::*;
31    use crate::adapter::{InvocationStatus, SkillOutput};
32
33    struct MockEffect;
34
35    impl RuntimeEffect for MockEffect {
36        fn family(&self) -> &'static str {
37            "deploy"
38        }
39
40        fn admit(
41            &self,
42            request: EffectStepRequest<'_>,
43        ) -> Result<Option<EffectAdmission>, RuntimeEffectError> {
44            let _ = request;
45            Ok(Some(EffectAdmission::new(
46                "deploy",
47                AuthorityVerb::Write,
48                AuthorityAdmissionWitness {
49                    verb: AuthorityVerb::Write,
50                    parent_term_id: "parent".to_owned(),
51                    child_term_id: "child".to_owned(),
52                    idempotency_key: Some("deploy-key".to_owned()),
53                    capability_ref: None,
54                },
55                (),
56            )))
57        }
58    }
59
60    #[test]
61    fn registry_dispatches_effect_family() {
62        let registry = RuntimeEffectRegistry::with_effect(MockEffect);
63        let step = test_step();
64        let inputs = JsonObject::new();
65        let env = BTreeMap::new();
66        let result = registry.admit(EffectStepRequest {
67            step: &step,
68            inputs: &inputs,
69            env: &env,
70            graph_dir: Path::new("."),
71        });
72        assert!(
73            matches!(
74                &result,
75                Ok(Some(admission))
76                    if admission.family() == "deploy" && admission.verb() == AuthorityVerb::Write
77            ),
78            "unexpected admission result: {result:?}"
79        );
80    }
81
82    #[test]
83    fn registry_rejects_missing_effect_family_after_admission() {
84        let registry = RuntimeEffectRegistry::empty();
85        let step = test_step();
86        let admission = EffectAdmission::new(
87            "absent",
88            AuthorityVerb::Write,
89            AuthorityAdmissionWitness {
90                verb: AuthorityVerb::Write,
91                parent_term_id: "parent".to_owned(),
92                child_term_id: "child".to_owned(),
93                idempotency_key: None,
94                capability_ref: None,
95            },
96            (),
97        );
98        let claim = JsonObject::new();
99        let mut output = SkillOutput {
100            status: InvocationStatus::Success,
101            stdout: String::new(),
102            stderr: String::new(),
103            exit_code: Some(0),
104            duration_ms: 0,
105            metadata: JsonObject::new(),
106        };
107
108        let result = registry.prepare_output(EffectOutputRequest {
109            step: &step,
110            admission: &admission,
111            claim: &claim,
112            output: &mut output,
113        });
114
115        assert!(
116            matches!(result, Err(RuntimeEffectError::MissingFamily { ref family }) if family == "absent"),
117            "unexpected missing-family result: {result:?}"
118        );
119    }
120
121    #[test]
122    fn verification_refs_round_trip_through_metadata() {
123        let mut metadata = JsonObject::new();
124        let reference = Reference::runx(runx_contracts::ReferenceType::Verification, "proof:1");
125        let insert = insert_effect_verification_ref(&mut metadata, reference.clone());
126        assert!(insert.is_ok(), "unexpected insert result: {insert:?}");
127        assert_eq!(effect_verification_refs(&metadata), Ok(vec![reference]));
128    }
129
130    fn test_step() -> GraphStep {
131        GraphStep {
132            id: "ship".to_owned(),
133            label: None,
134            skill: None,
135            tool: None,
136            run: None,
137            instructions: None,
138            artifacts: None,
139            runner: None,
140            inputs: JsonObject::new(),
141            context: BTreeMap::new(),
142            context_edges: Vec::new(),
143            context_skills: Vec::new(),
144            scopes: Vec::new(),
145            allowed_tools: None,
146            retry: None,
147            policy: None,
148            fanout_group: None,
149            when: None,
150            mutating: false,
151            idempotency_key: None,
152            mint_authority: None,
153            requested_scope_from: None,
154        }
155    }
156}