Skip to main content

agent_workspace_contract/
environment.rs

1use anyhow::Result;
2use async_trait::async_trait;
3use serde::{Deserialize, Serialize};
4use std::collections::{BTreeMap, BTreeSet};
5use std::fmt::Debug;
6use std::sync::Arc;
7
8mod base64_bytes {
9    use base64::{Engine as _, engine::general_purpose::STANDARD};
10    use serde::{Deserialize, Deserializer, Serializer};
11
12    pub fn serialize<S>(value: &[u8], serializer: S) -> Result<S::Ok, S::Error>
13    where
14        S: Serializer,
15    {
16        serializer.serialize_str(&STANDARD.encode(value))
17    }
18
19    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
20    where
21        D: Deserializer<'de>,
22    {
23        let value = String::deserialize(deserializer)?;
24        STANDARD.decode(value).map_err(serde::de::Error::custom)
25    }
26}
27
28mod catalog;
29pub use catalog::*;
30
31mod ports;
32pub use ports::*;
33
34mod owners;
35pub use owners::*;
36
37mod commands;
38pub use commands::*;
39
40mod resources;
41pub use resources::*;
42
43pub const COMPUTERS_PATH: &str = "/internal/v1/workspace/computers";
44pub const COMPUTER_PATH: &str = "/internal/v1/workspace/computers/{computerId}";
45pub const SPACES_PATH: &str = "/internal/v1/workspace/spaces";
46pub const SPACE_PATH: &str = "/internal/v1/workspace/spaces/{spaceId}";
47pub const SPACE_GRANTS_PATH: &str = "/internal/v1/workspace/spaces/{spaceId}/grants";
48pub const SPACE_GRANT_REVOKE_PATH: &str =
49    "/internal/v1/workspace/spaces/{spaceId}/grants/{grantId}/revoke";
50pub const SPACE_SESSIONS_PATH: &str = "/internal/v1/workspace/spaces/{spaceId}/sessions";
51pub const SPACE_SESSION_HEARTBEAT_PATH: &str =
52    "/internal/v1/workspace/space-sessions/{sessionId}/heartbeat";
53pub const SPACE_SESSION_CLOSE_PATH: &str =
54    "/internal/v1/workspace/space-sessions/{sessionId}/close";
55pub const SPACE_TICKETS_PATH: &str = "/internal/v1/workspace/space-access-tickets";
56pub const SPACE_LEASES_PATH: &str = "/internal/v1/workspace/spaces/{spaceId}/leases";
57pub const SPACE_LEASE_PATH: &str = "/internal/v1/workspace/spaces/{spaceId}/leases/{leaseId}";
58pub const SPACE_FILES_PATH: &str = "/internal/v1/workspace/spaces/{spaceId}/files/{*filePath}";
59pub const SPACE_FILES_READ_PATH: &str = "/internal/v1/workspace/spaces/{spaceId}/files/read";
60pub const SPACE_CHANGE_SETS_PATH: &str = "/internal/v1/workspace/spaces/{spaceId}/change-sets";
61pub const SPACE_CHANGE_SET_APPLY_PATH: &str =
62    "/internal/v1/workspace/spaces/{spaceId}/change-sets/{changeSetId}/apply";
63pub const SANDBOXES_PATH: &str = "/internal/v1/workspace/sandboxes";
64pub const SANDBOX_PATH: &str = "/internal/v1/workspace/sandboxes/{sandboxId}";
65pub const SANDBOX_STOP_PATH: &str = "/internal/v1/workspace/sandboxes/{sandboxId}/stop";
66pub const SNAPSHOTS_PATH: &str = "/internal/v1/workspace/snapshots";
67pub const SNAPSHOT_PATH: &str = "/internal/v1/workspace/snapshots/{snapshotId}";
68pub const BUILD_BUILTIN_SNAPSHOTS_PATH: &str = "/internal/v1/workspace/snapshots:build-builtin";
69pub const CATALOG_SNAPSHOTS_PATH: &str = "/internal/v1/workspace/snapshots:catalog";
70pub const TEMPLATES_PATH: &str = "/internal/v1/workspace/templates";
71pub const TEMPLATE_VERSIONS_PATH: &str = "/internal/v1/workspace/templates/{templateId}/versions";
72pub const TEMPLATE_VERSION_PATH: &str =
73    "/internal/v1/workspace/templates/{templateId}/versions/{version}";
74pub const TEMPLATE_VERSION_PUBLISH_PATH: &str =
75    "/internal/v1/workspace/templates/{templateId}/versions/{version}/publish";
76pub const TEMPLATE_VERSION_DEPRECATE_PATH: &str =
77    "/internal/v1/workspace/templates/{templateId}/versions/{version}/deprecate";
78pub const DELEGATIONS_PATH: &str = "/internal/v1/workspace/delegations";
79pub const DELEGATION_REVOKE_PATH: &str = "/internal/v1/workspace/delegations/{delegationId}/revoke";
80pub const ENVIRONMENT_MAX_FILE_BYTES: usize = 8 * 1024 * 1024;
81pub const ENVIRONMENT_MAX_PATH_BYTES: usize = 4096;
82
83/// Logical directory for a Space on its parent Computer filesystem.
84pub fn space_directory_path(space_id: &str) -> String {
85    format!("spaces/{space_id}")
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
89#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
90pub enum EnvironmentErrorCode {
91    NotFound,
92    Forbidden,
93    Conflict,
94    InvalidState,
95    CapabilityUnsupported,
96    TicketExpired,
97    LeaseRequired,
98    StaleFencingToken,
99    RevisionConflict,
100}
101
102#[derive(Debug, Clone)]
103pub struct EnvironmentDomainError {
104    pub code: EnvironmentErrorCode,
105    pub message: String,
106}
107
108impl EnvironmentDomainError {
109    pub fn new(code: EnvironmentErrorCode, message: impl Into<String>) -> Self {
110        Self {
111            code,
112            message: message.into(),
113        }
114    }
115}
116
117impl std::fmt::Display for EnvironmentDomainError {
118    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        formatter.write_str(&self.message)
120    }
121}
122
123impl std::error::Error for EnvironmentDomainError {}
124
125#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
126#[serde(rename_all = "snake_case")]
127pub enum PrincipalKind {
128    User,
129    Agent,
130}
131
132#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
133#[serde(deny_unknown_fields, rename_all = "camelCase")]
134pub struct PrincipalRef {
135    pub kind: PrincipalKind,
136    pub id: String,
137}
138
139impl PrincipalRef {
140    pub fn user(id: impl Into<String>) -> Self {
141        Self {
142            kind: PrincipalKind::User,
143            id: id.into(),
144        }
145    }
146
147    pub fn agent(id: impl Into<String>) -> Self {
148        Self {
149            kind: PrincipalKind::Agent,
150            id: id.into(),
151        }
152    }
153}
154
155#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
156#[serde(deny_unknown_fields, rename_all = "camelCase")]
157pub struct ResourceScope {
158    pub tenant_id: String,
159    pub project_id: String,
160}
161
162#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
163#[serde(deny_unknown_fields, rename_all = "camelCase")]
164pub struct ProviderResourceRef {
165    pub provider: String,
166    pub external_id: String,
167}
168
169#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
170#[serde(deny_unknown_fields, rename_all = "camelCase")]
171pub struct ProviderCapabilities {
172    #[serde(default)]
173    pub capabilities: BTreeSet<String>,
174}
175
176impl ProviderCapabilities {
177    pub fn supports(&self, capability: &str) -> bool {
178        self.capabilities.contains(capability)
179    }
180}
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
183#[serde(rename_all = "snake_case")]
184pub enum ComputerLifecycle {
185    Ready,
186    Deleted,
187}
188
189#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
190#[serde(deny_unknown_fields, rename_all = "camelCase")]
191pub struct ComputerRecord {
192    pub id: String,
193    pub scope: ResourceScope,
194    pub provider_ref: ProviderResourceRef,
195    pub lifecycle: ComputerLifecycle,
196    pub capabilities: ProviderCapabilities,
197    pub owners: Vec<OwnerRef>,
198    pub version: u64,
199    pub created_at_ms: u64,
200    pub updated_at_ms: u64,
201}
202
203#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
204#[serde(deny_unknown_fields, rename_all = "camelCase")]
205pub struct ComputerBinding {
206    pub computer_id: String,
207    pub principal: PrincipalRef,
208    pub role: String,
209    pub active: bool,
210    pub bound_at_ms: u64,
211    #[serde(default, skip_serializing_if = "Option::is_none")]
212    pub released_at_ms: Option<u64>,
213    pub version: u64,
214}
215
216#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
217#[serde(rename_all = "snake_case")]
218pub enum SpacePersistence {
219    Persistent,
220    CopyOnWrite,
221}
222
223#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
224#[serde(rename_all = "snake_case")]
225pub enum SpaceLifecycle {
226    Ready,
227    Deleted,
228}
229
230#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
231#[serde(deny_unknown_fields, rename_all = "camelCase")]
232pub struct SpaceRecord {
233    pub id: String,
234    pub computer_id: String,
235    pub scope: ResourceScope,
236    pub persistence: SpacePersistence,
237    pub lifecycle: SpaceLifecycle,
238    pub capabilities: ProviderCapabilities,
239    pub owners: Vec<OwnerRef>,
240    #[serde(default, skip_serializing_if = "Option::is_none")]
241    pub source_snapshot_id: Option<String>,
242    pub revision: u64,
243    pub version: u64,
244    pub created_at_ms: u64,
245    pub updated_at_ms: u64,
246}
247
248#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
249#[serde(rename_all = "snake_case")]
250pub enum SandboxPlacement {
251    Computer(String),
252    ProviderPool(String),
253}
254
255#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
256#[serde(rename_all = "snake_case")]
257pub enum SandboxLifecycle {
258    Ready,
259    Stopped,
260    Expired,
261}
262
263#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
264#[serde(deny_unknown_fields, rename_all = "camelCase")]
265pub struct SandboxMount {
266    pub alias: String,
267    pub space_id: String,
268    pub mode: SpaceAccessMode,
269    #[serde(default, skip_serializing_if = "Option::is_none")]
270    pub path_prefix: Option<String>,
271    pub access_ticket_token: String,
272}
273
274/// Persisted mount metadata. The bearer token from [`SandboxMount`] is
275/// deliberately replaced by its non-secret ticket id before persistence.
276#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
277#[serde(deny_unknown_fields, rename_all = "camelCase")]
278pub struct SandboxMountRecord {
279    pub alias: String,
280    pub space_id: String,
281    pub mode: SpaceAccessMode,
282    #[serde(default, skip_serializing_if = "Option::is_none")]
283    pub path_prefix: Option<String>,
284    pub access_ticket_id: String,
285}
286
287#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
288#[serde(deny_unknown_fields, rename_all = "camelCase")]
289pub struct SandboxRecord {
290    pub id: String,
291    pub scope: ResourceScope,
292    pub provider_ref: ProviderResourceRef,
293    pub placement: SandboxPlacement,
294    pub lifecycle: SandboxLifecycle,
295    pub owners: Vec<OwnerRef>,
296    #[serde(default, skip_serializing_if = "Option::is_none")]
297    pub access_delegation_id: Option<String>,
298    pub capabilities: ProviderCapabilities,
299    #[serde(default)]
300    pub mounts: Vec<SandboxMountRecord>,
301    pub expires_at_ms: u64,
302    pub version: u64,
303    pub created_at_ms: u64,
304    pub updated_at_ms: u64,
305}
306
307#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
308#[serde(rename_all = "snake_case")]
309pub enum SnapshotLifecycle {
310    Building,
311    Ready,
312    Failed,
313    Deleted,
314}
315
316#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
317#[serde(tag = "kind", rename_all = "snake_case")]
318pub enum SnapshotSourceRecord {
319    Build { spec: SnapshotBuildSpec },
320}
321
322#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
323#[serde(deny_unknown_fields, rename_all = "camelCase")]
324pub struct SnapshotRecord {
325    pub id: String,
326    pub scope: ResourceScope,
327    pub provider: String,
328    pub provider_ref: ProviderResourceRef,
329    pub source: SnapshotSourceRecord,
330    pub visibility: SnapshotVisibility,
331    pub owners: Vec<OwnerRef>,
332    #[serde(default)]
333    pub builtin: bool,
334    pub created_by: PrincipalRef,
335    pub lifecycle: SnapshotLifecycle,
336    #[serde(default, skip_serializing_if = "Option::is_none")]
337    pub digest: Option<String>,
338    #[serde(default, skip_serializing_if = "Option::is_none")]
339    pub size_bytes: Option<u64>,
340    #[serde(default, skip_serializing_if = "Option::is_none")]
341    pub error: Option<String>,
342    pub created_at_ms: u64,
343    pub updated_at_ms: u64,
344}
345
346#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
347#[serde(rename_all = "snake_case")]
348pub enum TemplateTarget {
349    Computer,
350    Sandbox,
351}
352
353#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
354#[serde(rename_all = "snake_case")]
355pub enum TemplateVersionState {
356    Draft,
357    Published,
358    Deprecated,
359}
360
361#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
362#[serde(deny_unknown_fields, rename_all = "camelCase")]
363pub struct TemplateVersionRecord {
364    pub template_id: String,
365    pub version: String,
366    pub scope: ResourceScope,
367    pub provider: String,
368    pub created_by: PrincipalRef,
369    #[serde(default, skip_serializing_if = "Option::is_none")]
370    pub provider_template_id: Option<String>,
371    pub target: TemplateTarget,
372    #[serde(default, skip_serializing_if = "Option::is_none")]
373    pub base_snapshot_id: Option<String>,
374    #[serde(default)]
375    pub resource_profile: BTreeMap<String, String>,
376    #[serde(default)]
377    pub parameter_schema: BTreeMap<String, String>,
378    pub state: TemplateVersionState,
379    pub created_at_ms: u64,
380}
381
382#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
383#[serde(rename_all = "snake_case")]
384pub enum SpaceCapability {
385    Read,
386    Write,
387    Share,
388    Manage,
389    Mount,
390    Snapshot,
391    Restore,
392    Delete,
393}
394
395#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
396#[serde(deny_unknown_fields, rename_all = "camelCase")]
397pub struct SpaceGrant {
398    pub id: String,
399    pub scope: ResourceScope,
400    pub space_id: String,
401    pub principal: PrincipalRef,
402    pub capabilities: BTreeSet<SpaceCapability>,
403    #[serde(default)]
404    pub path_prefixes: Vec<String>,
405    #[serde(default, skip_serializing_if = "Option::is_none")]
406    pub expires_at_ms: Option<u64>,
407    pub active: bool,
408    pub version: u64,
409}
410
411#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
412#[serde(rename_all = "snake_case")]
413pub enum SpaceAccessMode {
414    ReadOnly,
415    DirectWrite,
416    CopyOnWrite,
417}
418
419#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
420#[serde(rename_all = "snake_case")]
421pub enum SpaceSessionLifecycle {
422    Opening,
423    Active,
424    Closing,
425    Closed,
426    Expired,
427    Revoked,
428}
429
430#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
431#[serde(deny_unknown_fields, rename_all = "camelCase")]
432pub struct SpaceCollaborationSession {
433    pub id: String,
434    pub scope: ResourceScope,
435    pub space_id: String,
436    pub principal: PrincipalRef,
437    #[serde(default, skip_serializing_if = "Option::is_none")]
438    pub delegation_id: Option<String>,
439    pub requested_mode: SpaceAccessMode,
440    pub effective_mode: SpaceAccessMode,
441    pub lifecycle: SpaceSessionLifecycle,
442    pub last_heartbeat_at_ms: u64,
443    pub expires_at_ms: u64,
444    pub version: u64,
445}
446
447#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
448#[serde(deny_unknown_fields, rename_all = "camelCase")]
449pub struct SpaceAccessTicketRecord {
450    pub id: String,
451    pub token_hash: String,
452    pub scope: ResourceScope,
453    pub space_id: String,
454    pub session_id: String,
455    pub mode: SpaceAccessMode,
456    /// Capability-specific path rules captured at issue time. Runtime
457    /// intersects these with current Grant/Delegation authority on every use.
458    #[serde(default)]
459    pub capability_paths: BTreeMap<SpaceCapability, Vec<String>>,
460    #[serde(default, skip_serializing_if = "Option::is_none")]
461    pub fencing_token: Option<u64>,
462    pub expires_at_ms: u64,
463    pub revoked: bool,
464}
465
466#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
467#[serde(deny_unknown_fields, rename_all = "camelCase")]
468pub struct IssuedSpaceAccessTicket {
469    pub ticket: SpaceAccessTicketRecord,
470    /// Returned exactly once. Repositories and logs must never persist this
471    /// bearer value.
472    pub token: String,
473}
474
475#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
476#[serde(deny_unknown_fields, rename_all = "camelCase")]
477pub struct SpaceWriteLease {
478    pub id: String,
479    pub scope: ResourceScope,
480    pub space_id: String,
481    pub session_id: String,
482    pub fencing_token: u64,
483    pub version: u64,
484    pub acquired_at_ms: u64,
485    pub expires_at_ms: u64,
486    pub active: bool,
487}
488
489#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
490#[serde(deny_unknown_fields, rename_all = "camelCase")]
491pub struct DelegationRecord {
492    pub id: String,
493    pub scope: ResourceScope,
494    pub grantor_user_id: String,
495    pub delegate_agent_id: String,
496    #[serde(default)]
497    pub computer_ids: BTreeSet<String>,
498    #[serde(default)]
499    pub space_ids: BTreeSet<String>,
500    pub capabilities: BTreeSet<SpaceCapability>,
501    #[serde(default)]
502    pub path_prefixes: Vec<String>,
503    pub purpose: String,
504    pub expires_at_ms: u64,
505    #[serde(default, skip_serializing_if = "Option::is_none")]
506    pub revoked_at_ms: Option<u64>,
507}
508
509#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
510#[serde(rename_all = "snake_case")]
511pub enum ChangeSetLifecycle {
512    Ready,
513    Applied,
514}
515
516#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
517#[serde(deny_unknown_fields, rename_all = "camelCase")]
518pub struct SpaceChangeSetRecord {
519    pub id: String,
520    pub scope: ResourceScope,
521    pub source_space_id: String,
522    pub target_space_id: String,
523    pub base_revision: u64,
524    pub created_by_session_id: String,
525    #[serde(default, skip_serializing_if = "Option::is_none")]
526    pub provider_change_ref: Option<String>,
527    pub lifecycle: ChangeSetLifecycle,
528    pub created_at_ms: u64,
529}
530
531#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
532#[serde(deny_unknown_fields, rename_all = "camelCase")]
533pub struct CreateComputerRequest {
534    pub provider: String,
535    #[serde(default, skip_serializing_if = "Option::is_none")]
536    pub snapshot_id: Option<String>,
537    #[serde(default, skip_serializing_if = "Option::is_none")]
538    pub template_id: Option<String>,
539    #[serde(default, skip_serializing_if = "Option::is_none")]
540    pub template_version: Option<String>,
541    #[serde(default)]
542    pub profile: BTreeMap<String, String>,
543    /// Additional owners beyond the authenticated creator.
544    #[serde(default)]
545    pub owners: Vec<OwnerRef>,
546}
547
548#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
549#[serde(deny_unknown_fields, rename_all = "camelCase")]
550pub struct CreateSpaceRequest {
551    pub computer_id: String,
552    pub persistence: SpacePersistence,
553    #[serde(default, skip_serializing_if = "Option::is_none")]
554    pub source_snapshot_id: Option<String>,
555    /// Must be a subset of the parent Computer owners when non-empty.
556    #[serde(default)]
557    pub owners: Vec<OwnerRef>,
558}
559
560#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
561#[serde(deny_unknown_fields, rename_all = "camelCase")]
562pub struct CreateSandboxRequest {
563    pub provider: String,
564    pub placement: SandboxPlacement,
565    #[serde(default, skip_serializing_if = "Option::is_none")]
566    pub delegation_id: Option<String>,
567    pub ttl_ms: u64,
568    #[serde(default)]
569    pub mounts: Vec<SandboxMount>,
570    #[serde(default, skip_serializing_if = "Option::is_none")]
571    pub snapshot_id: Option<String>,
572    #[serde(default, skip_serializing_if = "Option::is_none")]
573    pub template_id: Option<String>,
574    #[serde(default, skip_serializing_if = "Option::is_none")]
575    pub template_version: Option<String>,
576    #[serde(default, skip_serializing_if = "Option::is_none")]
577    pub source_snapshot_id: Option<String>,
578    #[serde(default)]
579    pub profile: BTreeMap<String, String>,
580}
581
582/// Provider-facing request after product identifiers and access tickets have
583/// been resolved. These DTOs cannot be supplied by HTTP callers.
584#[derive(Debug, Clone, PartialEq, Eq)]
585pub struct ProviderComputerRequest {
586    pub template_external_id: Option<String>,
587    pub profile: BTreeMap<String, String>,
588}
589
590#[derive(Debug, Clone, PartialEq, Eq)]
591pub struct ProviderSpaceRequest {
592    pub persistence: SpacePersistence,
593    pub source_snapshot_external_id: Option<String>,
594}
595
596#[derive(Debug, Clone, PartialEq, Eq)]
597pub struct ProviderSandboxMount {
598    pub alias: String,
599    pub space_directory: String,
600    pub mode: SpaceAccessMode,
601    pub path_prefix: Option<String>,
602}
603
604#[derive(Debug, Clone, PartialEq, Eq)]
605pub enum ProviderSandboxPlacement {
606    Computer(String),
607    ProviderPool(String),
608}
609
610#[derive(Debug, Clone, PartialEq, Eq)]
611pub struct ProviderSandboxRequest {
612    pub placement: ProviderSandboxPlacement,
613    pub ttl_ms: u64,
614    pub mounts: Vec<ProviderSandboxMount>,
615    pub template_external_id: Option<String>,
616    pub source_snapshot_external_id: Option<String>,
617    pub profile: BTreeMap<String, String>,
618}
619
620#[derive(Debug, Clone, PartialEq, Eq)]
621pub enum ProviderSnapshotSource {
622    Computer(String),
623    Sandbox(String),
624    Space(String),
625}
626
627#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
628#[serde(deny_unknown_fields, rename_all = "camelCase")]
629pub struct ListEnvironmentResourcesQuery {
630    #[serde(default, skip_serializing_if = "Option::is_none")]
631    pub owner_kind: Option<OwnerKind>,
632    #[serde(default, skip_serializing_if = "Option::is_none")]
633    pub owner_id: Option<String>,
634    /// When set, resource owners must contain every listed owner (JSON array).
635    #[serde(default, skip_serializing_if = "Vec::is_empty")]
636    pub contains_all: Vec<OwnerRef>,
637}
638
639#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
640#[serde(deny_unknown_fields, rename_all = "camelCase")]
641pub struct RevokeSpaceGrantRequest {
642    pub expected_version: u64,
643}
644
645#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
646#[serde(deny_unknown_fields, rename_all = "camelCase")]
647pub struct CreateSpaceGrantRequest {
648    pub principal: PrincipalRef,
649    pub capabilities: BTreeSet<SpaceCapability>,
650    #[serde(default)]
651    pub path_prefixes: Vec<String>,
652    #[serde(default, skip_serializing_if = "Option::is_none")]
653    pub expires_at_ms: Option<u64>,
654}
655
656#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
657#[serde(deny_unknown_fields, rename_all = "camelCase")]
658pub struct OpenSpaceSessionRequest {
659    pub mode: SpaceAccessMode,
660    #[serde(default, skip_serializing_if = "Option::is_none")]
661    pub delegation_id: Option<String>,
662    pub ttl_ms: u64,
663}
664
665#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
666#[serde(deny_unknown_fields, rename_all = "camelCase")]
667pub struct IssueSpaceAccessTicketRequest {
668    pub session_id: String,
669    pub ttl_ms: u64,
670}
671
672#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
673#[serde(deny_unknown_fields, rename_all = "camelCase")]
674pub struct AcquireSpaceWriteLeaseRequest {
675    pub session_id: String,
676    pub ttl_ms: u64,
677}
678
679#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
680#[serde(deny_unknown_fields, rename_all = "camelCase")]
681pub struct ReleaseSpaceWriteLeaseRequest {
682    pub session_id: String,
683    pub fencing_token: u64,
684}
685
686#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
687#[serde(deny_unknown_fields, rename_all = "camelCase")]
688pub struct HeartbeatSpaceSessionRequest {
689    pub ttl_ms: u64,
690}
691
692#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
693#[serde(deny_unknown_fields, rename_all = "camelCase")]
694pub struct CreateDelegationRequest {
695    pub delegate_agent_id: String,
696    #[serde(default)]
697    pub computer_ids: BTreeSet<String>,
698    #[serde(default)]
699    pub space_ids: BTreeSet<String>,
700    pub capabilities: BTreeSet<SpaceCapability>,
701    #[serde(default)]
702    pub path_prefixes: Vec<String>,
703    pub purpose: String,
704    pub expires_at_ms: u64,
705}
706
707#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
708#[serde(deny_unknown_fields, rename_all = "camelCase")]
709pub struct CreateTemplateVersionRequest {
710    pub template_id: String,
711    pub version: String,
712    pub provider: String,
713    #[serde(default, skip_serializing_if = "Option::is_none")]
714    pub provider_template_id: Option<String>,
715    pub target: TemplateTarget,
716    #[serde(default, skip_serializing_if = "Option::is_none")]
717    pub base_snapshot_id: Option<String>,
718    #[serde(default)]
719    pub resource_profile: BTreeMap<String, String>,
720    #[serde(default)]
721    pub parameter_schema: BTreeMap<String, String>,
722}
723
724#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
725#[serde(deny_unknown_fields, rename_all = "camelCase")]
726pub struct CreateSpaceChangeSetRequest {
727    pub source_space_id: String,
728    pub target_space_id: String,
729    pub created_by_session_id: String,
730}
731
732#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
733#[serde(deny_unknown_fields, rename_all = "camelCase")]
734pub struct ApplySpaceChangeSetRequest {
735    pub access_ticket_token: String,
736    pub fencing_token: u64,
737    pub expected_revision: u64,
738}
739
740#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
741#[serde(deny_unknown_fields, rename_all = "camelCase")]
742pub struct WriteSpaceFileRequest {
743    pub access_ticket_token: String,
744    pub fencing_token: u64,
745    pub expected_revision: u64,
746    #[serde(with = "base64_bytes")]
747    pub content: Vec<u8>,
748}
749
750#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
751#[serde(deny_unknown_fields, rename_all = "camelCase")]
752pub struct ReadSpaceFileRequest {
753    pub access_ticket_token: String,
754    pub path: String,
755}
756
757#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
758#[serde(deny_unknown_fields, rename_all = "camelCase")]
759pub struct ProviderFileContent {
760    pub bytes: Vec<u8>,
761    #[serde(default, skip_serializing_if = "Option::is_none")]
762    pub provider_revision: Option<String>,
763}
764
765#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
766#[serde(deny_unknown_fields, rename_all = "camelCase")]
767pub struct ProviderWriteResult {
768    #[serde(default, skip_serializing_if = "Option::is_none")]
769    pub provider_revision: Option<String>,
770}