Skip to main content

type_bridge_contract/
managed_scope.rs

1//! Durable managed-scope identities and frozen profile fingerprints.
2
3use std::fmt;
4
5use serde::de::Error as _;
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7
8use crate::codec::to_canonical_json;
9use crate::diagnostic::{Diagnostic, DiagnosticCategory};
10use crate::fingerprint::{
11    CanonicalizationVersion, Fingerprint, FingerprintDomain, SemanticProfileId,
12};
13use crate::limits::MAX_CANONICAL_STRING_BYTES;
14use crate::semantic_profile::{InterfaceKind, SemanticProfile};
15
16/// The only managed-scope profile supported by the first workspace format.
17pub const EXCLUSIVE_MANAGED_SCOPE_PROFILE_ID: &str = "typebridge.managed-scope/exclusive/v1";
18/// Fingerprint domain for frozen managed-scope profile definitions.
19pub const MANAGED_SCOPE_PROFILE_FINGERPRINT_DOMAIN: &str =
20    "typebridge.schema.managed-scope-profile";
21/// Canonicalization contract for frozen managed-scope profile definitions.
22pub const MANAGED_SCOPE_PROFILE_CANONICALIZATION: &str = "typebridge.managed-scope-profile/v1";
23/// Fingerprint domain for frozen semantic-profile definitions.
24pub const SEMANTIC_PROFILE_FINGERPRINT_DOMAIN: &str = "typebridge.schema.semantic-profile";
25/// Canonicalization contract for frozen semantic-profile definitions.
26pub const SEMANTIC_PROFILE_CANONICALIZATION: &str = "typebridge.semantic-profile/v1";
27
28/// A durable deployment-managed schema scope identity.
29#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
30pub struct ManagedScopeId(String);
31
32impl ManagedScopeId {
33    /// Validate and construct a durable managed-scope identity.
34    pub fn new(value: impl Into<String>) -> Result<Self, Diagnostic> {
35        let value = value.into();
36        if value.is_empty() || value.len() > MAX_CANONICAL_STRING_BYTES {
37            return Err(Diagnostic::stable(
38                DiagnosticCategory::InvalidContract,
39                "malformed_managed_scope_id",
40                "managed scope ID is empty or exceeds the canonical string limit",
41            )
42            .with_detail(
43                "maximum_bytes",
44                i64::try_from(MAX_CANONICAL_STRING_BYTES).unwrap_or(i64::MAX),
45            ));
46        }
47        Ok(Self(value))
48    }
49
50    /// Return the canonical identity spelling.
51    pub fn as_str(&self) -> &str {
52        &self.0
53    }
54}
55
56impl fmt::Display for ManagedScopeId {
57    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
58        formatter.write_str(self.as_str())
59    }
60}
61
62impl Serialize for ManagedScopeId {
63    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
64    where
65        S: Serializer,
66    {
67        serializer.serialize_str(self.as_str())
68    }
69}
70
71impl<'de> Deserialize<'de> for ManagedScopeId {
72    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
73    where
74        D: Deserializer<'de>,
75    {
76        Self::new(String::deserialize(deserializer)?).map_err(D::Error::custom)
77    }
78}
79
80/// A registry-owned managed-scope profile identity.
81#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
82pub struct ManagedScopeProfileId(String);
83
84impl ManagedScopeProfileId {
85    /// Return the frozen exclusive-profile identity.
86    #[must_use]
87    pub fn exclusive() -> Self {
88        Self(EXCLUSIVE_MANAGED_SCOPE_PROFILE_ID.to_owned())
89    }
90
91    /// Validate a managed-scope profile identity against the closed registry.
92    pub fn new(value: impl Into<String>) -> Result<Self, Diagnostic> {
93        let value = value.into();
94        if value != EXCLUSIVE_MANAGED_SCOPE_PROFILE_ID {
95            return Err(Diagnostic::stable(
96                DiagnosticCategory::UnsupportedCapability,
97                "unsupported_managed_scope_profile",
98                "managed scope profile is not present in the frozen registry",
99            ));
100        }
101        Ok(Self(value))
102    }
103
104    /// Return the canonical profile identity spelling.
105    pub fn as_str(&self) -> &str {
106        &self.0
107    }
108}
109
110impl fmt::Display for ManagedScopeProfileId {
111    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
112        formatter.write_str(self.as_str())
113    }
114}
115
116impl Serialize for ManagedScopeProfileId {
117    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
118    where
119        S: Serializer,
120    {
121        serializer.serialize_str(self.as_str())
122    }
123}
124
125impl<'de> Deserialize<'de> for ManagedScopeProfileId {
126    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
127    where
128        D: Deserializer<'de>,
129    {
130        Self::new(String::deserialize(deserializer)?).map_err(D::Error::custom)
131    }
132}
133
134#[derive(Serialize)]
135struct ExclusiveManagedScopeProfileView {
136    internal_facts: &'static str,
137    non_internal_facts: &'static str,
138    profile_id: &'static str,
139}
140
141/// Return the byte-exact frozen exclusive-profile definition.
142pub fn exclusive_managed_scope_profile_bytes() -> Result<Vec<u8>, Diagnostic> {
143    to_canonical_json(&ExclusiveManagedScopeProfileView {
144        internal_facts: "excluded",
145        non_internal_facts: "managed",
146        profile_id: EXCLUSIVE_MANAGED_SCOPE_PROFILE_ID,
147    })
148}
149
150/// Content fingerprint of one frozen managed-scope profile definition.
151#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
152#[serde(transparent)]
153pub struct ManagedScopeProfileFingerprint(Fingerprint);
154
155impl ManagedScopeProfileFingerprint {
156    /// Compute the fingerprint of the frozen exclusive profile.
157    pub fn exclusive() -> Result<Self, Diagnostic> {
158        Ok(Self(Fingerprint::compute(
159            FingerprintDomain::new(MANAGED_SCOPE_PROFILE_FINGERPRINT_DOMAIN)?,
160            CanonicalizationVersion::new(MANAGED_SCOPE_PROFILE_CANONICALIZATION)?,
161            None,
162            &exclusive_managed_scope_profile_bytes()?,
163        )))
164    }
165
166    /// Return the generic domain-separated fingerprint.
167    pub const fn as_fingerprint(&self) -> &Fingerprint {
168        &self.0
169    }
170}
171
172/// A registry-validated managed-scope profile identity and content fingerprint.
173#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
174pub struct ManagedScopeProfileBinding {
175    fingerprint: ManagedScopeProfileFingerprint,
176    id: ManagedScopeProfileId,
177}
178
179impl ManagedScopeProfileBinding {
180    /// Resolve the frozen exclusive managed-scope profile.
181    pub fn exclusive() -> Result<Self, Diagnostic> {
182        Ok(Self {
183            fingerprint: ManagedScopeProfileFingerprint::exclusive()?,
184            id: ManagedScopeProfileId::exclusive(),
185        })
186    }
187
188    /// Return the profile identity.
189    pub const fn id(&self) -> &ManagedScopeProfileId {
190        &self.id
191    }
192
193    /// Return the profile content fingerprint.
194    pub const fn fingerprint(&self) -> &ManagedScopeProfileFingerprint {
195        &self.fingerprint
196    }
197}
198
199/// Durable identity binding attached explicitly to one managed fact selection.
200#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
201pub struct ManagedScopeBinding {
202    id: ManagedScopeId,
203    profile: ManagedScopeProfileBinding,
204}
205
206impl ManagedScopeBinding {
207    /// Bind a durable scope identity to the frozen exclusive profile.
208    pub fn exclusive(id: ManagedScopeId) -> Result<Self, Diagnostic> {
209        Ok(Self {
210            id,
211            profile: ManagedScopeProfileBinding::exclusive()?,
212        })
213    }
214
215    /// Return the durable scope identity.
216    pub const fn id(&self) -> &ManagedScopeId {
217        &self.id
218    }
219
220    /// Return the frozen scope-profile binding.
221    pub const fn profile(&self) -> &ManagedScopeProfileBinding {
222        &self.profile
223    }
224}
225
226#[derive(Serialize)]
227struct SemanticProfileView<'a> {
228    id: &'a SemanticProfileId,
229    key_owns_default: crate::value::Cardinality,
230    owns_default: crate::value::Cardinality,
231    plays_default: crate::value::Cardinality,
232    relates_default: crate::value::Cardinality,
233}
234
235/// Return byte-exact content for one frozen semantic profile.
236pub fn semantic_profile_canonical_bytes(profile: &SemanticProfile) -> Result<Vec<u8>, Diagnostic> {
237    to_canonical_json(&SemanticProfileView {
238        id: profile.id(),
239        key_owns_default: profile.effective_cardinality(InterfaceKind::Owns, None, true),
240        owns_default: profile.default_cardinality(InterfaceKind::Owns),
241        plays_default: profile.default_cardinality(InterfaceKind::Plays),
242        relates_default: profile.default_cardinality(InterfaceKind::Relates),
243    })
244}
245
246/// Content fingerprint of one frozen semantic-profile definition.
247#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
248#[serde(transparent)]
249pub struct SemanticProfileFingerprint(Fingerprint);
250
251impl SemanticProfileFingerprint {
252    /// Compute a content fingerprint from a resolved frozen profile.
253    pub fn compute(profile: &SemanticProfile) -> Result<Self, Diagnostic> {
254        Ok(Self(Fingerprint::compute(
255            FingerprintDomain::new(SEMANTIC_PROFILE_FINGERPRINT_DOMAIN)?,
256            CanonicalizationVersion::new(SEMANTIC_PROFILE_CANONICALIZATION)?,
257            None,
258            &semantic_profile_canonical_bytes(profile)?,
259        )))
260    }
261
262    /// Return the generic domain-separated fingerprint.
263    pub const fn as_fingerprint(&self) -> &Fingerprint {
264        &self.0
265    }
266}
267
268/// A registry-resolved semantic-profile identity and exact content fingerprint.
269#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
270pub struct SemanticProfileBinding {
271    fingerprint: SemanticProfileFingerprint,
272    id: SemanticProfileId,
273}
274
275impl SemanticProfileBinding {
276    /// Resolve one supported semantic profile and bind its frozen content.
277    pub fn resolve(id: SemanticProfileId) -> Result<Self, Diagnostic> {
278        let profile = SemanticProfile::resolve(&id)?;
279        Ok(Self {
280            fingerprint: profile.content_fingerprint()?,
281            id,
282        })
283    }
284
285    /// Resolve the frozen TypeDB 3.12.1 semantic profile.
286    pub fn typedb_3_12_1() -> Result<Self, Diagnostic> {
287        Self::resolve(SemanticProfileId::new("typedb-3.12.1/v1")?)
288    }
289
290    /// Return the exact semantic-profile identity.
291    pub const fn id(&self) -> &SemanticProfileId {
292        &self.id
293    }
294
295    /// Return the registry-owned semantic-profile content fingerprint.
296    pub const fn fingerprint(&self) -> &SemanticProfileFingerprint {
297        &self.fingerprint
298    }
299}
300
301impl SemanticProfile {
302    /// Compute the content fingerprint of this frozen profile definition.
303    pub fn content_fingerprint(&self) -> Result<SemanticProfileFingerprint, Diagnostic> {
304        SemanticProfileFingerprint::compute(self)
305    }
306}