Skip to main content

split_brain_harness/
types.rs

1use serde::{Deserialize, Serialize};
2
3// ---------------------------------------------------------------------------
4// Backend selection
5// ---------------------------------------------------------------------------
6
7#[derive(Debug, Deserialize, Clone)]
8pub enum BackendType {
9    #[serde(rename = "openai-compat")]
10    OpenAiCompat,
11    #[serde(rename = "ollama-native")]
12    OllamaNative,
13    #[serde(rename = "local-embedded")]
14    LocalEmbedded,
15    #[serde(rename = "anthropic")]
16    Anthropic,
17}
18
19// ---------------------------------------------------------------------------
20// Verification mode
21// ---------------------------------------------------------------------------
22
23#[derive(Debug, Deserialize, Clone, Default)]
24pub enum VerifyMode {
25    /// Deterministic consistency checks only — no extra LLM call (default).
26    #[serde(rename = "deterministic")]
27    #[default]
28    Deterministic,
29    /// Deterministic checks + a second LLM call against the verifier soul prompt.
30    #[serde(rename = "llm")]
31    Llm,
32    /// Skip verification entirely.
33    #[serde(rename = "none")]
34    None,
35}
36
37// ---------------------------------------------------------------------------
38// Runtime configuration
39// ---------------------------------------------------------------------------
40
41#[derive(Debug, Deserialize, Clone)]
42pub struct Config {
43    pub backend: BackendType,
44    pub endpoint: String,
45    pub model_name: String,
46    pub soul_path: String,
47    pub api_key: Option<String>,
48    pub verify_mode: VerifyMode,
49    pub timeout_secs: u64,
50    /// Print system prompt + payload to stderr before the model call.
51    pub dump_prompt: bool,
52    /// Print raw model output to stderr before extraction.
53    pub dump_raw: bool,
54    /// Path to the capability memory JSON file for forge persistence.
55    /// None = in-memory only (no cross-session reputation).
56    pub memory_path: Option<String>,
57    /// Path to the append-only forge audit log (JSONL).
58    /// None = no audit logging.
59    pub audit_path: Option<String>,
60    /// If set, `sbh serve` requires `Authorization: Bearer <serve_key>`.
61    /// The serve key is NOT forwarded as the upstream API key.
62    pub serve_key: Option<String>,
63    /// Max requests per minute per IP for `sbh serve`. Default 60.
64    pub serve_rate_limit: u32,
65    /// Max request body size in bytes for `sbh serve`. Default 1 MiB.
66    pub serve_max_body_bytes: usize,
67    /// Path to the append-only session escalation log (JSONL).
68    /// Written on every slow-boil escalation event detected by `sbh serve`.
69    /// None = events are not persisted.
70    pub session_log_path: Option<String>,
71    /// Path to operator-supplied context docs (TOML file or directory of TOML files).
72    /// Merged with the embedded default corpus and injected into the system prompt.
73    /// None = embedded default corpus only.
74    pub context_path: Option<String>,
75}
76
77impl std::fmt::Display for BackendType {
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        match self {
80            BackendType::OpenAiCompat => write!(f, "openai-compat"),
81            BackendType::OllamaNative => write!(f, "ollama-native"),
82            BackendType::LocalEmbedded => write!(f, "local-embedded"),
83            BackendType::Anthropic => write!(f, "anthropic"),
84        }
85    }
86}
87
88impl std::fmt::Display for VerifyMode {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        match self {
91            VerifyMode::Deterministic => write!(f, "deterministic"),
92            VerifyMode::Llm => write!(f, "llm"),
93            VerifyMode::None => write!(f, "none"),
94        }
95    }
96}
97
98// ---------------------------------------------------------------------------
99// Soul container
100// ---------------------------------------------------------------------------
101
102#[derive(Debug, Clone)]
103pub struct Soul {
104    pub logic_system_prompt: String,
105    pub creative_system_prompt: String,
106    pub verifier_system_prompt: String,
107    pub code_gen_system_prompt: String,
108}
109
110// ---------------------------------------------------------------------------
111// Telemetry output schema
112// ---------------------------------------------------------------------------
113
114#[derive(Debug, Serialize, Deserialize, Clone)]
115#[serde(deny_unknown_fields)]
116pub struct AfferentTelemetry {
117    pub primary_emotion: String,
118    pub emotional_intensity: f32,
119    pub structural_tone: Vec<String>,
120}
121
122#[derive(Debug, Serialize, Deserialize, Clone)]
123#[serde(deny_unknown_fields)]
124pub struct IntentMatrix {
125    pub stated_objective: String,
126    pub subtextual_motive: String,
127    pub manipulation_risk: String,
128}
129
130#[derive(Debug, Serialize, Deserialize, Clone)]
131#[serde(deny_unknown_fields)]
132pub struct CognitiveState {
133    pub urgency_vector: f32,
134    pub coherence_rating: f32,
135}
136
137#[derive(Debug, Serialize, Deserialize, Clone)]
138pub struct TelemetryResult {
139    pub affective_telemetry: AfferentTelemetry,
140    pub intent_matrix: IntentMatrix,
141    pub cognitive_state: CognitiveState,
142}
143
144// ---------------------------------------------------------------------------
145// Verification layer
146// ---------------------------------------------------------------------------
147
148/// One step in the analysis pipeline — propose, deterministic check, or LLM verify.
149#[derive(Debug, Serialize, Deserialize, Clone)]
150pub struct TraceEntry {
151    pub stage: String,
152    pub claim: String,
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub evidence: Option<String>,
155    pub passed: bool,
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub note: Option<String>,
158}
159
160/// Result of the verification stage.
161#[derive(Debug, Serialize, Deserialize, Clone)]
162pub struct VerificationReport {
163    pub passed: bool,
164    pub consistency_flags: Vec<String>,
165    pub unsupported_claims: Vec<String>,
166    pub assumptions: Vec<String>,
167    pub unresolved: Vec<String>,
168    pub confidence: f32,
169    /// When true, confidence is below threshold — caller should pause and ask
170    /// for clarification rather than acting on the result.
171    pub stop_and_ask: bool,
172}
173
174/// Summary of pre-Stage-1 obfuscation detections from the normalizer pass.
175#[derive(Debug, Serialize, Deserialize, Clone, Default)]
176pub struct ObfuscationReport {
177    /// 0.0 = clean input, 1.0 = heavily obfuscated. Threshold ~0.25 for action.
178    pub score: f32,
179    /// Human-readable list of detected obfuscation events, e.g. ["homoglyph (3)", "base64"].
180    pub detections: Vec<String>,
181    /// The normalized (deobfuscated) text that was passed to Stage 1.
182    pub normalized_input: String,
183}
184
185/// Full pipeline output: telemetry + verification + step-level trace.
186/// `capability_request` is `None` unless the model emitted one alongside
187/// its telemetry (Phase 1 schema — no execution in this release).
188#[derive(Debug, Serialize, Deserialize, Clone)]
189pub struct HarnessResult {
190    pub telemetry: TelemetryResult,
191    pub verification: VerificationReport,
192    pub trace: Vec<TraceEntry>,
193    #[serde(skip_serializing_if = "Option::is_none", default)]
194    pub capability_request: Option<crate::capability::CapabilityRequest>,
195    /// Present when the input required deobfuscation before Stage 1.
196    #[serde(skip_serializing_if = "Option::is_none", default)]
197    pub obfuscation: Option<ObfuscationReport>,
198}