Skip to main content

adk_computer_use/contracts/
action.rs

1//! Action classification, resource context, provenance, postconditions, and the
2//! immutable [`ActionEnvelope`] proposed by the graph and enforced by the runtime.
3
4use super::target::{TargetEvidence, TargetSensitivityEvidence};
5use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
6use serde_json::Value;
7
8/// Enforced execution mode selected for an action.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum ExecutionMode {
12    /// Observe and preview only.
13    Shadow,
14    /// Proved non-foreground actuation only.
15    Background,
16    /// Bounded exclusive foreground transaction.
17    Foreground,
18}
19
20/// Operation-aware action class used by authorization and policy.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(rename_all = "snake_case")]
23pub enum ActionClass {
24    /// Read-only observation.
25    Observe,
26    /// Navigation without persistent side effects.
27    Navigate,
28    /// A reversible edit.
29    EditReversible,
30    /// Communication that leaves the machine.
31    CommunicateExternal,
32    /// An authentication interaction.
33    Authentication,
34    /// A financial transaction.
35    Financial,
36    /// A destructive, non-reversible operation.
37    Destructive,
38    /// A change to privileges or permissions.
39    PrivilegeChange,
40    /// Access to secret material.
41    SecretAccess,
42}
43
44/// Optional resource identifiers describing what an action touches.
45#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
46#[serde(rename_all = "camelCase")]
47pub struct ActionResourceContext {
48    /// Target application/bundle identifier.
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub target_app_id: Option<String>,
51    /// Target window identifier.
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub target_window_id: Option<Value>,
54    /// Filesystem path acted upon.
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub filesystem_path: Option<String>,
57    /// Filesystem destination (e.g. for a move/copy).
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub filesystem_destination: Option<String>,
60    /// Registry path acted upon (Windows).
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub registry_path: Option<String>,
63    /// Process name acted upon.
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub process_name: Option<String>,
66    /// Process identifier acted upon.
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub process_id: Option<u32>,
69    /// Browser domain acted upon.
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub browser_domain: Option<String>,
72}
73
74/// Provenance describing whether an action derives from untrusted instructions.
75#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
76#[serde(rename_all = "camelCase")]
77pub struct ActionProvenance {
78    /// Whether the action originates from untrusted instruction text.
79    pub untrusted_instruction: bool,
80    /// Observation frames the action was derived from.
81    pub source_observation_ids: Vec<String>,
82    /// Whether the action crosses a data trust boundary.
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub crosses_data_boundary: Option<bool>,
85}
86
87fn deserialize_false<'de, D>(deserializer: D) -> Result<bool, D::Error>
88where
89    D: Deserializer<'de>,
90{
91    let value = bool::deserialize(deserializer)?;
92    if value {
93        return Err(de::Error::custom(
94            "process postcondition can only prove a non-running process",
95        ));
96    }
97    Ok(false)
98}
99
100fn serialize_false<S>(value: &bool, serializer: S) -> Result<S::Ok, S::Error>
101where
102    S: Serializer,
103{
104    if *value {
105        return Err(serde::ser::Error::custom(
106            "process postcondition can only prove a non-running process",
107        ));
108    }
109    serializer.serialize_bool(false)
110}
111
112/// Digest-only expected state independently verified by computer-use-mcp.
113#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
114#[serde(tag = "kind")]
115pub enum ActionPostcondition {
116    /// Expected UI element state, keyed by role/label with a value digest.
117    #[serde(rename = "ui_element")]
118    UiElement {
119        /// Accessibility role of the element, if specified.
120        #[serde(skip_serializing_if = "Option::is_none")]
121        role: Option<String>,
122        /// Label of the element, if specified.
123        #[serde(skip_serializing_if = "Option::is_none")]
124        label: Option<String>,
125        /// Whether the element is expected to exist.
126        exists: bool,
127        /// Digest of the expected value (never the raw value).
128        #[serde(rename = "valueDigest", skip_serializing_if = "Option::is_none")]
129        value_digest: Option<String>,
130    },
131    /// Expected filesystem state with a content digest.
132    #[serde(rename = "filesystem")]
133    Filesystem {
134        /// Path expected to exist or not.
135        path: String,
136        /// Whether the path is expected to exist.
137        exists: bool,
138        /// Digest of the expected file contents (never the raw contents).
139        #[serde(rename = "contentDigest", skip_serializing_if = "Option::is_none")]
140        content_digest: Option<String>,
141    },
142    /// Expected registry state with a value digest (Windows).
143    #[serde(rename = "registry")]
144    Registry {
145        /// Registry path.
146        path: String,
147        /// Value name.
148        name: String,
149        /// Whether the value is expected to exist.
150        exists: bool,
151        /// Digest of the expected value (never the raw value).
152        #[serde(rename = "valueDigest", skip_serializing_if = "Option::is_none")]
153        value_digest: Option<String>,
154    },
155    /// Expected process state. Can only prove a process is *not* running.
156    #[serde(rename = "process")]
157    Process {
158        /// Process identifier.
159        pid: u32,
160        /// Must be `false`; a running-process assertion is rejected on the wire.
161        #[serde(deserialize_with = "deserialize_false", serialize_with = "serialize_false")]
162        running: bool,
163    },
164    /// Expected window existence state.
165    #[serde(rename = "window")]
166    Window {
167        /// Window identifier.
168        #[serde(rename = "windowId")]
169        window_id: u64,
170        /// Whether the window is expected to exist.
171        exists: bool,
172    },
173}
174
175/// Immutable action proposed by the graph and enforced by the runtime.
176#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
177#[serde(rename_all = "camelCase")]
178pub struct ActionEnvelope {
179    /// Unique action identifier.
180    pub action_id: String,
181    /// The session this action belongs to.
182    pub session_id: String,
183    /// Optional execution group for multi-agent coordination.
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub execution_group_id: Option<String>,
186    /// Authenticated principal proposing the action.
187    pub principal_id: String,
188    /// Optional agent identifier.
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub agent_id: Option<String>,
191    /// Runtime tool name.
192    pub tool: String,
193    /// Semantic operation name.
194    pub operation: String,
195    /// Operation-aware action class.
196    pub action_class: ActionClass,
197    /// Requested execution mode.
198    pub requested_mode: ExecutionMode,
199    /// Fresh target evidence, when the action has a target.
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub target: Option<TargetEvidence>,
202    /// Value-free target sensitivity evidence, when available.
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub target_sensitivity: Option<TargetSensitivityEvidence>,
205    /// Resource identifiers the action touches.
206    #[serde(skip_serializing_if = "Option::is_none")]
207    pub resource: Option<ActionResourceContext>,
208    /// Provenance describing instruction trust.
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub provenance: Option<ActionProvenance>,
211    /// Data sensitivity labels attached to the action.
212    pub data_labels: Vec<String>,
213    /// Digest-only postcondition independently verified by the runtime.
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub postcondition: Option<ActionPostcondition>,
216    /// Whether the action is reversible.
217    pub reversible: bool,
218    /// Whether the action has an external side effect.
219    pub external_side_effect: bool,
220    /// RFC 3339 timestamp the action was proposed.
221    pub proposed_at: String,
222    /// RFC 3339 expiry after which the action is invalid.
223    pub expires_at: String,
224    /// Digest of the action arguments, bound by approval.
225    pub args_digest: String,
226}