Skip to main content

mold_core/
model_policy.rs

1use std::fmt;
2use std::path::Path;
3
4use crate::minimax_h3;
5
6use serde::{Deserialize, Serialize};
7
8/// Stable machine-readable reason returned while MiniMax H3 authorization is absent.
9pub const MINIMAX_H3_AUTHORIZATION_REQUIRED: &str = "MINIMAX_H3_AUTHORIZATION_REQUIRED";
10
11/// Repository record that owns the authorization decision.
12pub const MINIMAX_H3_AUTHORIZATION_ISSUE_URL: &str = "https://github.com/utensils/mold/issues/831";
13
14/// License revision reviewed when this policy was introduced.
15pub const MINIMAX_H3_LICENSE_URL: &str = "https://huggingface.co/MiniMaxAI/MiniMax-H3/blob/\
16bfc8ed0353f5a9733be73e6b2c98ec0948195b86/LICENSE";
17
18/// Content identity of the reviewed license bytes, pinned by the H3
19/// conformance manifest and required by any future authorization record.
20pub 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
81/// Return the activation state for a model identity and its resolved family.
82///
83/// The family is required when the public identifier is opaque (for example a
84/// `cv:` catalog ID). Callers that have resolved catalog metadata must pass it.
85pub 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
95/// Whether an identity may appear in ordinary model-discovery surfaces.
96///
97/// This is a convenience view over [`model_activation`], not a second policy
98/// table. Catalog-family lists and other non-error-producing discovery paths
99/// use it so a compliance-gated family cannot leak through static taxonomy
100/// while mutating ingress paths continue to use [`require_model_activation`].
101pub fn model_activation_available(identifier: &str, family: Option<&str>) -> bool {
102    model_activation(identifier, family) == ModelActivation::Available
103}
104
105/// Return whether a model may be discovered and acquired from its pinned
106/// upstream source.
107///
108/// MiniMax H3's reviewed authorization permits upstream-direct downloads and
109/// local storage, but execution remains independently gated by
110/// [`model_activation`]. Keeping those authorities separate prevents a
111/// downloadable checkpoint from becoming an implicit runtime approval.
112pub 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
148/// Return the activation state for one concrete model artifact path.
149///
150/// `artifact_root` is a caller-owned trust boundary such as
151/// [`crate::Config::resolved_models_dir`]. Its own path components describe
152/// storage placement, not model identity, so only the artifact-relative suffix
153/// is inspected when `path` is contained by that root. Paths outside the root
154/// remain fail-closed and are inspected in full.
155///
156/// This distinction matters when an operator deliberately names `MOLD_HOME`
157/// after a UAT target (for example `/.../minimax-h3`): an ordinary FLUX file
158/// below that root is not H3, while a nested `MiniMax-H3/...` artifact still is.
159pub 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}