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}
245
246#[async_trait]
247pub trait SpaceProvider: Send + Sync + Debug {
248 fn kind(&self) -> &str;
249 fn capabilities(&self) -> ProviderCapabilities;
250 async fn create_space(
251 &self,
252 scope: &ResourceScope,
253 request: &ProviderSpaceRequest,
254 idempotency_key: &str,
255 ) -> Result<ProviderResourceRef>;
256 async fn read_file(&self, external_id: &str, path: &str) -> Result<ProviderFileContent>;
257 async fn write_file(
258 &self,
259 external_id: &str,
260 path: &str,
261 content: &[u8],
262 expected_provider_revision: Option<&str>,
263 idempotency_key: &str,
264 ) -> Result<ProviderWriteResult>;
265 async fn create_change_set(
266 &self,
267 source_external_id: &str,
268 target_external_id: &str,
269 idempotency_key: &str,
270 ) -> Result<String>;
271 async fn apply_change_set(
272 &self,
273 source_external_id: &str,
274 target_external_id: &str,
275 provider_change_ref: &str,
276 idempotency_key: &str,
277 ) -> Result<ProviderWriteResult>;
278 async fn delete_space(&self, external_id: &str, idempotency_key: &str) -> Result<()>;
279}
280
281#[async_trait]
282pub trait SnapshotProviderPort: Send + Sync + Debug {
283 fn kind(&self) -> &str;
284 async fn capture_snapshot(
285 &self,
286 source: &ProviderSnapshotSource,
287 idempotency_key: &str,
288 ) -> Result<ProviderResourceRef>;
289 async fn delete_snapshot(
290 &self,
291 snapshot_external_id: &str,
292 idempotency_key: &str,
293 ) -> Result<()>;
294}
295
296#[async_trait]
297pub trait TemplateProviderPort: Send + Sync + Debug {
298 fn kind(&self) -> &str;
299 async fn inspect_template(&self, external_id: &str) -> Result<ProviderCapabilities>;
300}
301
302pub trait EnvironmentProviderRegistry: Send + Sync + Debug {
303 fn computer(&self, provider: &str) -> Result<Arc<dyn ComputerProvider>>;
304 fn sandbox(&self, provider: &str) -> Result<Arc<dyn SandboxProvider>>;
305 fn space(&self, provider: &str) -> Result<Arc<dyn SpaceProvider>>;
306 fn snapshot(&self, provider: &str) -> Result<Arc<dyn SnapshotProviderPort>>;
307 fn template(&self, provider: &str) -> Result<Arc<dyn TemplateProviderPort>>;
308}