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