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