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