1use std::fmt;
2use std::path::Path;
3
4use crate::minimax_h3;
5
6use serde::{Deserialize, Serialize};
7
8pub const MINIMAX_H3_AUTHORIZATION_REQUIRED: &str = "MINIMAX_H3_AUTHORIZATION_REQUIRED";
10
11pub const MINIMAX_H3_AUTHORIZATION_ISSUE_URL: &str = "https://github.com/utensils/mold/issues/831";
13
14pub const MINIMAX_H3_LICENSE_URL: &str = "https://huggingface.co/MiniMaxAI/MiniMax-H3/blob/\
16bfc8ed0353f5a9733be73e6b2c98ec0948195b86/LICENSE";
17
18pub const MINIMAX_H3_LICENSE_SHA256: &str =
21 "59b99642b95ea21630e311198ddbfffbfe05aadba0c2f5d884cbdf4efcc90f44";
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum ModelActivation {
25 Available,
26 ComplianceGated,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct ModelActivationError;
31
32#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)]
33pub struct ModelAccessCapabilities {
34 #[serde(default)]
35 pub restrictions: Vec<ModelAccessRestriction>,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)]
39pub struct ModelAccessRestriction {
40 pub code: String,
41 pub family: String,
42 pub message: String,
43 pub license_url: String,
44 pub authorization_url: String,
45}
46
47impl fmt::Display for ModelActivationError {
48 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
49 write!(
50 formatter,
51 "MiniMax H3 support is compliance-gated and is not activated in this build \
52 ({MINIMAX_H3_AUTHORIZATION_REQUIRED}). See {MINIMAX_H3_AUTHORIZATION_ISSUE_URL}"
53 )
54 }
55}
56
57impl std::error::Error for ModelActivationError {}
58
59impl ModelActivationError {
60 pub fn restriction(self) -> ModelAccessRestriction {
61 minimax_h3_restriction()
62 }
63}
64
65pub fn model_access_capabilities() -> ModelAccessCapabilities {
66 ModelAccessCapabilities {
67 restrictions: Vec::new(),
68 }
69}
70
71fn minimax_h3_restriction() -> ModelAccessRestriction {
72 ModelAccessRestriction {
73 code: MINIMAX_H3_AUTHORIZATION_REQUIRED.to_string(),
74 family: "minimax-h3".to_string(),
75 message: ModelActivationError.to_string(),
76 license_url: MINIMAX_H3_LICENSE_URL.to_string(),
77 authorization_url: MINIMAX_H3_AUTHORIZATION_ISSUE_URL.to_string(),
78 }
79}
80
81pub fn model_activation(identifier: &str, family: Option<&str>) -> ModelActivation {
86 if is_reviewed_minimax_h3_model(identifier) {
87 ModelActivation::Available
88 } else if is_minimax_h3_identity(identifier) || family.is_some_and(is_minimax_h3_identity) {
89 ModelActivation::ComplianceGated
90 } else {
91 ModelActivation::Available
92 }
93}
94
95pub fn model_activation_available(identifier: &str, family: Option<&str>) -> bool {
102 model_activation(identifier, family) == ModelActivation::Available
103}
104
105pub fn model_acquisition(identifier: &str, family: Option<&str>) -> ModelActivation {
113 if is_reviewed_minimax_h3_acquisition_identity(identifier) {
114 ModelActivation::Available
115 } else if cfg!(feature = "h3")
116 && (is_minimax_h3_identity(identifier) || family.is_some_and(is_minimax_h3_identity))
117 {
118 ModelActivation::ComplianceGated
119 } else {
120 model_activation(identifier, family)
121 }
122}
123
124pub fn model_acquisition_available(identifier: &str, family: Option<&str>) -> bool {
125 model_acquisition(identifier, family) == ModelActivation::Available
126}
127
128pub fn require_model_acquisition(
129 identifier: &str,
130 family: Option<&str>,
131) -> Result<(), ModelActivationError> {
132 match model_acquisition(identifier, family) {
133 ModelActivation::Available => Ok(()),
134 ModelActivation::ComplianceGated => Err(ModelActivationError),
135 }
136}
137
138pub fn require_model_activation(
139 identifier: &str,
140 family: Option<&str>,
141) -> Result<(), ModelActivationError> {
142 match model_activation(identifier, family) {
143 ModelActivation::Available => Ok(()),
144 ModelActivation::ComplianceGated => Err(ModelActivationError),
145 }
146}
147
148pub fn model_artifact_activation(
160 path: &Path,
161 artifact_root: Option<&Path>,
162 family: Option<&str>,
163) -> ModelActivation {
164 let identity_path = artifact_root
165 .and_then(|root| path.strip_prefix(root).ok())
166 .unwrap_or(path);
167 let is_h3 = is_minimax_h3_identity(&identity_path.to_string_lossy())
168 || family.is_some_and(is_minimax_h3_identity);
169 if cfg!(feature = "h3") && is_h3 {
170 ModelActivation::Available
171 } else if is_h3 {
172 ModelActivation::ComplianceGated
173 } else {
174 ModelActivation::Available
175 }
176}
177
178pub fn require_model_artifact_activation(
179 path: &Path,
180 artifact_root: Option<&Path>,
181 family: Option<&str>,
182) -> Result<(), ModelActivationError> {
183 match model_artifact_activation(path, artifact_root, family) {
184 ModelActivation::Available => Ok(()),
185 ModelActivation::ComplianceGated => Err(ModelActivationError),
186 }
187}
188
189fn is_minimax_h3_identity(value: &str) -> bool {
190 let normalized = value.trim().to_ascii_lowercase().chars().fold(
191 String::with_capacity(value.len()),
192 |mut out, ch| {
193 if ch.is_ascii_alphanumeric() {
194 out.push(ch);
195 } else if !out.ends_with('-') {
196 out.push('-');
197 }
198 out
199 },
200 );
201 let needle = "minimax-h3";
202 let separated_alias = normalized.match_indices(needle).any(|(start, _)| {
203 let before = normalized[..start].chars().next_back();
204 let after = normalized[start + needle.len()..].chars().next();
205 before.is_none_or(|ch| !ch.is_ascii_alphanumeric())
206 && after.is_none_or(|ch| !ch.is_ascii_alphanumeric())
207 });
208 separated_alias || normalized.split('-').any(is_minimax_h3_compact_alias)
209}
210
211fn is_reviewed_minimax_h3_acquisition_identity(value: &str) -> bool {
212 matches!(
213 value.trim().to_ascii_lowercase().as_str(),
214 "minimax-h3" | "minimax-h3-fl2va:comfy-pruned-int8" | "minimax-h3-ref2va:comfy-pruned-int8"
215 )
216}
217
218pub fn is_reviewed_minimax_h3_model(value: &str) -> bool {
219 let value = value.trim();
220 value.eq_ignore_ascii_case(minimax_h3::FL2VA_COMFY)
221 || value.eq_ignore_ascii_case(minimax_h3::REF2VA_COMFY)
222}
223
224fn is_minimax_h3_compact_alias(token: &str) -> bool {
225 fn has_class_suffix(value: &str, prefix: &str) -> bool {
226 value.strip_prefix(prefix).is_some_and(|suffix| {
227 suffix
228 .chars()
229 .next()
230 .is_none_or(|ch| ch.is_ascii_alphabetic())
231 })
232 }
233
234 has_class_suffix(token, "minimaxh3") || has_class_suffix(token, "autoencoderklminimaxh3")
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 #[test]
242 #[cfg(not(feature = "h3"))]
243 fn minimax_h3_aliases_and_task_variants_are_compliance_gated() {
244 for identifier in [
245 "minimax-h3",
246 "MiniMax H3",
247 "MiniMax-H3-FL2VA",
248 "minimax_h3_ref2va:bf16",
249 "MiniMaxH3",
250 "MiniMaxH3Scheduler",
251 "MiniMaxH3Transformer3DModel",
252 "AutoencoderKLMiniMaxH3",
253 "hf:MiniMaxAI/MiniMax-H3",
254 "hf:MiniMaxAI/MiniMaxH3",
255 "hf:Comfy-Org/MiniMax-H3",
256 "https://huggingface.co/MiniMaxAI/MiniMax-H3/tree/main",
257 ] {
258 assert_eq!(
259 model_activation(identifier, None),
260 ModelActivation::ComplianceGated,
261 "{identifier}"
262 );
263 }
264
265 assert_eq!(
266 model_activation("cv:42", Some("MiniMax-H3")),
267 ModelActivation::ComplianceGated
268 );
269 }
270
271 #[test]
272 fn unrelated_models_and_h3_lookalikes_remain_available() {
273 for identifier in [
274 "flux-dev:q8",
275 "my-h3-model",
276 "h3",
277 "minimax-h30",
278 "minimaxh30",
279 "notminimax-h3",
280 "notminimaxh3",
281 ] {
282 assert_eq!(
283 model_activation(identifier, None),
284 ModelActivation::Available,
285 "{identifier}"
286 );
287 }
288 }
289
290 #[test]
291 #[cfg(not(feature = "h3"))]
292 fn discovery_availability_is_a_view_of_the_activation_authority() {
293 assert!(!model_activation_available(
294 "minimax-h3",
295 Some("minimax-h3")
296 ));
297 assert!(model_activation_available("flux", Some("flux")));
298 }
299
300 #[test]
301 #[cfg(not(feature = "h3"))]
302 fn reviewed_h3_models_are_ordinary_activation_identities() {
303 for identifier in [
304 "minimax-h3-fl2va:comfy-pruned-int8",
305 "minimax-h3-ref2va:comfy-pruned-int8",
306 ] {
307 assert_eq!(
308 model_acquisition(identifier, Some("minimax-h3")),
309 ModelActivation::Available,
310 "{identifier}"
311 );
312 assert_eq!(
313 model_activation(identifier, Some("minimax-h3")),
314 ModelActivation::Available,
315 "{identifier}"
316 );
317 }
318
319 assert_eq!(
320 model_activation("minimax-h3", Some("minimax-h3")),
321 ModelActivation::ComplianceGated
322 );
323
324 for unreviewed in [
325 "hf:Comfy-Org/MiniMax-H3",
326 "minimax-h3:custom",
327 "transformer/high_noise.safetensors",
328 ] {
329 assert_eq!(
330 model_acquisition(unreviewed, Some("minimax-h3")),
331 ModelActivation::ComplianceGated,
332 "{unreviewed}"
333 );
334 }
335 }
336
337 #[test]
338 #[cfg(not(feature = "h3"))]
339 fn artifact_policy_ignores_h3_named_storage_root_but_not_nested_identity() {
340 let artifact_root = Path::new("/Volumes/ExternalStorage/mold-uat/minimax-h3/models");
341 let flux = artifact_root.join("flux-dev/transformer/model.safetensors");
342 let h3 = artifact_root.join("custom/MiniMax-H3/transformer/model.safetensors");
343
344 assert_eq!(
345 model_artifact_activation(&flux, Some(artifact_root), Some("flux")),
346 ModelActivation::Available
347 );
348 assert_eq!(
349 model_artifact_activation(&h3, Some(artifact_root), Some("custom")),
350 ModelActivation::ComplianceGated
351 );
352 }
353
354 #[test]
355 #[cfg(not(feature = "h3"))]
356 fn artifact_policy_inspects_full_paths_outside_its_trusted_root() {
357 let artifact_root = Path::new("/srv/mold/models");
358 let external = Path::new("/Volumes/MiniMax-H3/weights.safetensors");
359
360 assert_eq!(
361 model_artifact_activation(external, Some(artifact_root), None),
362 ModelActivation::ComplianceGated
363 );
364 }
365
366 #[test]
367 #[cfg(not(feature = "h3"))]
368 fn rejection_is_stable_and_does_not_echo_the_supplied_identifier() {
369 let secretish_identifier = "hf:MiniMaxAI/MiniMax-H3?token=do-not-echo";
370 let error = require_model_activation(secretish_identifier, None).unwrap_err();
371 let message = error.to_string();
372 assert!(message.contains(MINIMAX_H3_AUTHORIZATION_REQUIRED));
373 assert!(message.contains(MINIMAX_H3_AUTHORIZATION_ISSUE_URL));
374 assert!(!message.contains(secretish_identifier));
375 }
376
377 #[test]
378 #[cfg(not(feature = "h3"))]
379 fn capabilities_do_not_advertise_a_family_wide_h3_restriction() {
380 let capabilities = model_access_capabilities();
381 assert!(capabilities.restrictions.is_empty());
382
383 let round_trip: ModelAccessCapabilities =
384 serde_json::from_str(&serde_json::to_string(&capabilities).unwrap()).unwrap();
385 assert_eq!(round_trip, capabilities);
386 }
387
388 #[test]
389 #[cfg(feature = "h3")]
390 fn public_h3_feature_activates_only_the_exact_runtime_partition() {
391 assert_eq!(
392 model_activation(minimax_h3::FL2VA_COMFY, Some("minimax-h3")),
393 ModelActivation::Available
394 );
395 for identifier in [
396 "minimax-h3",
397 "hf:Comfy-Org/MiniMax-H3",
398 "MiniMaxH3Transformer3DModel",
399 ] {
400 assert_eq!(
401 model_activation(identifier, Some("minimax-h3")),
402 ModelActivation::ComplianceGated,
403 "{identifier}"
404 );
405 }
406 assert_eq!(
407 model_activation(minimax_h3::REF2VA_COMFY, Some("minimax-h3")),
408 ModelActivation::Available
409 );
410 assert!(model_access_capabilities().restrictions.is_empty());
411 assert_eq!(
412 model_artifact_activation(
413 Path::new("/models/minimax-h3/transformer.safetensors"),
414 Some(Path::new("/models")),
415 Some("minimax-h3")
416 ),
417 ModelActivation::Available
418 );
419 }
420
421 #[test]
422 #[cfg(feature = "h3")]
423 fn public_h3_feature_keeps_acquisition_on_reviewed_manifests() {
424 for reviewed in [
425 "minimax-h3-fl2va:comfy-pruned-int8",
426 "minimax-h3-ref2va:comfy-pruned-int8",
427 ] {
428 assert_eq!(
429 model_acquisition(reviewed, Some("minimax-h3")),
430 ModelActivation::Available
431 );
432 }
433 for unreviewed in ["hf:Comfy-Org/MiniMax-H3", "minimax-h3:custom"] {
434 assert_eq!(
435 model_acquisition(unreviewed, Some("minimax-h3")),
436 ModelActivation::ComplianceGated
437 );
438 }
439 }
440}