Skip to main content

newt_core/
kit.rs

1//! The **component registry** — the model support kit's catalog of parts.
2//!
3//! `docs/design/model-support-kit.md`: *the kit* is the catalog of composable
4//! support parts (a profile *assembles* a subset of them). This grows the flat
5//! [`KNOWN_TECHNIQUES`](crate::config::KNOWN_TECHNIQUES) string list into a typed
6//! registry: each part carries the four contract fields the kit needs to compose
7//! parts honestly — its **axis**, its **kind** (where it mounts), what it
8//! **presupposes**, and its **tier** (does it run headless).
9//!
10//! This PR is internal — it changes no behavior. It is the substrate the bundle +
11//! loadout layers (and `Config::validate`'s presupposes check) build on.
12
13/// Where a component mounts in the harness loop — tells a driver how to run it.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum MountKind {
16    /// A system-prompt provider, mounted pre-loop (the `MemoryProvider` seam).
17    Provider,
18    /// A per-turn post-hook (runs after each turn).
19    PerTurn,
20    /// A loop-altering technique (re-enters/repeats the turn).
21    Loop,
22    /// A turn-reshaping mode (e.g. plan → approve → execute).
23    Mode,
24    /// A pre-send request-knob patch (e.g. `num_ctx`, `reasoning_effort`).
25    RequestKnobs,
26}
27
28/// Which axis of support a component serves (`docs/design/model-support-kit.md`).
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum Axis {
31    /// How the model thinks (`effort`, `think`).
32    Reasoning,
33    /// How work is decomposed (`plan`).
34    Structure,
35    /// What the model knows (`knowledge_base`).
36    Grounding,
37    /// Checking & fixing output (`verify_gate`, `review`, `retry`).
38    GatingRepair,
39}
40
41/// Whether a component runs in the headless flight tier (`wyvern`, no TUI) or needs
42/// the interactive surface — the amphibious split made a *checked* property.
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum Tier {
45    /// Runs anywhere, including headless `wyvern`.
46    Headless,
47    /// Needs the interactive TUI; skipped (not errored) when headless.
48    TuiOnly,
49}
50
51/// One row in the [`COMPONENT_REGISTRY`] — a support part and its contract.
52#[derive(Debug, Clone, Copy)]
53pub struct RegistryEntry {
54    /// The part's id — exactly the string a profile lists in `techniques`.
55    pub id: &'static str,
56    /// Where it mounts.
57    pub kind: MountKind,
58    /// Which axis it serves.
59    pub axis: Axis,
60    /// Parts that must also be enabled for this one to be valid (a profile listing
61    /// this part without all of these is rejected by `Config::validate`).
62    pub presupposes: &'static [&'static str],
63    /// Whether it runs headless.
64    pub tier: Tier,
65}
66
67/// The catalog of parts (the kit). The `id`s are exactly
68/// [`KNOWN_TECHNIQUES`](crate::config::KNOWN_TECHNIQUES) — pinned by a test so the
69/// two cannot drift.
70pub const COMPONENT_REGISTRY: &[RegistryEntry] = &[
71    RegistryEntry {
72        id: "knowledge_base", // R1 — inject the authoritative import surface (#74)
73        kind: MountKind::Provider,
74        axis: Axis::Grounding,
75        presupposes: &[],
76        tier: Tier::Headless,
77    },
78    RegistryEntry {
79        id: "verify_gate", // R2 — flag/revert files with fabricated imports (#73)
80        kind: MountKind::PerTurn,
81        axis: Axis::GatingRepair,
82        presupposes: &[],
83        tier: Tier::Headless,
84    },
85    RegistryEntry {
86        id: "retry", // the revert-retry loop — the gate's action arm
87        kind: MountKind::Loop,
88        axis: Axis::GatingRepair,
89        // retry is verify_gate's action arm: a profile that reverts-on-fabrication
90        // declares it is gating. (Operationally retry runs its own gate, so this is a
91        // hygiene requirement, not a runtime dependency — see model-support-kit.md.)
92        presupposes: &["verify_gate"],
93        tier: Tier::Headless,
94    },
95];
96
97/// Look up a component by its id, or `None` if unknown.
98#[must_use]
99pub fn component(id: &str) -> Option<&'static RegistryEntry> {
100    COMPONENT_REGISTRY.iter().find(|e| e.id == id)
101}
102
103/// Whether `id` names a registered component.
104#[must_use]
105pub fn is_known(id: &str) -> bool {
106    component(id).is_some()
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use crate::config::KNOWN_TECHNIQUES;
113
114    #[test]
115    fn registry_ids_match_known_techniques() {
116        let reg: Vec<&str> = COMPONENT_REGISTRY.iter().map(|e| e.id).collect();
117        assert_eq!(
118            reg, KNOWN_TECHNIQUES,
119            "the registry and KNOWN_TECHNIQUES must not drift"
120        );
121    }
122
123    #[test]
124    fn presupposes_reference_real_components() {
125        // No part may presuppose an id that isn't itself in the registry.
126        for e in COMPONENT_REGISTRY {
127            for pre in e.presupposes {
128                assert!(
129                    is_known(pre),
130                    "{} presupposes unknown component {pre}",
131                    e.id
132                );
133            }
134        }
135    }
136
137    #[test]
138    fn lookup_carries_the_contract() {
139        let retry = component("retry").expect("retry is registered");
140        assert_eq!(retry.kind, MountKind::Loop);
141        assert_eq!(retry.axis, Axis::GatingRepair);
142        assert_eq!(retry.tier, Tier::Headless);
143        assert_eq!(retry.presupposes, &["verify_gate"]);
144        assert!(component("nope").is_none());
145    }
146}