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}