Skip to main content

lsp_max_protocol/
explain.rs

1use serde::{Deserialize, Serialize};
2
3pub const EXPLAIN_DIAGNOSTIC: &str = "max/explain.diagnostic";
4pub const EXPLAIN_STATUS: &str = "max/explain.status";
5pub const EXPLAIN_RECEIPT: &str = "max/explain.receipt";
6
7/// Request: explain a specific diagnostic
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ExplainDiagnosticParams {
10    /// Serialised document URI
11    pub uri: String,
12    pub position: ExplainPosition,
13    /// Optional: diagnostic code to explain (e.g. "ANTI-LLM-META-003")
14    pub diagnostic_code: Option<String>,
15}
16
17/// Minimal position type for explain requests (row/column, 0-based).
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct ExplainPosition {
20    pub line: u32,
21    pub character: u32,
22}
23
24/// A single law-axis trace step
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct LawAxisTrace {
27    pub axis: String,               // e.g. "transcript", "negative_control", "receipt"
28    pub status: String,             // ADMITTED | CANDIDATE | REFUSED | UNKNOWN | OPEN
29    pub description: String,        // why this axis is in this state
30    pub resolution: Option<String>, // what would clear this axis
31}
32
33/// Full explanation response
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct ExplainDiagnosticResult {
36    pub diagnostic_code: String,
37    pub law_status: String, // overall status
38    pub summary: String,    // one-sentence explanation
39    pub law_axes: Vec<LawAxisTrace>,
40    pub resolution_steps: Vec<String>, // ordered steps to resolve
41    pub related_receipts: Vec<String>, // receipt paths if any
42    pub related_docs: Vec<String>,     // doc links
43}
44
45/// Request: explain the status of a method
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct ExplainStatusParams {
48    pub method: String,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct ExplainStatusResult {
53    pub method: String,
54    pub law_status: String,
55    pub explanation: String,
56    pub law_axes: Vec<LawAxisTrace>,
57    pub can_promote: bool,
58    pub promotion_blockers: Vec<String>,
59}
60
61/// Request: explain a receipt chain
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct ExplainReceiptParams {
64    pub receipt_path: String,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct ExplainReceiptResult {
69    pub receipt_path: String,
70    pub valid: bool,
71    pub has_begin_marker: bool,
72    pub has_end_marker: bool,
73    pub has_digest: bool,
74    pub has_checkpoint: bool,
75    pub issues: Vec<String>,
76    pub law_status: String,
77}
78
79/// Well-known diagnostic code explanations
80pub fn explain_code(code: &str) -> ExplainDiagnosticResult {
81    match code {
82        "ANTI-LLM-META-001" => ExplainDiagnosticResult {
83            diagnostic_code: code.to_string(),
84            law_status: "REFUSED".into(),
85            summary: "Forbidden plain tower-lsp reference detected".into(),
86            law_axes: vec![LawAxisTrace {
87                axis: "naming_law".into(),
88                status: "REFUSED".into(),
89                description: "All references must use lsp-max, not tower-lsp or tower_lsp".into(),
90                resolution: Some("Replace with lsp-max or lsp_max".into()),
91            }],
92            resolution_steps: vec![
93                "Search for tower-lsp / tower_lsp using scripts/check-law-compliance.sh".into(),
94                "Replace all occurrences with lsp-max (crate name) or lsp_max (Rust identifier)".into(),
95                "Verify with `just dx-verify`".into(),
96            ],
97            related_receipts: vec![],
98            related_docs: vec!["CLAUDE.md#naming-law".into()],
99        },
100        "ANTI-LLM-META-002" => ExplainDiagnosticResult {
101            diagnostic_code: code.to_string(),
102            law_status: "REFUSED".into(),
103            summary: "Victory language detected — use bounded status".into(),
104            law_axes: vec![LawAxisTrace {
105                axis: "language_law".into(),
106                status: "REFUSED".into(),
107                description: "Terms like 'done', 'complete', 'solved' are forbidden; they assert certainty that cannot be receipted".into(),
108                resolution: Some("Replace with ADMITTED, CANDIDATE, OPEN, PARTIAL, or BLOCKED".into()),
109            }],
110            resolution_steps: vec![
111                "Replace 'done' → 'ADMITTED' (if receipt exists) or 'CANDIDATE'".into(),
112                "Replace 'complete' → 'PARTIAL' or 'CANDIDATE'".into(),
113                "Replace 'solved' → 'ADMITTED' (if receipt exists) or 'CANDIDATE'".into(),
114            ],
115            related_receipts: vec![],
116            related_docs: vec!["CLAUDE.md#victory-language".into()],
117        },
118        "ANTI-LLM-META-003" => ExplainDiagnosticResult {
119            diagnostic_code: code.to_string(),
120            law_status: "BLOCKED".into(),
121            summary: "law:ADMITTED claimed without law:receipt — receipt chain OPEN".into(),
122            law_axes: vec![
123                LawAxisTrace {
124                    axis: "receipt".into(),
125                    status: "OPEN".into(),
126                    description: "ADMITTED status requires a receipt artifact (BEGIN/END markers + SHA256 digest)".into(),
127                    resolution: Some("Run `lsp-max admit receipt <method>` to generate a receipt template".into()),
128                },
129                LawAxisTrace {
130                    axis: "transcript".into(),
131                    status: "UNKNOWN".into(),
132                    description: "No transcript present to receipt".into(),
133                    resolution: Some("Run the dogfood test for this method and capture output as transcript".into()),
134                },
135            ],
136            resolution_steps: vec![
137                "Generate receipt: `lsp-max admit receipt <method>`".into(),
138                "Attach transcript to the receipt file".into(),
139                "Add negative-control test in tests/negative/".into(),
140                "Run `lsp-max admit promote <method>` once all axes are present".into(),
141            ],
142            related_receipts: vec![],
143            related_docs: vec!["CLAUDE.md#receipt-chain".into()],
144        },
145        "ANTI-LLM-META-004" => ExplainDiagnosticResult {
146            diagnostic_code: code.to_string(),
147            law_status: "UNKNOWN".into(),
148            summary: "lsp:Request without law:status — defaults to UNKNOWN".into(),
149            law_axes: vec![LawAxisTrace {
150                axis: "law_status".into(),
151                status: "UNKNOWN".into(),
152                description: "Every lsp:Request must have an explicit law:status assertion".into(),
153                resolution: Some("Add law:status law:CANDIDATE to the method declaration".into()),
154            }],
155            resolution_steps: vec![
156                "Add `law:status law:CANDIDATE ;` to the method's TTL block".into(),
157                "UNKNOWN must not collapse to ADMITTED without tracing".into(),
158            ],
159            related_receipts: vec![],
160            related_docs: vec!["CLAUDE.md#unknown-status".into()],
161        },
162        _ => ExplainDiagnosticResult {
163            diagnostic_code: code.to_string(),
164            law_status: "UNKNOWN".into(),
165            summary: format!("Unknown diagnostic code: {code}"),
166            law_axes: vec![],
167            resolution_steps: vec!["Check CLAUDE.md for law documentation".into()],
168            related_receipts: vec![],
169            related_docs: vec![],
170        },
171    }
172}
173
174pub fn explain_method_status(method: &str, law_status: &str) -> ExplainStatusResult {
175    let (explanation, axes, can_promote, blockers) = match law_status {
176        "ADMITTED" => (
177            format!("{method} is ADMITTED — receipt chain is closed"),
178            vec![
179                LawAxisTrace {
180                    axis: "transcript".into(),
181                    status: "PRESENT".into(),
182                    description: "Transcript file attached".into(),
183                    resolution: None,
184                },
185                LawAxisTrace {
186                    axis: "negative_control".into(),
187                    status: "PRESENT".into(),
188                    description: "Negative-control test present".into(),
189                    resolution: None,
190                },
191                LawAxisTrace {
192                    axis: "receipt".into(),
193                    status: "PRESENT".into(),
194                    description: "Receipt artifact with digest".into(),
195                    resolution: None,
196                },
197            ],
198            false,
199            vec![],
200        ),
201        "REFUSED" => (
202            format!("{method} is REFUSED — law-blocked by ontology assertion"),
203            vec![LawAxisTrace {
204                axis: "policy".into(),
205                status: "REFUSED".into(),
206                description: "Ontology law:REFUSED assertion present".into(),
207                resolution: Some("Edit domain.ttl law:reason to understand the policy".into()),
208            }],
209            false,
210            vec!["REFUSED methods cannot be promoted — update ontology policy to change".into()],
211        ),
212        "UNKNOWN" => (
213            format!("{method} status is UNKNOWN — not yet traced"),
214            vec![LawAxisTrace {
215                axis: "tracing".into(),
216                status: "UNKNOWN".into(),
217                description: "Law-axis tracing not initiated".into(),
218                resolution: Some("Add law:status law:CANDIDATE to begin tracing".into()),
219            }],
220            false,
221            vec!["UNKNOWN must not collapse to ADMITTED without explicit tracing".into()],
222        ),
223        _ => (
224            format!("{method} is CANDIDATE — receipt chain OPEN"),
225            vec![
226                LawAxisTrace {
227                    axis: "transcript".into(),
228                    status: "OPEN".into(),
229                    description: "No transcript attached yet".into(),
230                    resolution: Some("Run dogfood test and capture output".into()),
231                },
232                LawAxisTrace {
233                    axis: "negative_control".into(),
234                    status: "OPEN".into(),
235                    description: "No negative-control test yet".into(),
236                    resolution: Some("Create tests/negative/<method>.rs".into()),
237                },
238                LawAxisTrace {
239                    axis: "receipt".into(),
240                    status: "OPEN".into(),
241                    description: "No receipt artifact yet".into(),
242                    resolution: Some("Run `lsp-max admit receipt <method>`".into()),
243                },
244            ],
245            true,
246            vec![
247                "transcript OPEN".into(),
248                "negative_control OPEN".into(),
249                "receipt OPEN".into(),
250            ],
251        ),
252    };
253
254    ExplainStatusResult {
255        method: method.to_string(),
256        law_status: law_status.to_string(),
257        explanation,
258        law_axes: axes,
259        can_promote,
260        promotion_blockers: blockers,
261    }
262}