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#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ExplainDiagnosticParams {
10 pub uri: String,
12 pub position: ExplainPosition,
13 pub diagnostic_code: Option<String>,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct ExplainPosition {
20 pub line: u32,
21 pub character: u32,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct LawAxisTrace {
27 pub axis: String, pub status: String, pub description: String, pub resolution: Option<String>, }
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct ExplainDiagnosticResult {
36 pub diagnostic_code: String,
37 pub law_status: String, pub summary: String, pub law_axes: Vec<LawAxisTrace>,
40 pub resolution_steps: Vec<String>, pub related_receipts: Vec<String>, pub related_docs: Vec<String>, }
44
45#[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#[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
79pub 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}