systemprompt_models/origin/
attestation.rs1use serde::{Deserialize, Serialize};
8
9use super::OriginParseError;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
14#[serde(rename_all = "kebab-case")]
15pub enum ClientAttestation {
16 HostToken,
17 BridgeSecret,
18 Declared,
19 NativeMarker,
20 UserAgent,
21 None,
22 Internal,
23 Unknown,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
39#[serde(rename_all = "kebab-case")]
40pub enum NativeMarker {
41 ClaudeDesktopEntrypoint,
42 ClaudeCliEntrypoint,
43 ClaudeMetadataUserId,
44 ClaudeMetadataJson,
45 CodexTurnMetadata,
46}
47
48impl ClientAttestation {
49 pub const ALL: [Self; 8] = [
50 Self::HostToken,
51 Self::BridgeSecret,
52 Self::Declared,
53 Self::NativeMarker,
54 Self::UserAgent,
55 Self::None,
56 Self::Internal,
57 Self::Unknown,
58 ];
59
60 #[must_use]
61 pub const fn as_str(self) -> &'static str {
62 match self {
63 Self::HostToken => "host-token",
64 Self::BridgeSecret => "bridge-secret",
65 Self::Declared => "declared",
66 Self::NativeMarker => "native-marker",
67 Self::UserAgent => "user-agent",
68 Self::None => "none",
69 Self::Internal => "internal",
70 Self::Unknown => "unknown",
71 }
72 }
73
74 #[must_use]
75 pub const fn label(self) -> &'static str {
76 match self {
77 Self::HostToken => "Attested by bridge host token",
78 Self::BridgeSecret => "Via bridge, host unverified",
79 Self::Declared => "Declared by client",
80 Self::NativeMarker => "Native marker only",
81 Self::UserAgent => "User-Agent only, unverified",
82 Self::None => "No client evidence",
83 Self::Internal => "Internal",
84 Self::Unknown => "Unknown",
85 }
86 }
87
88 pub fn parse(value: &str) -> Result<Self, OriginParseError> {
89 Self::ALL
90 .into_iter()
91 .find(|tier| tier.as_str() == value)
92 .ok_or_else(|| OriginParseError::Attestation(value.to_owned()))
93 }
94
95 #[must_use]
96 pub const fn is_bridge_channel(self) -> bool {
97 matches!(self, Self::HostToken | Self::BridgeSecret)
98 }
99}
100
101impl NativeMarker {
102 pub const ALL: [Self; 5] = [
103 Self::ClaudeDesktopEntrypoint,
104 Self::ClaudeCliEntrypoint,
105 Self::ClaudeMetadataUserId,
106 Self::ClaudeMetadataJson,
107 Self::CodexTurnMetadata,
108 ];
109
110 #[must_use]
111 pub const fn as_str(self) -> &'static str {
112 match self {
113 Self::ClaudeDesktopEntrypoint => "claude-desktop-entrypoint",
114 Self::ClaudeCliEntrypoint => "claude-cli-entrypoint",
115 Self::ClaudeMetadataUserId => "claude-metadata-user-id",
116 Self::ClaudeMetadataJson => "claude-metadata-json",
117 Self::CodexTurnMetadata => "codex-turn-metadata",
118 }
119 }
120
121 pub fn parse(value: &str) -> Result<Self, OriginParseError> {
122 Self::ALL
123 .into_iter()
124 .find(|marker| marker.as_str() == value)
125 .ok_or_else(|| OriginParseError::NativeMarker(value.to_owned()))
126 }
127
128 #[must_use]
129 pub const fn client(self) -> super::ClientKind {
130 match self {
131 Self::ClaudeDesktopEntrypoint => super::ClientKind::ClaudeDesktop,
132 Self::ClaudeCliEntrypoint | Self::ClaudeMetadataUserId | Self::ClaudeMetadataJson => {
136 super::ClientKind::ClaudeCode
137 },
138 Self::CodexTurnMetadata => super::ClientKind::Codex,
139 }
140 }
141}