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}