1use super::*;
2use crate::CommandSpec;
3
4#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
5#[serde(deny_unknown_fields, rename_all = "camelCase")]
6pub enum EnvironmentIdempotencyState {
7 Pending,
8 Completed,
9}
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(deny_unknown_fields, rename_all = "camelCase")]
13pub struct EnvironmentIdempotencyRecord {
14 pub id: String,
15 pub scope: ResourceScope,
16 pub action: String,
17 pub key_hash: String,
19 pub request_hash: String,
20 #[serde(default, skip_serializing_if = "Option::is_none")]
21 pub resource_id: Option<String>,
22 pub state: EnvironmentIdempotencyState,
23 pub claim_owner: String,
25 pub claim_expires_at_ms: u64,
27 pub attempt: u32,
28 #[serde(default, skip_serializing_if = "Option::is_none")]
29 pub result_json: Option<String>,
30 pub started_at_ms: u64,
31 #[serde(default, skip_serializing_if = "Option::is_none")]
32 pub completed_at_ms: Option<u64>,
33}
34
35#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
41#[serde(deny_unknown_fields, rename_all = "camelCase")]
42pub struct EnvironmentMetadataSnapshot {
43 pub version: u64,
44 #[serde(default)]
45 pub computers: Vec<ComputerRecord>,
46 #[serde(default)]
47 pub spaces: Vec<SpaceRecord>,
48 #[serde(default)]
49 pub sandboxes: Vec<SandboxRecord>,
50 #[serde(default)]
51 pub snapshots: Vec<SnapshotRecord>,
52 #[serde(default)]
53 pub templates: Vec<TemplateVersionRecord>,
54 #[serde(default)]
55 pub grants: Vec<SpaceGrant>,
56 #[serde(default)]
57 pub sessions: Vec<SpaceCollaborationSession>,
58 #[serde(default)]
59 pub tickets: Vec<SpaceAccessTicketRecord>,
60 #[serde(default)]
61 pub leases: Vec<SpaceWriteLease>,
62 #[serde(default)]
63 pub fencing_tokens: BTreeMap<String, u64>,
64 #[serde(default)]
65 pub delegations: Vec<DelegationRecord>,
66 #[serde(default)]
67 pub change_sets: Vec<SpaceChangeSetRecord>,
68 #[serde(default)]
69 pub idempotency: Vec<EnvironmentIdempotencyRecord>,
70}
71
72#[async_trait]
73pub trait EnvironmentMetadataRepository: Send + Sync + Debug {
74 async fn load(&self, scope: &ResourceScope) -> Result<EnvironmentMetadataSnapshot>;
75
76 async fn list_scopes(
78 &self,
79 after: Option<&ResourceScope>,
80 limit: usize,
81 ) -> Result<Vec<ResourceScope>>;
82
83 async fn compare_and_swap(
86 &self,
87 scope: &ResourceScope,
88 expected_version: u64,
89 snapshot: &EnvironmentMetadataSnapshot,
90 ) -> Result<bool>;
91}
92
93#[async_trait]
94pub trait ComputerProvider: Send + Sync + Debug {
95 fn kind(&self) -> &str;
96 fn capabilities(&self) -> ProviderCapabilities;
97 fn validate_create(&self, _request: &ProviderComputerRequest) -> Result<()> {
98 Ok(())
99 }
100 async fn create_computer(
101 &self,
102 scope: &ResourceScope,
103 request: &ProviderComputerRequest,
104 idempotency_key: &str,
105 ) -> Result<ProviderResourceRef>;
106 async fn delete_computer(&self, external_id: &str, idempotency_key: &str) -> Result<()>;
107 async fn create_space_directory(
108 &self,
109 computer_external_id: &str,
110 space_directory: &str,
111 request: &ProviderSpaceRequest,
112 idempotency_key: &str,
113 ) -> Result<()> {
114 let _ = (
115 computer_external_id,
116 space_directory,
117 request,
118 idempotency_key,
119 );
120 Err(EnvironmentDomainError::new(
121 EnvironmentErrorCode::CapabilityUnsupported,
122 "computer does not support Space directories",
123 )
124 .into())
125 }
126 async fn delete_space_directory(
127 &self,
128 computer_external_id: &str,
129 space_directory: &str,
130 idempotency_key: &str,
131 ) -> Result<()> {
132 let _ = (computer_external_id, space_directory, idempotency_key);
133 Err(EnvironmentDomainError::new(
134 EnvironmentErrorCode::CapabilityUnsupported,
135 "computer does not support Space directories",
136 )
137 .into())
138 }
139 async fn read_file(
140 &self,
141 computer_external_id: &str,
142 space_directory: &str,
143 path: &str,
144 ) -> Result<ProviderFileContent> {
145 let _ = (computer_external_id, space_directory, path);
146 Err(EnvironmentDomainError::new(
147 EnvironmentErrorCode::CapabilityUnsupported,
148 "computer does not support file reads",
149 )
150 .into())
151 }
152 async fn write_file(
153 &self,
154 computer_external_id: &str,
155 space_directory: &str,
156 path: &str,
157 content: &[u8],
158 expected_provider_revision: Option<&str>,
159 idempotency_key: &str,
160 ) -> Result<ProviderWriteResult> {
161 let _ = (
162 computer_external_id,
163 space_directory,
164 path,
165 content,
166 expected_provider_revision,
167 idempotency_key,
168 );
169 Err(EnvironmentDomainError::new(
170 EnvironmentErrorCode::CapabilityUnsupported,
171 "computer does not support file writes",
172 )
173 .into())
174 }
175 async fn create_change_set(
176 &self,
177 computer_external_id: &str,
178 source_space_directory: &str,
179 target_space_directory: &str,
180 idempotency_key: &str,
181 ) -> Result<String> {
182 let _ = (
183 computer_external_id,
184 source_space_directory,
185 target_space_directory,
186 idempotency_key,
187 );
188 Err(EnvironmentDomainError::new(
189 EnvironmentErrorCode::CapabilityUnsupported,
190 "computer does not support change sets",
191 )
192 .into())
193 }
194 async fn apply_change_set(
195 &self,
196 computer_external_id: &str,
197 source_space_directory: &str,
198 target_space_directory: &str,
199 provider_change_ref: &str,
200 idempotency_key: &str,
201 ) -> Result<ProviderWriteResult> {
202 let _ = (
203 computer_external_id,
204 source_space_directory,
205 target_space_directory,
206 provider_change_ref,
207 idempotency_key,
208 );
209 Err(EnvironmentDomainError::new(
210 EnvironmentErrorCode::CapabilityUnsupported,
211 "computer does not support change sets",
212 )
213 .into())
214 }
215 async fn exec_command(
216 &self,
217 computer_external_id: &str,
218 space_directory: &str,
219 command: &CommandSpec,
220 ) -> Result<ProviderCommandOutput> {
221 let _ = (computer_external_id, space_directory, command);
222 Err(EnvironmentDomainError::new(
223 EnvironmentErrorCode::CapabilityUnsupported,
224 "computer does not support managed commands",
225 )
226 .into())
227 }
228}
229
230#[async_trait]
231pub trait SandboxProvider: Send + Sync + Debug {
232 fn kind(&self) -> &str;
233 fn capabilities(&self) -> ProviderCapabilities;
234 fn validate_create(&self, _request: &ProviderSandboxRequest) -> Result<()> {
235 Ok(())
236 }
237 async fn create_sandbox(
238 &self,
239 scope: &ResourceScope,
240 request: &ProviderSandboxRequest,
241 idempotency_key: &str,
242 ) -> Result<ProviderResourceRef>;
243 async fn stop_sandbox(&self, external_id: &str, idempotency_key: &str) -> Result<()>;
244 async fn write_file(
245 &self,
246 external_id: &str,
247 path: &str,
248 content: &[u8],
249 expected_provider_revision: Option<&str>,
250 idempotency_key: &str,
251 ) -> Result<ProviderWriteResult> {
252 let _ = (
253 external_id,
254 path,
255 content,
256 expected_provider_revision,
257 idempotency_key,
258 );
259 Err(EnvironmentDomainError::new(
260 EnvironmentErrorCode::CapabilityUnsupported,
261 "sandbox does not support file writes",
262 )
263 .into())
264 }
265 async fn exec_command(
266 &self,
267 external_id: &str,
268 command: &CommandSpec,
269 ) -> Result<ProviderCommandOutput> {
270 let _ = (external_id, command);
271 Err(EnvironmentDomainError::new(
272 EnvironmentErrorCode::CapabilityUnsupported,
273 "sandbox does not support managed commands",
274 )
275 .into())
276 }
277}
278
279#[async_trait]
280pub trait SpaceProvider: Send + Sync + Debug {
281 fn kind(&self) -> &str;
282 fn capabilities(&self) -> ProviderCapabilities;
283 async fn create_space(
284 &self,
285 scope: &ResourceScope,
286 request: &ProviderSpaceRequest,
287 idempotency_key: &str,
288 ) -> Result<ProviderResourceRef>;
289 async fn read_file(&self, external_id: &str, path: &str) -> Result<ProviderFileContent>;
290 async fn write_file(
291 &self,
292 external_id: &str,
293 path: &str,
294 content: &[u8],
295 expected_provider_revision: Option<&str>,
296 idempotency_key: &str,
297 ) -> Result<ProviderWriteResult>;
298 async fn create_change_set(
299 &self,
300 source_external_id: &str,
301 target_external_id: &str,
302 idempotency_key: &str,
303 ) -> Result<String>;
304 async fn apply_change_set(
305 &self,
306 source_external_id: &str,
307 target_external_id: &str,
308 provider_change_ref: &str,
309 idempotency_key: &str,
310 ) -> Result<ProviderWriteResult>;
311 async fn delete_space(&self, external_id: &str, idempotency_key: &str) -> Result<()>;
312}
313
314#[async_trait]
315pub trait SnapshotProviderPort: Send + Sync + Debug {
316 fn kind(&self) -> &str;
317 async fn capture_snapshot(
318 &self,
319 source: &ProviderSnapshotSource,
320 idempotency_key: &str,
321 ) -> Result<ProviderResourceRef>;
322 async fn delete_snapshot(
323 &self,
324 snapshot_external_id: &str,
325 idempotency_key: &str,
326 ) -> Result<()>;
327}
328
329#[async_trait]
330pub trait TemplateProviderPort: Send + Sync + Debug {
331 fn kind(&self) -> &str;
332 async fn inspect_template(&self, external_id: &str) -> Result<ProviderCapabilities>;
333}
334
335pub trait EnvironmentProviderRegistry: Send + Sync + Debug {
336 fn computer(&self, provider: &str) -> Result<Arc<dyn ComputerProvider>>;
337 fn sandbox(&self, provider: &str) -> Result<Arc<dyn SandboxProvider>>;
338 fn space(&self, provider: &str) -> Result<Arc<dyn SpaceProvider>>;
339 fn snapshot(&self, provider: &str) -> Result<Arc<dyn SnapshotProviderPort>>;
340 fn template(&self, provider: &str) -> Result<Arc<dyn TemplateProviderPort>>;
341}