Skip to main content

agent_workspace_contract/environment/
ports.rs

1use super::*;
2
3#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
4#[serde(deny_unknown_fields, rename_all = "camelCase")]
5pub enum EnvironmentIdempotencyState {
6    Pending,
7    Completed,
8}
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(deny_unknown_fields, rename_all = "camelCase")]
12pub struct EnvironmentIdempotencyRecord {
13    pub id: String,
14    pub scope: ResourceScope,
15    pub action: String,
16    /// SHA-256 of the caller-provided key; the key itself is never persisted.
17    pub key_hash: String,
18    pub request_hash: String,
19    #[serde(default, skip_serializing_if = "Option::is_none")]
20    pub resource_id: Option<String>,
21    pub state: EnvironmentIdempotencyState,
22    /// Runtime instance currently allowed to enter Provider I/O.
23    pub claim_owner: String,
24    /// A dead instance can be taken over only after this lease expires.
25    pub claim_expires_at_ms: u64,
26    pub attempt: u32,
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub result_json: Option<String>,
29    pub started_at_ms: u64,
30    #[serde(default, skip_serializing_if = "Option::is_none")]
31    pub completed_at_ms: Option<u64>,
32}
33
34/// Durable metadata snapshot for one tenant/project scope.
35///
36/// Provider bytes and credentials are deliberately absent. Repositories may
37/// store each collection in a dedicated table while exposing one CAS unit to
38/// the application service so fencing and revision changes commit together.
39#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
40#[serde(deny_unknown_fields, rename_all = "camelCase")]
41pub struct EnvironmentMetadataSnapshot {
42    pub version: u64,
43    #[serde(default)]
44    pub computers: Vec<ComputerRecord>,
45    #[serde(default)]
46    pub computer_bindings: Vec<ComputerBinding>,
47    #[serde(default)]
48    pub spaces: Vec<SpaceRecord>,
49    #[serde(default)]
50    pub sandboxes: Vec<SandboxRecord>,
51    #[serde(default)]
52    pub snapshots: Vec<SnapshotRecord>,
53    #[serde(default)]
54    pub templates: Vec<TemplateVersionRecord>,
55    #[serde(default)]
56    pub grants: Vec<SpaceGrant>,
57    #[serde(default)]
58    pub sessions: Vec<SpaceCollaborationSession>,
59    #[serde(default)]
60    pub tickets: Vec<SpaceAccessTicketRecord>,
61    #[serde(default)]
62    pub leases: Vec<SpaceWriteLease>,
63    #[serde(default)]
64    pub fencing_tokens: BTreeMap<String, u64>,
65    #[serde(default)]
66    pub delegations: Vec<DelegationRecord>,
67    #[serde(default)]
68    pub change_sets: Vec<SpaceChangeSetRecord>,
69    #[serde(default)]
70    pub idempotency: Vec<EnvironmentIdempotencyRecord>,
71}
72
73#[async_trait]
74pub trait EnvironmentMetadataRepository: Send + Sync + Debug {
75    async fn load(&self, scope: &ResourceScope) -> Result<EnvironmentMetadataSnapshot>;
76
77    /// Stable tenant/project pagination for background lifecycle reconciliation.
78    async fn list_scopes(
79        &self,
80        after: Option<&ResourceScope>,
81        limit: usize,
82    ) -> Result<Vec<ResourceScope>>;
83
84    /// Atomically replace metadata when the stored scope version equals
85    /// `expected_version`. Returns false on a concurrent writer conflict.
86    async fn compare_and_swap(
87        &self,
88        scope: &ResourceScope,
89        expected_version: u64,
90        snapshot: &EnvironmentMetadataSnapshot,
91    ) -> Result<bool>;
92}
93
94#[async_trait]
95pub trait ComputerProvider: Send + Sync + Debug {
96    fn kind(&self) -> &str;
97    fn capabilities(&self) -> ProviderCapabilities;
98    fn validate_create(&self, _request: &ProviderComputerRequest) -> Result<()> {
99        Ok(())
100    }
101    async fn create_computer(
102        &self,
103        scope: &ResourceScope,
104        request: &ProviderComputerRequest,
105        idempotency_key: &str,
106    ) -> Result<ProviderResourceRef>;
107    async fn delete_computer(&self, external_id: &str, idempotency_key: &str) -> Result<()>;
108}
109
110#[async_trait]
111pub trait SandboxProvider: Send + Sync + Debug {
112    fn kind(&self) -> &str;
113    fn capabilities(&self) -> ProviderCapabilities;
114    fn validate_create(&self, _request: &ProviderSandboxRequest) -> Result<()> {
115        Ok(())
116    }
117    async fn create_sandbox(
118        &self,
119        scope: &ResourceScope,
120        request: &ProviderSandboxRequest,
121        idempotency_key: &str,
122    ) -> Result<ProviderResourceRef>;
123    async fn stop_sandbox(&self, external_id: &str, idempotency_key: &str) -> Result<()>;
124}
125
126#[async_trait]
127pub trait SpaceProvider: Send + Sync + Debug {
128    fn kind(&self) -> &str;
129    fn capabilities(&self) -> ProviderCapabilities;
130    async fn create_space(
131        &self,
132        scope: &ResourceScope,
133        request: &ProviderSpaceRequest,
134        idempotency_key: &str,
135    ) -> Result<ProviderResourceRef>;
136    async fn read_file(&self, external_id: &str, path: &str) -> Result<ProviderFileContent>;
137    async fn write_file(
138        &self,
139        external_id: &str,
140        path: &str,
141        content: &[u8],
142        expected_provider_revision: Option<&str>,
143        idempotency_key: &str,
144    ) -> Result<ProviderWriteResult>;
145    async fn create_change_set(
146        &self,
147        source_external_id: &str,
148        target_external_id: &str,
149        idempotency_key: &str,
150    ) -> Result<String>;
151    async fn apply_change_set(
152        &self,
153        source_external_id: &str,
154        target_external_id: &str,
155        provider_change_ref: &str,
156        idempotency_key: &str,
157    ) -> Result<ProviderWriteResult>;
158    async fn delete_space(&self, external_id: &str, idempotency_key: &str) -> Result<()>;
159}
160
161#[async_trait]
162pub trait SnapshotProviderPort: Send + Sync + Debug {
163    fn kind(&self) -> &str;
164    async fn capture_snapshot(
165        &self,
166        source: &ProviderSnapshotSource,
167        idempotency_key: &str,
168    ) -> Result<ProviderResourceRef>;
169    async fn delete_snapshot(
170        &self,
171        snapshot_external_id: &str,
172        idempotency_key: &str,
173    ) -> Result<()>;
174}
175
176#[async_trait]
177pub trait TemplateProviderPort: Send + Sync + Debug {
178    fn kind(&self) -> &str;
179    async fn inspect_template(&self, external_id: &str) -> Result<ProviderCapabilities>;
180}
181
182pub trait EnvironmentProviderRegistry: Send + Sync + Debug {
183    fn computer(&self, provider: &str) -> Result<Arc<dyn ComputerProvider>>;
184    fn sandbox(&self, provider: &str) -> Result<Arc<dyn SandboxProvider>>;
185    fn space(&self, provider: &str) -> Result<Arc<dyn SpaceProvider>>;
186    fn snapshot(&self, provider: &str) -> Result<Arc<dyn SnapshotProviderPort>>;
187    fn template(&self, provider: &str) -> Result<Arc<dyn TemplateProviderPort>>;
188}