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