systemprompt_models/wire/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)]
28#[serde(rename_all = "kebab-case")]
29pub enum NativeMarker {
30 ClaudeMetadataUserId,
31 CodexTurnMetadata,
32 OpencodeSessionJson,
33}
34
35impl ClientAttestation {
36 pub const ALL: [Self; 8] = [
37 Self::HostToken,
38 Self::BridgeSecret,
39 Self::Declared,
40 Self::NativeMarker,
41 Self::UserAgent,
42 Self::None,
43 Self::Internal,
44 Self::Unknown,
45 ];
46
47 #[must_use]
48 pub const fn as_str(self) -> &'static str {
49 match self {
50 Self::HostToken => "host-token",
51 Self::BridgeSecret => "bridge-secret",
52 Self::Declared => "declared",
53 Self::NativeMarker => "native-marker",
54 Self::UserAgent => "user-agent",
55 Self::None => "none",
56 Self::Internal => "internal",
57 Self::Unknown => "unknown",
58 }
59 }
60
61 #[must_use]
62 pub const fn label(self) -> &'static str {
63 match self {
64 Self::HostToken => "Attested by bridge host token",
65 Self::BridgeSecret => "Via bridge, host unverified",
66 Self::Declared => "Declared by client",
67 Self::NativeMarker => "Native marker only",
68 Self::UserAgent => "User-Agent only, unverified",
69 Self::None => "No client evidence",
70 Self::Internal => "Internal",
71 Self::Unknown => "Unknown",
72 }
73 }
74
75 pub fn parse(value: &str) -> Result<Self, OriginParseError> {
76 Self::ALL
77 .into_iter()
78 .find(|tier| tier.as_str() == value)
79 .ok_or_else(|| OriginParseError::Attestation(value.to_owned()))
80 }
81
82 #[must_use]
83 pub const fn is_bridge_channel(self) -> bool {
84 matches!(self, Self::HostToken | Self::BridgeSecret)
85 }
86}
87
88impl NativeMarker {
89 pub const ALL: [Self; 3] = [
90 Self::ClaudeMetadataUserId,
91 Self::CodexTurnMetadata,
92 Self::OpencodeSessionJson,
93 ];
94
95 #[must_use]
96 pub const fn as_str(self) -> &'static str {
97 match self {
98 Self::ClaudeMetadataUserId => "claude-metadata-user-id",
99 Self::CodexTurnMetadata => "codex-turn-metadata",
100 Self::OpencodeSessionJson => "opencode-session-json",
101 }
102 }
103
104 pub fn parse(value: &str) -> Result<Self, OriginParseError> {
105 Self::ALL
106 .into_iter()
107 .find(|marker| marker.as_str() == value)
108 .ok_or_else(|| OriginParseError::NativeMarker(value.to_owned()))
109 }
110
111 #[must_use]
112 pub const fn client(self) -> super::ClientKind {
113 match self {
114 Self::ClaudeMetadataUserId => super::ClientKind::ClaudeCode,
118 Self::CodexTurnMetadata => super::ClientKind::Codex,
119 Self::OpencodeSessionJson => super::ClientKind::OpenCode,
120 }
121 }
122}