1use serde::{Deserialize, Serialize};
2
3pub const INTENT_DECLARE: &str = "max/intent.declare";
4pub const INTENT_VALIDATE: &str = "max/intent.validate";
5pub const INTENT_REVOKE: &str = "max/intent.revoke";
6pub const INTENT_LIST: &str = "max/intent.list";
7
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
10pub enum IntentKind {
11 FileWrite { uri: String },
13 ShellExec { command: String },
15 LspCall { method: String },
17 AdmissionPromotion { method: String },
19 GitPush { branch: String },
21 ReceiptGeneration { receipt_id: String },
23 Custom {
25 kind: String,
26 payload: serde_json::Value,
27 },
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
32pub enum IntentOutcome {
33 Cleared,
35 Blocked { reason: String },
37 ClarificationRequired { question: String },
39 Deferred { condition: String },
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct IntentDeclareParams {
45 pub intent_id: String,
47 pub kind: IntentKind,
49 pub rationale: String,
51 pub context_uri: Option<String>,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct IntentDeclareResult {
57 pub intent_id: String,
58 pub outcome: IntentOutcome,
59 pub law_status: String,
61 pub gate_open: bool,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct IntentValidateParams {
66 pub intent_id: String,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct IntentValidateResult {
71 pub intent_id: String,
72 pub valid: bool,
73 pub outcome: IntentOutcome,
74 pub violations: Vec<String>,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct IntentRevokeParams {
79 pub intent_id: String,
80 pub reason: String,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct IntentRevokeResult {
85 pub intent_id: String,
86 pub status: String,
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct IntentListParams {
91 pub filter_outcome: Option<String>,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct IntentListResult {
97 pub intents: Vec<IntentSummary>,
98 pub total: usize,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct IntentSummary {
103 pub intent_id: String,
104 pub kind_label: String,
105 pub outcome: String,
106 pub law_status: String,
107 pub gate_open: bool,
108}
109
110pub struct IntentRegistry {
112 intents: std::collections::HashMap<String, (IntentDeclareParams, IntentDeclareResult)>,
113}
114
115impl IntentRegistry {
116 pub fn new() -> Self {
117 Self {
118 intents: std::collections::HashMap::new(),
119 }
120 }
121
122 pub fn declare(&mut self, params: IntentDeclareParams) -> IntentDeclareResult {
123 let outcome = self.validate_intent(¶ms);
124 let gate_open = matches!(outcome, IntentOutcome::Cleared);
125 let law_status = if gate_open {
126 "CANDIDATE".into()
127 } else {
128 "REFUSED".into()
129 };
130 let result = IntentDeclareResult {
131 intent_id: params.intent_id.clone(),
132 outcome: outcome.clone(),
133 law_status,
134 gate_open,
135 };
136 self.intents
137 .insert(params.intent_id.clone(), (params, result.clone()));
138 result
139 }
140
141 fn validate_intent(&self, params: &IntentDeclareParams) -> IntentOutcome {
142 match ¶ms.kind {
143 IntentKind::FileWrite { uri }
144 if uri.contains("tower-lsp") || uri.contains("tower_lsp") =>
145 {
146 IntentOutcome::Blocked {
147 reason: "LawViolation: uri contains forbidden tower-lsp reference".into(),
148 }
149 }
150 IntentKind::ShellExec { command } if command.contains("--no-verify") => {
151 IntentOutcome::Blocked {
152 reason: "LawViolation: --no-verify bypasses law compliance hooks".into(),
153 }
154 }
155 IntentKind::AdmissionPromotion { method } if method.is_empty() => {
156 IntentOutcome::ClarificationRequired {
157 question: "Which method is being promoted?".into(),
158 }
159 }
160 _ => IntentOutcome::Cleared,
161 }
162 }
163
164 pub fn revoke(&mut self, id: &str) -> Option<IntentRevokeResult> {
165 self.intents.remove(id).map(|_| IntentRevokeResult {
166 intent_id: id.to_string(),
167 status: "REVOKED".into(),
168 })
169 }
170
171 pub fn list(&self, filter: Option<&str>) -> IntentListResult {
172 let intents: Vec<IntentSummary> = self
173 .intents
174 .values()
175 .filter(|(_, r)| filter.map(|f| r.outcome.label() == f).unwrap_or(true))
176 .map(|(p, r)| IntentSummary {
177 intent_id: p.intent_id.clone(),
178 kind_label: p.kind.label(),
179 outcome: r.outcome.label().to_string(),
180 law_status: r.law_status.clone(),
181 gate_open: r.gate_open,
182 })
183 .collect();
184 let total = intents.len();
185 IntentListResult { intents, total }
186 }
187}
188
189impl Default for IntentRegistry {
190 fn default() -> Self {
191 Self::new()
192 }
193}
194
195impl IntentKind {
196 pub fn label(&self) -> String {
197 match self {
198 Self::FileWrite { uri } => format!("FileWrite:{uri}"),
199 Self::ShellExec { command } => format!("ShellExec:{command}"),
200 Self::LspCall { method } => format!("LspCall:{method}"),
201 Self::AdmissionPromotion { method } => format!("AdmissionPromotion:{method}"),
202 Self::GitPush { branch } => format!("GitPush:{branch}"),
203 Self::ReceiptGeneration { receipt_id } => {
204 format!("ReceiptGeneration:{receipt_id}")
205 }
206 Self::Custom { kind, .. } => format!("Custom:{kind}"),
207 }
208 }
209}
210
211impl IntentOutcome {
212 pub fn label(&self) -> &str {
213 match self {
214 Self::Cleared => "Cleared",
215 Self::Blocked { .. } => "Blocked",
216 Self::ClarificationRequired { .. } => "ClarificationRequired",
217 Self::Deferred { .. } => "Deferred",
218 }
219 }
220}