Skip to main content

heddle_object_model/object/
facet_kind.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Typed history-graph facet kinds (ADR 0051).
3//!
4//! A facet's laws are not inferred from a path, thread name, or object-store
5//! reuse. Git Projection, checkout, and land may only act on roots that can
6//! produce [`SourceHistoryLaws`].
7
8use std::fmt;
9
10/// Durable facet a repository fact belongs to.
11///
12/// Closed on purpose: a newly added variant is a compile failure at every
13/// `match` until its checkout, land, projection, sync, and purge laws are
14/// written down.
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
16pub enum FacetKind {
17    /// Immutable source states and trees. The only facet Git Projection,
18    /// checkout, and land may select.
19    SourceHistory,
20    /// Encrypted runtime profiles (env/secret store). Never a checkout,
21    /// land, or Git Projection target.
22    ConfidentialRuntime,
23    /// Collaboration operations (discussions, context). Adjacent metadata.
24    Collaboration,
25    /// Agent timeline operations. Adjacent execution provenance.
26    AgentTimeline,
27}
28
29/// Proof that a root is Source History and may be checked out, landed, or
30/// visited by Git Projection.
31///
32/// The only way to obtain this token is [`FacetKind::source_history_laws`].
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub struct SourceHistoryLaws {
35    _private: (),
36}
37
38impl FacetKind {
39    /// Every defined facet. Tests use this to prove exclusion is closed.
40    pub const ALL: [Self; 4] = [
41        Self::SourceHistory,
42        Self::ConfidentialRuntime,
43        Self::Collaboration,
44        Self::AgentTimeline,
45    ];
46
47    pub const fn as_str(self) -> &'static str {
48        match self {
49            Self::SourceHistory => "source-history",
50            Self::ConfidentialRuntime => "confidential-runtime",
51            Self::Collaboration => "collaboration",
52            Self::AgentTimeline => "agent-timeline",
53        }
54    }
55
56    /// Source History laws, or `None` when this facet cannot be checked out,
57    /// landed, or selected by Git Projection.
58    pub const fn source_history_laws(self) -> Option<SourceHistoryLaws> {
59        match self {
60            Self::SourceHistory => Some(SourceHistoryLaws { _private: () }),
61            Self::ConfidentialRuntime | Self::Collaboration | Self::AgentTimeline => None,
62        }
63    }
64
65    pub const fn may_checkout(self) -> bool {
66        self.source_history_laws().is_some()
67    }
68
69    pub const fn may_land(self) -> bool {
70        self.source_history_laws().is_some()
71    }
72
73    pub const fn git_projection_visits(self) -> bool {
74        self.source_history_laws().is_some()
75    }
76
77    /// Refuse worktree materialization unless this is Source History.
78    pub const fn require_worktree_materialization(self) -> Result<SourceHistoryLaws, Self> {
79        match self.source_history_laws() {
80            Some(laws) => Ok(laws),
81            None => Err(self),
82        }
83    }
84
85    /// Refuse land/merge-into-HEAD unless this is Source History.
86    pub const fn require_land(self) -> Result<SourceHistoryLaws, Self> {
87        match self.source_history_laws() {
88            Some(laws) => Ok(laws),
89            None => Err(self),
90        }
91    }
92
93    /// Refuse Git Projection unless this is Source History.
94    pub const fn require_git_projection(self) -> Result<SourceHistoryLaws, Self> {
95        match self.source_history_laws() {
96            Some(laws) => Ok(laws),
97            None => Err(self),
98        }
99    }
100}
101
102impl fmt::Display for FacetKind {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        f.write_str(self.as_str())
105    }
106}
107
108impl SourceHistoryLaws {
109    pub const fn may_checkout(self) -> bool {
110        true
111    }
112
113    pub const fn may_land(self) -> bool {
114        true
115    }
116
117    pub const fn git_projection_visits(self) -> bool {
118        true
119    }
120}
121
122const _: () = assert!(FacetKind::SourceHistory.git_projection_visits());
123const _: () = assert!(FacetKind::SourceHistory.may_checkout());
124const _: () = assert!(FacetKind::SourceHistory.may_land());
125const _: () = assert!(!FacetKind::ConfidentialRuntime.git_projection_visits());
126const _: () = assert!(!FacetKind::ConfidentialRuntime.may_checkout());
127const _: () = assert!(!FacetKind::ConfidentialRuntime.may_land());
128const _: () = assert!(!FacetKind::Collaboration.git_projection_visits());
129const _: () = assert!(!FacetKind::AgentTimeline.git_projection_visits());
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn only_source_history_has_projection_checkout_and_land_laws() {
137        for kind in FacetKind::ALL {
138            let allowed = kind == FacetKind::SourceHistory;
139            assert_eq!(kind.may_checkout(), allowed, "{kind}");
140            assert_eq!(kind.may_land(), allowed, "{kind}");
141            assert_eq!(kind.git_projection_visits(), allowed, "{kind}");
142            assert_eq!(kind.source_history_laws().is_some(), allowed, "{kind}");
143        }
144    }
145
146    #[test]
147    fn confidential_runtime_is_refused_at_every_source_history_chokepoint() {
148        let kind = FacetKind::ConfidentialRuntime;
149        assert_eq!(kind.require_worktree_materialization(), Err(kind));
150        assert_eq!(kind.require_land(), Err(kind));
151        assert_eq!(kind.require_git_projection(), Err(kind));
152    }
153
154    #[test]
155    fn source_history_laws_are_the_only_projectable_token() {
156        let laws = FacetKind::SourceHistory
157            .source_history_laws()
158            .expect("source history yields laws");
159        assert!(laws.may_checkout());
160        assert!(laws.may_land());
161        assert!(laws.git_projection_visits());
162    }
163}