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]
97pub trait EnvironmentCommandRepository: Send + Sync + Debug {
98 async fn store_computer_command(
99 &self,
100 request: StoreComputerCommandRequest,
101 ) -> Result<StoreComputerCommandOutcome>;
102
103 async fn get_computer_command(
104 &self,
105 scope: &ResourceScope,
106 computer_id: &str,
107 command_id: &str,
108 ) -> Result<Option<ComputerCommandResource>>;
109
110 async fn transition_computer_command(
111 &self,
112 request: ComputerCommandTransition,
113 ) -> Result<ComputerCommandResource>;
114
115 async fn complete_computer_command(
116 &self,
117 request: CompleteComputerCommandRequest,
118 ) -> Result<ComputerCommandResource>;
119
120 async fn computer_command_output(
121 &self,
122 scope: &ResourceScope,
123 computer_id: &str,
124 command_id: &str,
125 after: u64,
126 limit: usize,
127 ) -> Result<ComputerCommandOutputPage>;
128}
129
130pub trait EnvironmentRepository:
133 EnvironmentMetadataRepository + EnvironmentCommandRepository
134{
135}
136impl<T> EnvironmentRepository for T where
137 T: EnvironmentMetadataRepository + EnvironmentCommandRepository
138{
139}
140
141#[async_trait]
142pub trait ComputerProvider: Send + Sync + Debug {
143 fn kind(&self) -> &str;
144 fn capabilities(&self) -> ProviderCapabilities;
145 fn validate_create(&self, _request: &ProviderComputerRequest) -> Result<()> {
146 Ok(())
147 }
148 async fn create_computer(
149 &self,
150 scope: &ResourceScope,
151 request: &ProviderComputerRequest,
152 idempotency_key: &str,
153 ) -> Result<ProviderResourceRef>;
154 async fn delete_computer(&self, external_id: &str, idempotency_key: &str) -> Result<()>;
155 async fn create_space_directory(
156 &self,
157 computer_external_id: &str,
158 space_directory: &str,
159 request: &ProviderSpaceRequest,
160 idempotency_key: &str,
161 ) -> Result<()> {
162 let _ = (
163 computer_external_id,
164 space_directory,
165 request,
166 idempotency_key,
167 );
168 Err(EnvironmentDomainError::new(
169 EnvironmentErrorCode::CapabilityUnsupported,
170 "computer does not support Space directories",
171 )
172 .into())
173 }
174 async fn delete_space_directory(
175 &self,
176 computer_external_id: &str,
177 space_directory: &str,
178 idempotency_key: &str,
179 ) -> Result<()> {
180 let _ = (computer_external_id, space_directory, idempotency_key);
181 Err(EnvironmentDomainError::new(
182 EnvironmentErrorCode::CapabilityUnsupported,
183 "computer does not support Space directories",
184 )
185 .into())
186 }
187 async fn read_file(
188 &self,
189 computer_external_id: &str,
190 space_directory: &str,
191 path: &str,
192 ) -> Result<ProviderFileContent> {
193 let _ = (computer_external_id, space_directory, path);
194 Err(EnvironmentDomainError::new(
195 EnvironmentErrorCode::CapabilityUnsupported,
196 "computer does not support file reads",
197 )
198 .into())
199 }
200 async fn write_file(
201 &self,
202 computer_external_id: &str,
203 space_directory: &str,
204 path: &str,
205 content: &[u8],
206 expected_provider_revision: Option<&str>,
207 idempotency_key: &str,
208 ) -> Result<ProviderWriteResult> {
209 let _ = (
210 computer_external_id,
211 space_directory,
212 path,
213 content,
214 expected_provider_revision,
215 idempotency_key,
216 );
217 Err(EnvironmentDomainError::new(
218 EnvironmentErrorCode::CapabilityUnsupported,
219 "computer does not support file writes",
220 )
221 .into())
222 }
223 async fn delete_file(
224 &self,
225 computer_external_id: &str,
226 space_directory: &str,
227 path: &str,
228 idempotency_key: &str,
229 ) -> Result<()> {
230 let _ = (computer_external_id, space_directory, path, idempotency_key);
231 Err(EnvironmentDomainError::new(
232 EnvironmentErrorCode::CapabilityUnsupported,
233 "computer does not support file deletes",
234 )
235 .into())
236 }
237 async fn list_files(
238 &self,
239 computer_external_id: &str,
240 space_directory: &str,
241 path: &str,
242 ) -> Result<Vec<ProviderFileEntry>> {
243 let _ = (computer_external_id, space_directory, path);
244 Err(EnvironmentDomainError::new(
245 EnvironmentErrorCode::CapabilityUnsupported,
246 "computer does not support directory listing",
247 )
248 .into())
249 }
250 async fn create_change_set(
251 &self,
252 computer_external_id: &str,
253 source_space_directory: &str,
254 target_space_directory: &str,
255 idempotency_key: &str,
256 ) -> Result<String> {
257 let _ = (
258 computer_external_id,
259 source_space_directory,
260 target_space_directory,
261 idempotency_key,
262 );
263 Err(EnvironmentDomainError::new(
264 EnvironmentErrorCode::CapabilityUnsupported,
265 "computer does not support change sets",
266 )
267 .into())
268 }
269 async fn apply_change_set(
270 &self,
271 computer_external_id: &str,
272 source_space_directory: &str,
273 target_space_directory: &str,
274 provider_change_ref: &str,
275 idempotency_key: &str,
276 ) -> Result<ProviderWriteResult> {
277 let _ = (
278 computer_external_id,
279 source_space_directory,
280 target_space_directory,
281 provider_change_ref,
282 idempotency_key,
283 );
284 Err(EnvironmentDomainError::new(
285 EnvironmentErrorCode::CapabilityUnsupported,
286 "computer does not support change sets",
287 )
288 .into())
289 }
290 async fn exec_command(
291 &self,
292 computer_external_id: &str,
293 space_directory: &str,
294 command: &CommandSpec,
295 ) -> Result<ProviderCommandOutput> {
296 let _ = (computer_external_id, space_directory, command);
297 Err(EnvironmentDomainError::new(
298 EnvironmentErrorCode::CapabilityUnsupported,
299 "computer does not support managed commands",
300 )
301 .into())
302 }
303 async fn exec_managed_command(
304 &self,
305 computer_external_id: &str,
306 space_directory: &str,
307 command_id: &str,
308 command: &CommandSpec,
309 ) -> Result<ProviderCommandOutput> {
310 let _ = command_id;
311 self.exec_command(computer_external_id, space_directory, command)
312 .await
313 }
314 async fn resume_managed_command(
315 &self,
316 computer_external_id: &str,
317 space_directory: &str,
318 command_id: &str,
319 command: &CommandSpec,
320 ) -> Result<Option<ProviderCommandOutput>> {
321 let _ = (computer_external_id, space_directory, command_id, command);
322 Ok(None)
323 }
324 async fn retry_managed_command(
325 &self,
326 computer_external_id: &str,
327 space_directory: &str,
328 command_id: &str,
329 command: &CommandSpec,
330 ) -> Result<Option<ProviderCommandOutput>> {
331 self.resume_managed_command(computer_external_id, space_directory, command_id, command)
332 .await
333 }
334 async fn cancel_command(
335 &self,
336 computer_external_id: &str,
337 space_directory: &str,
338 command_id: &str,
339 ) -> Result<()> {
340 let _ = (computer_external_id, space_directory, command_id);
341 Ok(())
342 }
343}
344
345#[async_trait]
346pub trait SandboxProvider: Send + Sync + Debug {
347 fn kind(&self) -> &str;
348 fn capabilities(&self) -> ProviderCapabilities;
349 fn validate_create(&self, _request: &ProviderSandboxRequest) -> Result<()> {
350 Ok(())
351 }
352 async fn create_sandbox(
353 &self,
354 scope: &ResourceScope,
355 request: &ProviderSandboxRequest,
356 idempotency_key: &str,
357 ) -> Result<ProviderResourceRef>;
358 async fn stop_sandbox(&self, external_id: &str, idempotency_key: &str) -> Result<()>;
359 async fn write_file(
360 &self,
361 external_id: &str,
362 path: &str,
363 content: &[u8],
364 expected_provider_revision: Option<&str>,
365 idempotency_key: &str,
366 ) -> Result<ProviderWriteResult> {
367 let _ = (
368 external_id,
369 path,
370 content,
371 expected_provider_revision,
372 idempotency_key,
373 );
374 Err(EnvironmentDomainError::new(
375 EnvironmentErrorCode::CapabilityUnsupported,
376 "sandbox does not support file writes",
377 )
378 .into())
379 }
380 async fn exec_command(
381 &self,
382 external_id: &str,
383 command: &CommandSpec,
384 ) -> Result<ProviderCommandOutput> {
385 let _ = (external_id, command);
386 Err(EnvironmentDomainError::new(
387 EnvironmentErrorCode::CapabilityUnsupported,
388 "sandbox does not support managed commands",
389 )
390 .into())
391 }
392}
393
394#[async_trait]
395pub trait SpaceProvider: Send + Sync + Debug {
396 fn kind(&self) -> &str;
397 fn capabilities(&self) -> ProviderCapabilities;
398 async fn create_space(
399 &self,
400 scope: &ResourceScope,
401 request: &ProviderSpaceRequest,
402 idempotency_key: &str,
403 ) -> Result<ProviderResourceRef>;
404 async fn read_file(&self, external_id: &str, path: &str) -> Result<ProviderFileContent>;
405 async fn write_file(
406 &self,
407 external_id: &str,
408 path: &str,
409 content: &[u8],
410 expected_provider_revision: Option<&str>,
411 idempotency_key: &str,
412 ) -> Result<ProviderWriteResult>;
413 async fn create_change_set(
414 &self,
415 source_external_id: &str,
416 target_external_id: &str,
417 idempotency_key: &str,
418 ) -> Result<String>;
419 async fn apply_change_set(
420 &self,
421 source_external_id: &str,
422 target_external_id: &str,
423 provider_change_ref: &str,
424 idempotency_key: &str,
425 ) -> Result<ProviderWriteResult>;
426 async fn delete_space(&self, external_id: &str, idempotency_key: &str) -> Result<()>;
427}
428
429#[async_trait]
430pub trait SnapshotProviderPort: Send + Sync + Debug {
431 fn kind(&self) -> &str;
432 async fn capture_snapshot(
433 &self,
434 source: &ProviderSnapshotSource,
435 idempotency_key: &str,
436 ) -> Result<ProviderResourceRef>;
437 async fn delete_snapshot(
438 &self,
439 snapshot_external_id: &str,
440 idempotency_key: &str,
441 ) -> Result<()>;
442}
443
444#[async_trait]
445pub trait TemplateProviderPort: Send + Sync + Debug {
446 fn kind(&self) -> &str;
447 async fn inspect_template(&self, external_id: &str) -> Result<ProviderCapabilities>;
448}
449
450pub trait EnvironmentProviderRegistry: Send + Sync + Debug {
451 fn computer(&self, provider: &str) -> Result<Arc<dyn ComputerProvider>>;
452 fn sandbox(&self, provider: &str) -> Result<Arc<dyn SandboxProvider>>;
453 fn space(&self, provider: &str) -> Result<Arc<dyn SpaceProvider>>;
454 fn snapshot(&self, provider: &str) -> Result<Arc<dyn SnapshotProviderPort>>;
455 fn template(&self, provider: &str) -> Result<Arc<dyn TemplateProviderPort>>;
456}