Skip to main content

khive_types/
pack.rs

1//! Pack trait — the declarative composition unit for khive (ADR-025).
2//!
3//! A pack declares vocabulary (note kinds, entity kinds), verbs, and edge
4//! endpoint rules. This is purely static metadata — no I/O, no async.
5//! Runtime dispatch lives in `khive-runtime` (`PackRuntime` trait +
6//! `VerbRegistry`).
7//!
8//! This trait lives in khive-types (no_std, zero deps) so downstream crates
9//! can reference pack metadata without pulling in the full runtime.
10
11use crate::edge::EdgeRelation;
12
13/// Verb metadata for discovery and documentation.
14#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct VerbDef {
16    pub name: &'static str,
17    pub description: &'static str,
18}
19
20/// Match spec for one end of an [`EdgeEndpointRule`] (ADR-031).
21///
22/// Identifies a substrate + kind pair that the rule applies to. Note that
23/// `kind` strings refer to the pack-declared note kinds / entity kinds — not
24/// the closed [`EdgeRelation`] set, which is universal.
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum EndpointKind {
27    /// A note whose `kind` field equals the given string (e.g. `"task"`).
28    NoteOfKind(&'static str),
29    /// An entity whose `kind` field equals the given string (e.g. `"concept"`).
30    EntityOfKind(&'static str),
31}
32
33/// A pack-declared endpoint rule for a specific edge relation (ADR-031).
34///
35/// Rules are **additive**: they extend the set of allowed
36/// `(source, relation, target)` triples beyond the ADR-002 base contract.
37/// Packs cannot tighten the base rules — only broaden them. The closed
38/// [`EdgeRelation`] taxonomy itself is not extended; only the endpoint
39/// contract per relation is.
40///
41/// Example — GTD pack allows `depends_on` between task notes:
42///
43/// ```ignore
44/// EdgeEndpointRule {
45///     relation: EdgeRelation::DependsOn,
46///     source: EndpointKind::NoteOfKind("task"),
47///     target: EndpointKind::NoteOfKind("task"),
48/// }
49/// ```
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub struct EdgeEndpointRule {
52    pub relation: EdgeRelation,
53    pub source: EndpointKind,
54    pub target: EndpointKind,
55}
56
57/// A composable module that contributes vocabulary, verbs, and edge endpoint
58/// rules to the khive runtime.
59///
60/// Packs declare what entity kinds, note kinds, and verbs they introduce, and
61/// optionally extend the per-relation endpoint contract via [`EDGE_RULES`].
62/// The runtime merges vocabularies from all loaded packs and rejects
63/// unregistered kinds at the service boundary.
64///
65/// The closed [`EdgeRelation`] enum (ADR-021) is not extensible — only its
66/// per-relation endpoint contract is (ADR-031).
67///
68/// [`EDGE_RULES`]: Pack::EDGE_RULES
69pub trait Pack {
70    /// Short identifier for this pack (e.g. "kg", "tasks").
71    const NAME: &'static str;
72
73    /// Note kinds this pack contributes to the runtime vocabulary.
74    const NOTE_KINDS: &'static [&'static str];
75
76    /// Entity kinds this pack contributes to the runtime vocabulary.
77    const ENTITY_KINDS: &'static [&'static str];
78
79    /// Verbs this pack handles. The runtime routes verb calls to the pack
80    /// that declares them.
81    const VERBS: &'static [VerbDef];
82
83    /// Additional edge endpoint rules this pack contributes (ADR-031).
84    ///
85    /// Defaults to empty — packs that introduce no new endpoint pairs (or
86    /// only rely on the ADR-002 base contract) can ignore this.
87    const EDGE_RULES: &'static [EdgeEndpointRule] = &[];
88
89    /// Other pack names whose vocabulary this pack references (ADR-037).
90    ///
91    /// The runtime checks that every name in `REQUIRES` appears in the
92    /// loaded pack set before any pack is registered. Defaults to empty
93    /// so existing packs compile without changes.
94    const REQUIRES: &'static [&'static str] = &[];
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    struct TestPack;
102
103    impl Pack for TestPack {
104        const NAME: &'static str = "test";
105        const NOTE_KINDS: &'static [&'static str] = &["memo"];
106        const ENTITY_KINDS: &'static [&'static str] = &["widget"];
107        const VERBS: &'static [VerbDef] = &[VerbDef {
108            name: "do_thing",
109            description: "does a thing",
110        }];
111    }
112
113    #[test]
114    fn pack_trait_compiles() {
115        assert_eq!(TestPack::NAME, "test");
116        assert_eq!(TestPack::NOTE_KINDS, &["memo"]);
117        assert_eq!(TestPack::ENTITY_KINDS, &["widget"]);
118        assert_eq!(TestPack::VERBS.len(), 1);
119        assert_eq!(TestPack::VERBS[0].name, "do_thing");
120    }
121}