Skip to main content

systemprompt_cli/
descriptor.rs

1//! Per-command bootstrap descriptor: which of profile/secrets/paths a command
2//! needs.
3//!
4//! Copyright (c) systemprompt.io — Business Source License 1.1.
5//! See <https://systemprompt.io> for licensing details.
6
7#[derive(Debug, Clone, Copy, Default)]
8pub struct CommandDescriptor {
9    flags: u8,
10}
11
12impl CommandDescriptor {
13    const FLAG_PROFILE: u8 = 0b0000_0001;
14    const FLAG_SECRETS: u8 = 0b0000_0010;
15    const FLAG_PATHS: u8 = 0b0000_0100;
16    const FLAG_DATABASE: u8 = 0b0000_1000;
17    const FLAG_REMOTE_ELIGIBLE: u8 = 0b0001_0000;
18    const FLAG_SKIP_VALIDATION: u8 = 0b0010_0000;
19    const FLAG_READ_ONLY: u8 = 0b0100_0000;
20
21    pub const NONE: Self = Self { flags: 0 };
22
23    pub const PROFILE_ONLY: Self = Self {
24        flags: Self::FLAG_PROFILE,
25    };
26
27    pub const PROFILE_AND_SECRETS: Self = Self {
28        flags: Self::FLAG_PROFILE | Self::FLAG_SECRETS,
29    };
30
31    pub const PROFILE_SECRETS_AND_PATHS: Self = Self {
32        flags: Self::FLAG_PROFILE | Self::FLAG_SECRETS | Self::FLAG_PATHS,
33    };
34
35    pub const FULL: Self = Self {
36        flags: Self::FLAG_PROFILE
37            | Self::FLAG_SECRETS
38            | Self::FLAG_PATHS
39            | Self::FLAG_DATABASE
40            | Self::FLAG_REMOTE_ELIGIBLE,
41    };
42
43    pub const fn profile(&self) -> bool {
44        self.flags & Self::FLAG_PROFILE != 0
45    }
46
47    pub const fn secrets(&self) -> bool {
48        self.flags & Self::FLAG_SECRETS != 0
49    }
50
51    pub const fn paths(&self) -> bool {
52        self.flags & Self::FLAG_PATHS != 0
53    }
54
55    pub const fn database(&self) -> bool {
56        self.flags & Self::FLAG_DATABASE != 0
57    }
58
59    // Why: read-only and mutating were one boolean; refusing a read because no
60    // tenant session exists fails the caller for a reason they cannot act on.
61    pub const fn routing_class(&self) -> RoutingClass {
62        if self.flags & Self::FLAG_REMOTE_ELIGIBLE == 0 {
63            RoutingClass::LocalOnly
64        } else if self.flags & Self::FLAG_READ_ONLY != 0 {
65            RoutingClass::ReadOnly
66        } else {
67            RoutingClass::Mutating
68        }
69    }
70
71    pub const fn skip_validation(&self) -> bool {
72        self.flags & Self::FLAG_SKIP_VALIDATION != 0
73    }
74
75    pub const fn with_remote_eligible(self) -> Self {
76        Self {
77            flags: self.flags | Self::FLAG_REMOTE_ELIGIBLE,
78        }
79    }
80
81    pub const fn with_read_only(self) -> Self {
82        Self {
83            flags: self.flags | Self::FLAG_READ_ONLY,
84        }
85    }
86
87    pub const fn with_skip_validation(self) -> Self {
88        Self {
89            flags: self.flags | Self::FLAG_SKIP_VALIDATION,
90        }
91    }
92}
93
94/// What a command is allowed to do when the active profile is a cloud profile.
95///
96/// `LocalOnly` is never routed remotely and runs against whatever the profile
97/// resolves; `ReadOnly` prefers remote when a session is available and falls
98/// back to local with a warning; `Mutating` must route remotely or fail loudly.
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub enum RoutingClass {
101    LocalOnly,
102    ReadOnly,
103    Mutating,
104}
105
106pub trait DescribeCommand {
107    fn descriptor(&self) -> CommandDescriptor;
108}