Skip to main content

lit/commands/
content_type.rs

1//! Content type system — extends Lit from SWE-only VCS to a universal
2//! versioning system for CAD, EDA, manuscripts, databases, scientific data,
3//! media assets, and arbitrary domain content.
4//!
5//! Each content type carries diff/merge strategy hints, metadata schemas,
6//! and size/storage policies so that Lit can handle domain-specific files
7//! without requiring external plugins.
8
9use crate::core::find_repo_root;
10use crate::errors::LitError;
11use crate::response::ContentTypeResponse;
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use std::fs;
15use std::path::Path;
16
17// ── Data types ──────────────────────────────────────────────────────────────
18
19/// How a content type should be diffed
20#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
21pub enum DiffStrategy {
22    /// Line-based textual diff (source code, markdown, config)
23    Text,
24    /// Binary diff (delta compression)
25    Binary,
26    /// Structural diff (JSON/XML/AST tree comparison)
27    Structural,
28    /// Semantic diff (schema-aware: databases, CAD feature trees)
29    Semantic,
30    /// No diff — treat as opaque blob, show only size/hash changes
31    Opaque,
32}
33
34/// How a content type should be merged
35#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
36pub enum MergeStrategy {
37    /// Standard 3-way text merge
38    TextThreeWay,
39    /// Take ours or theirs — no automatic merge (binary, CAD)
40    ManualResolve,
41    /// Schema-aware merge (databases, structured data)
42    SchemaAware,
43    /// Component-level merge (EDA: merge at schematic block level)
44    ComponentLevel,
45    /// Append-only merge (logs, audit trails)
46    AppendOnly,
47    /// Last-writer-wins (media assets, compiled outputs)
48    LastWriterWins,
49}
50
51/// Storage tier hint
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
53pub enum StorageTier {
54    /// Normal object store (small text files)
55    Standard,
56    /// LFS — large file storage (binary assets, datasets)
57    Lfs,
58    /// Chunked — content-defined chunking for large structured files
59    Chunked,
60    /// External — reference to external storage (S3, GCS, NFS)
61    External,
62}
63
64/// Domain classification
65#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
66pub enum ContentDomain {
67    Software,
68    Cad,
69    Eda,
70    Cam,
71    Simulation,
72    MlModel,
73    Manuscript,
74    Database,
75    Scientific,
76    Media,
77    Geospatial,
78    Legal,
79    Financial,
80    Config,
81    Documentation,
82    Custom(String),
83}
84
85impl std::fmt::Display for ContentDomain {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        match self {
88            ContentDomain::Software => write!(f, "software"),
89            ContentDomain::Cad => write!(f, "cad"),
90            ContentDomain::Eda => write!(f, "eda"),
91            ContentDomain::Cam => write!(f, "cam"),
92            ContentDomain::Simulation => write!(f, "simulation"),
93            ContentDomain::MlModel => write!(f, "ml-model"),
94            ContentDomain::Manuscript => write!(f, "manuscript"),
95            ContentDomain::Database => write!(f, "database"),
96            ContentDomain::Scientific => write!(f, "scientific"),
97            ContentDomain::Media => write!(f, "media"),
98            ContentDomain::Geospatial => write!(f, "geospatial"),
99            ContentDomain::Legal => write!(f, "legal"),
100            ContentDomain::Financial => write!(f, "financial"),
101            ContentDomain::Config => write!(f, "config"),
102            ContentDomain::Documentation => write!(f, "documentation"),
103            ContentDomain::Custom(s) => write!(f, "custom:{}", s),
104        }
105    }
106}
107
108/// A registered content type with domain-specific handling policies
109#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct ContentType {
111    /// Unique identifier (e.g. "cad/step", "eda/kicad-pcb", "db/sqlite")
112    pub id: String,
113    /// Human-readable name
114    pub name: String,
115    /// Domain classification
116    pub domain: ContentDomain,
117    /// MIME type(s) associated with this content type
118    pub mime_types: Vec<String>,
119    /// File extensions (without dot) that map to this type
120    pub extensions: Vec<String>,
121    /// Magic bytes for binary detection (hex-encoded prefixes)
122    #[serde(default)]
123    pub magic_bytes: Vec<String>,
124    /// Recommended diff strategy
125    pub diff_strategy: DiffStrategy,
126    /// Recommended merge strategy
127    pub merge_strategy: MergeStrategy,
128    /// Storage tier
129    pub storage_tier: StorageTier,
130    /// Maximum inline size (bytes) before promoting to LFS
131    pub lfs_threshold: Option<u64>,
132    /// Metadata schema — JSON Schema fragment describing domain-specific fields
133    #[serde(default)]
134    pub metadata_schema: Option<serde_json::Value>,
135    /// Whether this type supports structural diffing natively
136    pub structural_diff: bool,
137    /// Whether this type supports component-level locking
138    pub component_locking: bool,
139    /// Description
140    pub description: String,
141}
142
143// ── Built-in content types ──────────────────────────────────────────────────
144
145fn builtin_types() -> Vec<ContentType> {
146    vec![
147        // ── CAD ──
148        ContentType {
149            id: "cad/step".into(),
150            name: "STEP CAD Model".into(),
151            domain: ContentDomain::Cad,
152            mime_types: vec!["model/step".into()],
153            extensions: vec!["step".into(), "stp".into(), "p21".into()],
154            magic_bytes: vec!["49534F2D".into()], // "ISO-"
155            diff_strategy: DiffStrategy::Structural,
156            merge_strategy: MergeStrategy::ManualResolve,
157            storage_tier: StorageTier::Lfs,
158            lfs_threshold: Some(1024 * 1024),
159            metadata_schema: Some(serde_json::json!({
160                "type": "object",
161                "properties": {
162                    "units": {"type": "string", "enum": ["mm", "in", "m"]},
163                    "assembly_count": {"type": "integer"},
164                    "bounding_box": {"type": "array", "items": {"type": "number"}}
165                }
166            })),
167            structural_diff: true,
168            component_locking: true,
169            description: "ISO 10303 STEP geometry exchange format".into(),
170        },
171        ContentType {
172            id: "cad/stl".into(),
173            name: "STL Mesh".into(),
174            domain: ContentDomain::Cad,
175            mime_types: vec!["model/stl".into()],
176            extensions: vec!["stl".into()],
177            magic_bytes: vec!["736F6C6964".into()], // "solid" (ASCII STL)
178            diff_strategy: DiffStrategy::Opaque,
179            merge_strategy: MergeStrategy::LastWriterWins,
180            storage_tier: StorageTier::Lfs,
181            lfs_threshold: Some(512 * 1024),
182            metadata_schema: Some(serde_json::json!({
183                "type": "object",
184                "properties": {
185                    "triangle_count": {"type": "integer"},
186                    "format": {"type": "string", "enum": ["ascii", "binary"]}
187                }
188            })),
189            structural_diff: false,
190            component_locking: false,
191            description: "Stereolithography mesh format for 3D printing".into(),
192        },
193        ContentType {
194            id: "cad/iges".into(),
195            name: "IGES CAD Model".into(),
196            domain: ContentDomain::Cad,
197            mime_types: vec!["model/iges".into()],
198            extensions: vec!["igs".into(), "iges".into()],
199            magic_bytes: vec![],
200            diff_strategy: DiffStrategy::Structural,
201            merge_strategy: MergeStrategy::ManualResolve,
202            storage_tier: StorageTier::Lfs,
203            lfs_threshold: Some(1024 * 1024),
204            metadata_schema: None,
205            structural_diff: true,
206            component_locking: false,
207            description: "Initial Graphics Exchange Specification".into(),
208        },
209        ContentType {
210            id: "cad/3mf".into(),
211            name: "3MF Model".into(),
212            domain: ContentDomain::Cad,
213            mime_types: vec![
214                "model/3mf".into(),
215                "application/vnd.ms-package.3dmanufacturing-3dmodel+xml".into(),
216            ],
217            extensions: vec!["3mf".into()],
218            magic_bytes: vec!["504B0304".into()], // ZIP header
219            diff_strategy: DiffStrategy::Structural,
220            merge_strategy: MergeStrategy::ComponentLevel,
221            storage_tier: StorageTier::Lfs,
222            lfs_threshold: Some(1024 * 1024),
223            metadata_schema: None,
224            structural_diff: true,
225            component_locking: true,
226            description: "3D Manufacturing Format (ZIP-based XML)".into(),
227        },
228        // ── CAD (native mechanical formats) ──
229        ContentType {
230            id: "cad/dwg".into(),
231            name: "AutoCAD DWG".into(),
232            domain: ContentDomain::Cad,
233            mime_types: vec!["image/vnd.dwg".into()],
234            extensions: vec!["dwg".into()],
235            magic_bytes: vec!["4143".into()],
236            diff_strategy: DiffStrategy::Opaque,
237            merge_strategy: MergeStrategy::ManualResolve,
238            storage_tier: StorageTier::Lfs,
239            lfs_threshold: Some(1024 * 1024),
240            metadata_schema: None,
241            structural_diff: false,
242            component_locking: true,
243            description: "Autodesk AutoCAD native drawing".into(),
244        },
245        ContentType {
246            id: "cad/dxf".into(),
247            name: "AutoCAD DXF".into(),
248            domain: ContentDomain::Cad,
249            mime_types: vec!["image/vnd.dxf".into()],
250            extensions: vec!["dxf".into()],
251            magic_bytes: vec![],
252            diff_strategy: DiffStrategy::Structural,
253            merge_strategy: MergeStrategy::ManualResolve,
254            storage_tier: StorageTier::Standard,
255            lfs_threshold: Some(20 * 1024 * 1024),
256            metadata_schema: None,
257            structural_diff: true,
258            component_locking: false,
259            description: "Drawing Exchange Format (ASCII/binary CAD interchange)".into(),
260        },
261        ContentType {
262            id: "cad/solidworks".into(),
263            name: "SolidWorks Document".into(),
264            domain: ContentDomain::Cad,
265            mime_types: vec!["application/x-solidworks".into()],
266            extensions: vec!["sldprt".into(), "sldasm".into(), "slddrw".into()],
267            magic_bytes: vec![],
268            diff_strategy: DiffStrategy::Opaque,
269            merge_strategy: MergeStrategy::ManualResolve,
270            storage_tier: StorageTier::Lfs,
271            lfs_threshold: Some(1024 * 1024),
272            metadata_schema: None,
273            structural_diff: false,
274            component_locking: true,
275            description: "Dassault SolidWorks part/assembly/drawing".into(),
276        },
277        ContentType {
278            id: "cad/catia".into(),
279            name: "CATIA Document".into(),
280            domain: ContentDomain::Cad,
281            mime_types: vec!["application/x-catia".into()],
282            extensions: vec![
283                "catpart".into(),
284                "catproduct".into(),
285                "catdrawing".into(),
286                "cgr".into(),
287            ],
288            magic_bytes: vec![],
289            diff_strategy: DiffStrategy::Opaque,
290            merge_strategy: MergeStrategy::ManualResolve,
291            storage_tier: StorageTier::Lfs,
292            lfs_threshold: Some(1024 * 1024),
293            metadata_schema: None,
294            structural_diff: false,
295            component_locking: true,
296            description: "Dassault CATIA V5 part/product/drawing".into(),
297        },
298        ContentType {
299            id: "cad/inventor".into(),
300            name: "Autodesk Inventor".into(),
301            domain: ContentDomain::Cad,
302            mime_types: vec!["application/x-inventor".into()],
303            extensions: vec!["ipt".into(), "iam".into(), "idw".into(), "ipn".into()],
304            magic_bytes: vec![],
305            diff_strategy: DiffStrategy::Opaque,
306            merge_strategy: MergeStrategy::ManualResolve,
307            storage_tier: StorageTier::Lfs,
308            lfs_threshold: Some(1024 * 1024),
309            metadata_schema: None,
310            structural_diff: false,
311            component_locking: true,
312            description: "Autodesk Inventor part/assembly/drawing/presentation".into(),
313        },
314        ContentType {
315            id: "cad/fusion360".into(),
316            name: "Fusion 360 Archive".into(),
317            domain: ContentDomain::Cad,
318            mime_types: vec!["application/x-fusion360".into()],
319            extensions: vec!["f3d".into(), "f3z".into()],
320            magic_bytes: vec!["504B0304".into()],
321            diff_strategy: DiffStrategy::Opaque,
322            merge_strategy: MergeStrategy::ManualResolve,
323            storage_tier: StorageTier::Lfs,
324            lfs_threshold: Some(1024 * 1024),
325            metadata_schema: None,
326            structural_diff: false,
327            component_locking: true,
328            description: "Autodesk Fusion 360 design archive".into(),
329        },
330        ContentType {
331            id: "cad/creo".into(),
332            name: "PTC Creo / Pro-ENGINEER".into(),
333            domain: ContentDomain::Cad,
334            mime_types: vec!["application/x-creo".into()],
335            extensions: vec!["prt".into(), "asm".into(), "drw".into(), "frm".into()],
336            magic_bytes: vec![],
337            diff_strategy: DiffStrategy::Opaque,
338            merge_strategy: MergeStrategy::ManualResolve,
339            storage_tier: StorageTier::Lfs,
340            lfs_threshold: Some(1024 * 1024),
341            metadata_schema: None,
342            structural_diff: false,
343            component_locking: true,
344            description: "PTC Creo/Pro-E part/assembly/drawing".into(),
345        },
346        ContentType {
347            id: "cad/siemens-nx".into(),
348            name: "Siemens NX".into(),
349            domain: ContentDomain::Cad,
350            mime_types: vec!["application/x-siemens-nx".into()],
351            // NX part/assembly/drawing all use .prt (shared with Creo .prt)
352            extensions: vec!["prt".into()],
353            magic_bytes: vec![],
354            diff_strategy: DiffStrategy::Opaque,
355            merge_strategy: MergeStrategy::ManualResolve,
356            storage_tier: StorageTier::Lfs,
357            lfs_threshold: Some(1024 * 1024),
358            metadata_schema: None,
359            structural_diff: false,
360            component_locking: true,
361            description: "Siemens NX (Unigraphics) part/assembly/drawing".into(),
362        },
363        ContentType {
364            id: "cad/solid-edge".into(),
365            name: "Solid Edge".into(),
366            domain: ContentDomain::Cad,
367            mime_types: vec!["application/x-solid-edge".into()],
368            extensions: vec![
369                "par".into(),
370                "psm".into(),
371                "pwd".into(),
372                "asm".into(),
373                "dft".into(),
374            ],
375            magic_bytes: vec![],
376            diff_strategy: DiffStrategy::Opaque,
377            merge_strategy: MergeStrategy::ManualResolve,
378            storage_tier: StorageTier::Lfs,
379            lfs_threshold: Some(1024 * 1024),
380            metadata_schema: None,
381            structural_diff: false,
382            component_locking: true,
383            description: "Siemens Solid Edge part/sheet-metal/weldment/assembly/draft".into(),
384        },
385        ContentType {
386            id: "cad/rhino".into(),
387            name: "Rhino 3DM".into(),
388            domain: ContentDomain::Cad,
389            mime_types: vec!["model/vnd.rhino".into()],
390            extensions: vec!["3dm".into()],
391            magic_bytes: vec![],
392            diff_strategy: DiffStrategy::Opaque,
393            merge_strategy: MergeStrategy::ManualResolve,
394            storage_tier: StorageTier::Lfs,
395            lfs_threshold: Some(1024 * 1024),
396            metadata_schema: None,
397            structural_diff: false,
398            component_locking: true,
399            description: "Rhinoceros 3D NURBS model (openNURBS)".into(),
400        },
401        ContentType {
402            id: "cad/sketchup".into(),
403            name: "SketchUp Model".into(),
404            domain: ContentDomain::Cad,
405            mime_types: vec!["application/vnd.sketchup.skp".into()],
406            extensions: vec!["skp".into()],
407            magic_bytes: vec![],
408            diff_strategy: DiffStrategy::Opaque,
409            merge_strategy: MergeStrategy::ManualResolve,
410            storage_tier: StorageTier::Lfs,
411            lfs_threshold: Some(1024 * 1024),
412            metadata_schema: None,
413            structural_diff: false,
414            component_locking: false,
415            description: "Trimble SketchUp model".into(),
416        },
417        ContentType {
418            id: "cad/freecad".into(),
419            name: "FreeCAD Document".into(),
420            domain: ContentDomain::Cad,
421            mime_types: vec!["application/x-extension-fcstd".into()],
422            extensions: vec!["fcstd".into(), "fcstd1".into()],
423            magic_bytes: vec!["504B0304".into()],
424            diff_strategy: DiffStrategy::Structural,
425            merge_strategy: MergeStrategy::ComponentLevel,
426            storage_tier: StorageTier::Lfs,
427            lfs_threshold: Some(1024 * 1024),
428            metadata_schema: None,
429            structural_diff: true,
430            component_locking: true,
431            description: "FreeCAD parametric document (ZIP-based)".into(),
432        },
433        ContentType {
434            id: "cad/openscad".into(),
435            name: "OpenSCAD Script".into(),
436            domain: ContentDomain::Cad,
437            mime_types: vec!["application/x-openscad".into()],
438            extensions: vec!["scad".into()],
439            magic_bytes: vec![],
440            diff_strategy: DiffStrategy::Text,
441            merge_strategy: MergeStrategy::TextThreeWay,
442            storage_tier: StorageTier::Standard,
443            lfs_threshold: None,
444            metadata_schema: None,
445            structural_diff: false,
446            component_locking: false,
447            description: "OpenSCAD programmatic solid model source".into(),
448        },
449        ContentType {
450            id: "cad/parasolid".into(),
451            name: "Parasolid Model".into(),
452            domain: ContentDomain::Cad,
453            mime_types: vec!["application/x-parasolid".into()],
454            extensions: vec![
455                "x_t".into(),
456                "x_b".into(),
457                "xmt_txt".into(),
458                "xmt_bin".into(),
459            ],
460            magic_bytes: vec![],
461            diff_strategy: DiffStrategy::Opaque,
462            merge_strategy: MergeStrategy::ManualResolve,
463            storage_tier: StorageTier::Lfs,
464            lfs_threshold: Some(1024 * 1024),
465            metadata_schema: None,
466            structural_diff: false,
467            component_locking: true,
468            description: "Siemens Parasolid B-rep geometry kernel format".into(),
469        },
470        ContentType {
471            id: "cad/acis".into(),
472            name: "ACIS SAT".into(),
473            domain: ContentDomain::Cad,
474            mime_types: vec!["application/x-acis".into()],
475            extensions: vec!["sat".into(), "sab".into()],
476            magic_bytes: vec![],
477            diff_strategy: DiffStrategy::Opaque,
478            merge_strategy: MergeStrategy::ManualResolve,
479            storage_tier: StorageTier::Lfs,
480            lfs_threshold: Some(1024 * 1024),
481            metadata_schema: None,
482            structural_diff: false,
483            component_locking: true,
484            description: "Spatial ACIS solid model (SAT/SAB)".into(),
485        },
486        ContentType {
487            id: "cad/jt".into(),
488            name: "JT Visualization".into(),
489            domain: ContentDomain::Cad,
490            mime_types: vec!["model/jt".into()],
491            extensions: vec!["jt".into()],
492            magic_bytes: vec![],
493            diff_strategy: DiffStrategy::Opaque,
494            merge_strategy: MergeStrategy::ManualResolve,
495            storage_tier: StorageTier::Lfs,
496            lfs_threshold: Some(1024 * 1024),
497            metadata_schema: None,
498            structural_diff: false,
499            component_locking: true,
500            description: "ISO 14306 JT lightweight 3D visualization".into(),
501        },
502        // ── 3D modeling / mesh interchange ──
503        ContentType {
504            id: "cad/obj".into(),
505            name: "Wavefront OBJ".into(),
506            domain: ContentDomain::Cad,
507            mime_types: vec!["model/obj".into()],
508            extensions: vec!["obj".into()],
509            magic_bytes: vec![],
510            diff_strategy: DiffStrategy::Text,
511            merge_strategy: MergeStrategy::ManualResolve,
512            storage_tier: StorageTier::Lfs,
513            lfs_threshold: Some(1024 * 1024),
514            metadata_schema: None,
515            structural_diff: false,
516            component_locking: false,
517            description: "Wavefront OBJ geometry mesh".into(),
518        },
519        ContentType {
520            id: "cad/fbx".into(),
521            name: "Autodesk FBX".into(),
522            domain: ContentDomain::Cad,
523            mime_types: vec!["application/octet-stream".into()],
524            extensions: vec!["fbx".into()],
525            magic_bytes: vec!["4B6179646172612046425820".into()],
526            diff_strategy: DiffStrategy::Opaque,
527            merge_strategy: MergeStrategy::LastWriterWins,
528            storage_tier: StorageTier::Lfs,
529            lfs_threshold: Some(1024 * 1024),
530            metadata_schema: None,
531            structural_diff: false,
532            component_locking: false,
533            description: "Autodesk FBX scene/asset interchange".into(),
534        },
535        ContentType {
536            id: "cad/gltf".into(),
537            name: "glTF / GLB".into(),
538            domain: ContentDomain::Cad,
539            mime_types: vec!["model/gltf+json".into(), "model/gltf-binary".into()],
540            extensions: vec!["gltf".into(), "glb".into()],
541            magic_bytes: vec!["676C5446".into()],
542            diff_strategy: DiffStrategy::Structural,
543            merge_strategy: MergeStrategy::ComponentLevel,
544            storage_tier: StorageTier::Lfs,
545            lfs_threshold: Some(1024 * 1024),
546            metadata_schema: Some(serde_json::json!({
547                "type": "object",
548                "properties": {
549                    "mesh_count": {"type": "integer"},
550                    "material_count": {"type": "integer"},
551                    "animation_count": {"type": "integer"},
552                    "generator": {"type": "string"}
553                }
554            })),
555            structural_diff: true,
556            component_locking: false,
557            description: "Khronos glTF 2.0 runtime 3D asset (text/binary)".into(),
558        },
559        ContentType {
560            id: "cad/collada".into(),
561            name: "COLLADA".into(),
562            domain: ContentDomain::Cad,
563            mime_types: vec!["model/vnd.collada+xml".into()],
564            extensions: vec!["dae".into()],
565            magic_bytes: vec![],
566            diff_strategy: DiffStrategy::Structural,
567            merge_strategy: MergeStrategy::ManualResolve,
568            storage_tier: StorageTier::Lfs,
569            lfs_threshold: Some(1024 * 1024),
570            metadata_schema: None,
571            structural_diff: true,
572            component_locking: false,
573            description: "COLLADA (.dae) XML 3D asset interchange".into(),
574        },
575        ContentType {
576            id: "cad/usd".into(),
577            name: "Universal Scene Description".into(),
578            domain: ContentDomain::Cad,
579            mime_types: vec!["model/vnd.usd".into()],
580            extensions: vec!["usd".into(), "usda".into(), "usdc".into(), "usdz".into()],
581            magic_bytes: vec![],
582            diff_strategy: DiffStrategy::Structural,
583            merge_strategy: MergeStrategy::ComponentLevel,
584            storage_tier: StorageTier::Lfs,
585            lfs_threshold: Some(1024 * 1024),
586            metadata_schema: None,
587            structural_diff: true,
588            component_locking: true,
589            description: "Pixar OpenUSD scene description (ascii/crate/zip)".into(),
590        },
591        ContentType {
592            id: "cad/ply".into(),
593            name: "Polygon File Format".into(),
594            domain: ContentDomain::Cad,
595            mime_types: vec!["application/x-ply".into()],
596            extensions: vec!["ply".into()],
597            magic_bytes: vec!["706C79".into()],
598            diff_strategy: DiffStrategy::Opaque,
599            merge_strategy: MergeStrategy::LastWriterWins,
600            storage_tier: StorageTier::Lfs,
601            lfs_threshold: Some(1024 * 1024),
602            metadata_schema: None,
603            structural_diff: false,
604            component_locking: false,
605            description: "Stanford PLY polygon/point-cloud mesh".into(),
606        },
607        ContentType {
608            id: "cad/blender".into(),
609            name: "Blender Scene".into(),
610            domain: ContentDomain::Cad,
611            mime_types: vec!["application/x-blender".into()],
612            extensions: vec!["blend".into()],
613            magic_bytes: vec!["424C454E444552".into()],
614            diff_strategy: DiffStrategy::Opaque,
615            merge_strategy: MergeStrategy::ManualResolve,
616            storage_tier: StorageTier::Lfs,
617            lfs_threshold: Some(1024 * 1024),
618            metadata_schema: None,
619            structural_diff: false,
620            component_locking: false,
621            description: "Blender .blend scene file".into(),
622        },
623        ContentType {
624            id: "cad/alembic".into(),
625            name: "Alembic Cache".into(),
626            domain: ContentDomain::Cad,
627            mime_types: vec!["application/x-alembic".into()],
628            extensions: vec!["abc".into()],
629            magic_bytes: vec!["4F6761776100".into()],
630            diff_strategy: DiffStrategy::Opaque,
631            merge_strategy: MergeStrategy::LastWriterWins,
632            storage_tier: StorageTier::Lfs,
633            lfs_threshold: Some(1024 * 1024),
634            metadata_schema: None,
635            structural_diff: false,
636            component_locking: false,
637            description: "Alembic baked geometry/animation cache".into(),
638        },
639        ContentType {
640            id: "cad/3ds".into(),
641            name: "Autodesk 3DS".into(),
642            domain: ContentDomain::Cad,
643            mime_types: vec!["application/x-3ds".into()],
644            extensions: vec!["3ds".into()],
645            magic_bytes: vec!["4D4D".into()],
646            diff_strategy: DiffStrategy::Opaque,
647            merge_strategy: MergeStrategy::LastWriterWins,
648            storage_tier: StorageTier::Lfs,
649            lfs_threshold: Some(512 * 1024),
650            metadata_schema: None,
651            structural_diff: false,
652            component_locking: false,
653            description: "Autodesk 3D Studio legacy mesh".into(),
654        },
655        // ── EDA ──
656        ContentType {
657            id: "eda/kicad-pcb".into(),
658            name: "KiCad PCB Layout".into(),
659            domain: ContentDomain::Eda,
660            mime_types: vec!["application/x-kicad-pcb".into()],
661            extensions: vec!["kicad_pcb".into()],
662            magic_bytes: vec![],
663            diff_strategy: DiffStrategy::Structural,
664            merge_strategy: MergeStrategy::ComponentLevel,
665            storage_tier: StorageTier::Standard,
666            lfs_threshold: Some(50 * 1024 * 1024),
667            metadata_schema: Some(serde_json::json!({
668                "type": "object",
669                "properties": {
670                    "layers": {"type": "integer"},
671                    "component_count": {"type": "integer"},
672                    "net_count": {"type": "integer"},
673                    "board_dimensions": {"type": "object", "properties": {
674                        "width_mm": {"type": "number"},
675                        "height_mm": {"type": "number"}
676                    }}
677                }
678            })),
679            structural_diff: true,
680            component_locking: true,
681            description: "KiCad PCB layout (S-expression format)".into(),
682        },
683        ContentType {
684            id: "eda/kicad-sch".into(),
685            name: "KiCad Schematic".into(),
686            domain: ContentDomain::Eda,
687            mime_types: vec!["application/x-kicad-schematic".into()],
688            extensions: vec!["kicad_sch".into()],
689            magic_bytes: vec![],
690            diff_strategy: DiffStrategy::Structural,
691            merge_strategy: MergeStrategy::ComponentLevel,
692            storage_tier: StorageTier::Standard,
693            lfs_threshold: None,
694            metadata_schema: None,
695            structural_diff: true,
696            component_locking: true,
697            description: "KiCad schematic (S-expression format)".into(),
698        },
699        ContentType {
700            id: "eda/gerber".into(),
701            name: "Gerber PCB Fabrication".into(),
702            domain: ContentDomain::Eda,
703            mime_types: vec!["application/x-gerber".into()],
704            extensions: vec![
705                "gbr".into(),
706                "ger".into(),
707                "gtl".into(),
708                "gbl".into(),
709                "gts".into(),
710                "gbs".into(),
711            ],
712            magic_bytes: vec![],
713            diff_strategy: DiffStrategy::Text,
714            merge_strategy: MergeStrategy::LastWriterWins,
715            storage_tier: StorageTier::Standard,
716            lfs_threshold: None,
717            metadata_schema: None,
718            structural_diff: false,
719            component_locking: false,
720            description: "Gerber RS-274X PCB fabrication data".into(),
721        },
722        ContentType {
723            id: "eda/spice".into(),
724            name: "SPICE Netlist".into(),
725            domain: ContentDomain::Eda,
726            mime_types: vec!["text/x-spice".into()],
727            extensions: vec!["spice".into(), "sp".into(), "cir".into()],
728            magic_bytes: vec![],
729            diff_strategy: DiffStrategy::Text,
730            merge_strategy: MergeStrategy::TextThreeWay,
731            storage_tier: StorageTier::Standard,
732            lfs_threshold: None,
733            metadata_schema: None,
734            structural_diff: false,
735            component_locking: false,
736            description: "SPICE circuit simulation netlist".into(),
737        },
738        // ── EDA (native tools, HDL, layout) ──
739        ContentType {
740            id: "eda/altium-sch".into(),
741            name: "Altium Schematic".into(),
742            domain: ContentDomain::Eda,
743            mime_types: vec!["application/x-altium-schdoc".into()],
744            extensions: vec!["schdoc".into()],
745            magic_bytes: vec![],
746            diff_strategy: DiffStrategy::Opaque,
747            merge_strategy: MergeStrategy::ManualResolve,
748            storage_tier: StorageTier::Lfs,
749            lfs_threshold: Some(1024 * 1024),
750            metadata_schema: None,
751            structural_diff: false,
752            component_locking: true,
753            description: "Altium Designer schematic document".into(),
754        },
755        ContentType {
756            id: "eda/altium-pcb".into(),
757            name: "Altium PCB".into(),
758            domain: ContentDomain::Eda,
759            mime_types: vec!["application/x-altium-pcbdoc".into()],
760            extensions: vec!["pcbdoc".into()],
761            magic_bytes: vec![],
762            diff_strategy: DiffStrategy::Opaque,
763            merge_strategy: MergeStrategy::ManualResolve,
764            storage_tier: StorageTier::Lfs,
765            lfs_threshold: Some(1024 * 1024),
766            metadata_schema: None,
767            structural_diff: false,
768            component_locking: true,
769            description: "Altium Designer PCB layout document".into(),
770        },
771        ContentType {
772            id: "eda/altium-project".into(),
773            name: "Altium Project".into(),
774            domain: ContentDomain::Eda,
775            mime_types: vec!["application/x-altium-project".into()],
776            extensions: vec!["prjpcb".into(), "prjfpg".into()],
777            magic_bytes: vec![],
778            diff_strategy: DiffStrategy::Text,
779            merge_strategy: MergeStrategy::ManualResolve,
780            storage_tier: StorageTier::Standard,
781            lfs_threshold: None,
782            metadata_schema: None,
783            structural_diff: false,
784            component_locking: false,
785            description: "Altium Designer project file".into(),
786        },
787        ContentType {
788            id: "eda/eagle".into(),
789            name: "EAGLE Design".into(),
790            domain: ContentDomain::Eda,
791            mime_types: vec!["application/x-eagle".into()],
792            extensions: vec!["brd".into(), "lbr".into()],
793            magic_bytes: vec![],
794            diff_strategy: DiffStrategy::Structural,
795            merge_strategy: MergeStrategy::ComponentLevel,
796            storage_tier: StorageTier::Standard,
797            lfs_threshold: Some(20 * 1024 * 1024),
798            metadata_schema: None,
799            structural_diff: true,
800            component_locking: true,
801            description: "Autodesk EAGLE board/library (XML)".into(),
802        },
803        ContentType {
804            id: "eda/orcad".into(),
805            name: "OrCAD Design".into(),
806            domain: ContentDomain::Eda,
807            mime_types: vec!["application/x-orcad".into()],
808            extensions: vec!["dsn".into(), "opj".into(), "olb".into()],
809            magic_bytes: vec![],
810            diff_strategy: DiffStrategy::Opaque,
811            merge_strategy: MergeStrategy::ManualResolve,
812            storage_tier: StorageTier::Lfs,
813            lfs_threshold: Some(1024 * 1024),
814            metadata_schema: None,
815            structural_diff: false,
816            component_locking: true,
817            description: "Cadence OrCAD schematic/project/library".into(),
818        },
819        ContentType {
820            id: "eda/verilog".into(),
821            name: "Verilog / SystemVerilog".into(),
822            domain: ContentDomain::Eda,
823            mime_types: vec!["text/x-verilog".into()],
824            extensions: vec!["v".into(), "sv".into(), "svh".into(), "vh".into()],
825            magic_bytes: vec![],
826            diff_strategy: DiffStrategy::Text,
827            merge_strategy: MergeStrategy::TextThreeWay,
828            storage_tier: StorageTier::Standard,
829            lfs_threshold: None,
830            metadata_schema: None,
831            structural_diff: false,
832            component_locking: false,
833            description: "Verilog/SystemVerilog HDL source".into(),
834        },
835        ContentType {
836            id: "eda/vhdl".into(),
837            name: "VHDL".into(),
838            domain: ContentDomain::Eda,
839            mime_types: vec!["text/x-vhdl".into()],
840            extensions: vec!["vhd".into(), "vhdl".into()],
841            magic_bytes: vec![],
842            diff_strategy: DiffStrategy::Text,
843            merge_strategy: MergeStrategy::TextThreeWay,
844            storage_tier: StorageTier::Standard,
845            lfs_threshold: None,
846            metadata_schema: None,
847            structural_diff: false,
848            component_locking: false,
849            description: "VHDL hardware description language source".into(),
850        },
851        ContentType {
852            id: "eda/excellon".into(),
853            name: "Excellon Drill".into(),
854            domain: ContentDomain::Eda,
855            mime_types: vec!["application/x-excellon".into()],
856            extensions: vec!["drl".into(), "xln".into(), "exc".into()],
857            magic_bytes: vec![],
858            diff_strategy: DiffStrategy::Text,
859            merge_strategy: MergeStrategy::LastWriterWins,
860            storage_tier: StorageTier::Standard,
861            lfs_threshold: None,
862            metadata_schema: None,
863            structural_diff: false,
864            component_locking: false,
865            description: "Excellon NC drill/route data for PCB fabrication".into(),
866        },
867        ContentType {
868            id: "eda/gdsii".into(),
869            name: "GDSII Layout".into(),
870            domain: ContentDomain::Eda,
871            mime_types: vec!["application/x-gdsii".into()],
872            extensions: vec!["gds".into(), "gds2".into(), "gdsii".into()],
873            magic_bytes: vec![],
874            diff_strategy: DiffStrategy::Opaque,
875            merge_strategy: MergeStrategy::ManualResolve,
876            storage_tier: StorageTier::Lfs,
877            lfs_threshold: Some(1024 * 1024),
878            metadata_schema: None,
879            structural_diff: false,
880            component_locking: true,
881            description: "Calma GDSII IC mask layout stream".into(),
882        },
883        ContentType {
884            id: "eda/oasis".into(),
885            name: "OASIS Layout".into(),
886            domain: ContentDomain::Eda,
887            mime_types: vec!["application/x-oasis".into()],
888            extensions: vec!["oas".into()],
889            magic_bytes: vec![],
890            diff_strategy: DiffStrategy::Opaque,
891            merge_strategy: MergeStrategy::ManualResolve,
892            storage_tier: StorageTier::Lfs,
893            lfs_threshold: Some(1024 * 1024),
894            metadata_schema: None,
895            structural_diff: false,
896            component_locking: true,
897            description: "SEMI OASIS IC mask layout (GDSII successor)".into(),
898        },
899        ContentType {
900            id: "eda/ipc2581".into(),
901            name: "IPC-2581".into(),
902            domain: ContentDomain::Eda,
903            mime_types: vec!["application/xml".into()],
904            extensions: vec!["cvg".into(), "xml2581".into()],
905            magic_bytes: vec![],
906            diff_strategy: DiffStrategy::Structural,
907            merge_strategy: MergeStrategy::ComponentLevel,
908            storage_tier: StorageTier::Standard,
909            lfs_threshold: Some(50 * 1024 * 1024),
910            metadata_schema: None,
911            structural_diff: true,
912            component_locking: true,
913            description: "IPC-2581 open PCB manufacturing data (XML)".into(),
914        },
915        ContentType {
916            id: "eda/touchstone".into(),
917            name: "Touchstone S-Parameters".into(),
918            domain: ContentDomain::Eda,
919            mime_types: vec!["text/x-touchstone".into()],
920            extensions: vec![
921                "s1p".into(),
922                "s2p".into(),
923                "s3p".into(),
924                "s4p".into(),
925                "snp".into(),
926            ],
927            magic_bytes: vec![],
928            diff_strategy: DiffStrategy::Text,
929            merge_strategy: MergeStrategy::LastWriterWins,
930            storage_tier: StorageTier::Standard,
931            lfs_threshold: None,
932            metadata_schema: None,
933            structural_diff: false,
934            component_locking: false,
935            description: "Touchstone RF/microwave network parameter data".into(),
936        },
937        ContentType {
938            id: "eda/lef-def".into(),
939            name: "LEF / DEF".into(),
940            domain: ContentDomain::Eda,
941            mime_types: vec!["text/x-lefdef".into()],
942            extensions: vec!["lef".into(), "def".into()],
943            magic_bytes: vec![],
944            diff_strategy: DiffStrategy::Text,
945            merge_strategy: MergeStrategy::ManualResolve,
946            storage_tier: StorageTier::Standard,
947            lfs_threshold: Some(50 * 1024 * 1024),
948            metadata_schema: None,
949            structural_diff: false,
950            component_locking: false,
951            description: "Library/Design Exchange Format for IC place-and-route".into(),
952        },
953        ContentType {
954            id: "eda/spef".into(),
955            name: "SPEF Parasitics".into(),
956            domain: ContentDomain::Eda,
957            mime_types: vec!["text/x-spef".into()],
958            extensions: vec!["spef".into()],
959            magic_bytes: vec![],
960            diff_strategy: DiffStrategy::Text,
961            merge_strategy: MergeStrategy::LastWriterWins,
962            storage_tier: StorageTier::Standard,
963            lfs_threshold: Some(50 * 1024 * 1024),
964            metadata_schema: None,
965            structural_diff: false,
966            component_locking: false,
967            description: "Standard Parasitic Exchange Format (IC timing)".into(),
968        },
969        // ── CAM (toolpaths / NC machining) ──
970        ContentType {
971            id: "cam/gcode".into(),
972            name: "G-code Toolpath".into(),
973            domain: ContentDomain::Cam,
974            mime_types: vec!["text/x-gcode".into()],
975            extensions: vec![
976                "gcode".into(),
977                "gco".into(),
978                "nc".into(),
979                "tap".into(),
980                "cnc".into(),
981                "ngc".into(),
982                "mpf".into(),
983                "g".into(),
984            ],
985            magic_bytes: vec![],
986            diff_strategy: DiffStrategy::Text,
987            merge_strategy: MergeStrategy::LastWriterWins,
988            storage_tier: StorageTier::Standard,
989            lfs_threshold: Some(50 * 1024 * 1024),
990            metadata_schema: Some(serde_json::json!({
991                "type": "object",
992                "properties": {
993                    "line_count": {"type": "integer"},
994                    "flavor": {"type": "string"},
995                    "machine": {"type": "string"},
996                    "estimated_time_s": {"type": "number"}
997                }
998            })),
999            structural_diff: false,
1000            component_locking: false,
1001            description: "RS-274 G-code CNC/3D-printer toolpath".into(),
1002        },
1003        ContentType {
1004            id: "cam/step-nc".into(),
1005            name: "STEP-NC".into(),
1006            domain: ContentDomain::Cam,
1007            mime_types: vec!["model/step-nc".into()],
1008            extensions: vec!["stpnc".into(), "238".into()],
1009            magic_bytes: vec![],
1010            diff_strategy: DiffStrategy::Structural,
1011            merge_strategy: MergeStrategy::ManualResolve,
1012            storage_tier: StorageTier::Lfs,
1013            lfs_threshold: Some(1024 * 1024),
1014            metadata_schema: None,
1015            structural_diff: true,
1016            component_locking: true,
1017            description: "ISO 14649 STEP-NC machining data".into(),
1018        },
1019        ContentType {
1020            id: "cam/apt".into(),
1021            name: "APT CL Data".into(),
1022            domain: ContentDomain::Cam,
1023            mime_types: vec!["text/x-apt".into()],
1024            extensions: vec!["apt".into(), "cls".into(), "cl".into()],
1025            magic_bytes: vec![],
1026            diff_strategy: DiffStrategy::Text,
1027            merge_strategy: MergeStrategy::LastWriterWins,
1028            storage_tier: StorageTier::Standard,
1029            lfs_threshold: None,
1030            metadata_schema: None,
1031            structural_diff: false,
1032            component_locking: false,
1033            description: "APT cutter-location source / CL data".into(),
1034        },
1035        ContentType {
1036            id: "cam/mastercam".into(),
1037            name: "Mastercam".into(),
1038            domain: ContentDomain::Cam,
1039            mime_types: vec!["application/x-mastercam".into()],
1040            extensions: vec![
1041                "mcam".into(),
1042                "mcx".into(),
1043                "mcx-7".into(),
1044                "mcx-8".into(),
1045                "mcx-9".into(),
1046            ],
1047            magic_bytes: vec![],
1048            diff_strategy: DiffStrategy::Opaque,
1049            merge_strategy: MergeStrategy::ManualResolve,
1050            storage_tier: StorageTier::Lfs,
1051            lfs_threshold: Some(1024 * 1024),
1052            metadata_schema: None,
1053            structural_diff: false,
1054            component_locking: true,
1055            description: "Mastercam part/toolpath document".into(),
1056        },
1057        // ── Manuscripts ──
1058        ContentType {
1059            id: "manuscript/latex".into(),
1060            name: "LaTeX Document".into(),
1061            domain: ContentDomain::Manuscript,
1062            mime_types: vec!["application/x-latex".into(), "text/x-tex".into()],
1063            extensions: vec!["tex".into(), "latex".into(), "ltx".into()],
1064            magic_bytes: vec![],
1065            diff_strategy: DiffStrategy::Text,
1066            merge_strategy: MergeStrategy::TextThreeWay,
1067            storage_tier: StorageTier::Standard,
1068            lfs_threshold: None,
1069            metadata_schema: Some(serde_json::json!({
1070                "type": "object",
1071                "properties": {
1072                    "document_class": {"type": "string"},
1073                    "word_count": {"type": "integer"},
1074                    "bibliography_entries": {"type": "integer"}
1075                }
1076            })),
1077            structural_diff: false,
1078            component_locking: false,
1079            description: "LaTeX typesetting source".into(),
1080        },
1081        ContentType {
1082            id: "manuscript/docx".into(),
1083            name: "Word Document".into(),
1084            domain: ContentDomain::Manuscript,
1085            mime_types: vec![
1086                "application/vnd.openxmlformats-officedocument.wordprocessingml.document".into(),
1087            ],
1088            extensions: vec!["docx".into()],
1089            magic_bytes: vec!["504B0304".into()], // ZIP
1090            diff_strategy: DiffStrategy::Structural,
1091            merge_strategy: MergeStrategy::ManualResolve,
1092            storage_tier: StorageTier::Lfs,
1093            lfs_threshold: Some(5 * 1024 * 1024),
1094            metadata_schema: Some(serde_json::json!({
1095                "type": "object",
1096                "properties": {
1097                    "page_count": {"type": "integer"},
1098                    "word_count": {"type": "integer"},
1099                    "author": {"type": "string"},
1100                    "revision": {"type": "integer"}
1101                }
1102            })),
1103            structural_diff: true,
1104            component_locking: false,
1105            description: "Microsoft Word OOXML document".into(),
1106        },
1107        ContentType {
1108            id: "manuscript/typst".into(),
1109            name: "Typst Document".into(),
1110            domain: ContentDomain::Manuscript,
1111            mime_types: vec!["text/x-typst".into()],
1112            extensions: vec!["typ".into()],
1113            magic_bytes: vec![],
1114            diff_strategy: DiffStrategy::Text,
1115            merge_strategy: MergeStrategy::TextThreeWay,
1116            storage_tier: StorageTier::Standard,
1117            lfs_threshold: None,
1118            metadata_schema: None,
1119            structural_diff: false,
1120            component_locking: false,
1121            description: "Typst markup language source".into(),
1122        },
1123        ContentType {
1124            id: "manuscript/asciidoc".into(),
1125            name: "AsciiDoc".into(),
1126            domain: ContentDomain::Manuscript,
1127            mime_types: vec!["text/asciidoc".into()],
1128            extensions: vec!["adoc".into(), "asciidoc".into(), "asc".into()],
1129            magic_bytes: vec![],
1130            diff_strategy: DiffStrategy::Text,
1131            merge_strategy: MergeStrategy::TextThreeWay,
1132            storage_tier: StorageTier::Standard,
1133            lfs_threshold: None,
1134            metadata_schema: None,
1135            structural_diff: false,
1136            component_locking: false,
1137            description: "AsciiDoc markup language".into(),
1138        },
1139        // ── Databases ──
1140        ContentType {
1141            id: "db/sqlite".into(),
1142            name: "SQLite Database".into(),
1143            domain: ContentDomain::Database,
1144            mime_types: vec![
1145                "application/vnd.sqlite3".into(),
1146                "application/x-sqlite3".into(),
1147            ],
1148            extensions: vec!["sqlite".into(), "sqlite3".into(), "db".into()],
1149            magic_bytes: vec!["53514C69746520666F726D6174".into()], // "SQLite format"
1150            diff_strategy: DiffStrategy::Semantic,
1151            merge_strategy: MergeStrategy::SchemaAware,
1152            storage_tier: StorageTier::Chunked,
1153            lfs_threshold: Some(10 * 1024 * 1024),
1154            metadata_schema: Some(serde_json::json!({
1155                "type": "object",
1156                "properties": {
1157                    "table_count": {"type": "integer"},
1158                    "row_count": {"type": "integer"},
1159                    "schema_version": {"type": "string"},
1160                    "page_size": {"type": "integer"}
1161                }
1162            })),
1163            structural_diff: true,
1164            component_locking: true,
1165            description: "SQLite embedded database file".into(),
1166        },
1167        ContentType {
1168            id: "db/csv".into(),
1169            name: "CSV Data".into(),
1170            domain: ContentDomain::Database,
1171            mime_types: vec!["text/csv".into()],
1172            extensions: vec!["csv".into(), "tsv".into()],
1173            magic_bytes: vec![],
1174            diff_strategy: DiffStrategy::Structural,
1175            merge_strategy: MergeStrategy::SchemaAware,
1176            storage_tier: StorageTier::Standard,
1177            lfs_threshold: Some(50 * 1024 * 1024),
1178            metadata_schema: Some(serde_json::json!({
1179                "type": "object",
1180                "properties": {
1181                    "column_count": {"type": "integer"},
1182                    "row_count": {"type": "integer"},
1183                    "delimiter": {"type": "string"},
1184                    "has_header": {"type": "boolean"}
1185                }
1186            })),
1187            structural_diff: true,
1188            component_locking: false,
1189            description: "Comma/tab-separated values".into(),
1190        },
1191        ContentType {
1192            id: "db/parquet".into(),
1193            name: "Apache Parquet".into(),
1194            domain: ContentDomain::Database,
1195            mime_types: vec!["application/x-parquet".into()],
1196            extensions: vec!["parquet".into()],
1197            magic_bytes: vec!["50415231".into()], // "PAR1"
1198            diff_strategy: DiffStrategy::Semantic,
1199            merge_strategy: MergeStrategy::SchemaAware,
1200            storage_tier: StorageTier::Lfs,
1201            lfs_threshold: Some(10 * 1024 * 1024),
1202            metadata_schema: Some(serde_json::json!({
1203                "type": "object",
1204                "properties": {
1205                    "row_groups": {"type": "integer"},
1206                    "row_count": {"type": "integer"},
1207                    "column_count": {"type": "integer"},
1208                    "compression": {"type": "string"}
1209                }
1210            })),
1211            structural_diff: true,
1212            component_locking: false,
1213            description: "Apache Parquet columnar storage".into(),
1214        },
1215        ContentType {
1216            id: "db/sql-migration".into(),
1217            name: "SQL Migration".into(),
1218            domain: ContentDomain::Database,
1219            mime_types: vec!["application/sql".into()],
1220            extensions: vec!["sql".into()],
1221            magic_bytes: vec![],
1222            diff_strategy: DiffStrategy::Text,
1223            merge_strategy: MergeStrategy::AppendOnly,
1224            storage_tier: StorageTier::Standard,
1225            lfs_threshold: None,
1226            metadata_schema: Some(serde_json::json!({
1227                "type": "object",
1228                "properties": {
1229                    "direction": {"type": "string", "enum": ["up", "down"]},
1230                    "version": {"type": "string"},
1231                    "idempotent": {"type": "boolean"}
1232                }
1233            })),
1234            structural_diff: false,
1235            component_locking: false,
1236            description: "SQL database migration script".into(),
1237        },
1238        // ── Scientific ──
1239        ContentType {
1240            id: "scientific/hdf5".into(),
1241            name: "HDF5 Dataset".into(),
1242            domain: ContentDomain::Scientific,
1243            mime_types: vec!["application/x-hdf5".into()],
1244            extensions: vec!["h5".into(), "hdf5".into(), "he5".into()],
1245            magic_bytes: vec!["894844460D0A1A0A".into()],
1246            diff_strategy: DiffStrategy::Semantic,
1247            merge_strategy: MergeStrategy::SchemaAware,
1248            storage_tier: StorageTier::Chunked,
1249            lfs_threshold: Some(10 * 1024 * 1024),
1250            metadata_schema: Some(serde_json::json!({
1251                "type": "object",
1252                "properties": {
1253                    "dataset_count": {"type": "integer"},
1254                    "total_size": {"type": "integer"},
1255                    "compression": {"type": "string"}
1256                }
1257            })),
1258            structural_diff: true,
1259            component_locking: true,
1260            description: "Hierarchical Data Format 5 for scientific datasets".into(),
1261        },
1262        ContentType {
1263            id: "scientific/fits".into(),
1264            name: "FITS Astronomical Data".into(),
1265            domain: ContentDomain::Scientific,
1266            mime_types: vec!["application/fits".into()],
1267            extensions: vec!["fits".into(), "fit".into()],
1268            magic_bytes: vec!["53494D504C45".into()], // "SIMPLE"
1269            diff_strategy: DiffStrategy::Opaque,
1270            merge_strategy: MergeStrategy::LastWriterWins,
1271            storage_tier: StorageTier::Lfs,
1272            lfs_threshold: Some(5 * 1024 * 1024),
1273            metadata_schema: None,
1274            structural_diff: false,
1275            component_locking: false,
1276            description: "Flexible Image Transport System (astronomy)".into(),
1277        },
1278        ContentType {
1279            id: "scientific/jupyter".into(),
1280            name: "Jupyter Notebook".into(),
1281            domain: ContentDomain::Scientific,
1282            mime_types: vec!["application/x-ipynb+json".into()],
1283            extensions: vec!["ipynb".into()],
1284            magic_bytes: vec![],
1285            diff_strategy: DiffStrategy::Structural,
1286            merge_strategy: MergeStrategy::ComponentLevel,
1287            storage_tier: StorageTier::Standard,
1288            lfs_threshold: Some(50 * 1024 * 1024),
1289            metadata_schema: Some(serde_json::json!({
1290                "type": "object",
1291                "properties": {
1292                    "cell_count": {"type": "integer"},
1293                    "kernel": {"type": "string"},
1294                    "language": {"type": "string"}
1295                }
1296            })),
1297            structural_diff: true,
1298            component_locking: true,
1299            description: "Jupyter/IPython notebook (cell-level versioning)".into(),
1300        },
1301        // ── Media ──
1302        ContentType {
1303            id: "media/image".into(),
1304            name: "Image Asset".into(),
1305            domain: ContentDomain::Media,
1306            mime_types: vec![
1307                "image/png".into(),
1308                "image/jpeg".into(),
1309                "image/webp".into(),
1310                "image/tiff".into(),
1311            ],
1312            extensions: vec![
1313                "png".into(),
1314                "jpg".into(),
1315                "jpeg".into(),
1316                "webp".into(),
1317                "tiff".into(),
1318                "tif".into(),
1319                "bmp".into(),
1320            ],
1321            magic_bytes: vec!["89504E47".into(), "FFD8FF".into()], // PNG, JPEG
1322            diff_strategy: DiffStrategy::Opaque,
1323            merge_strategy: MergeStrategy::LastWriterWins,
1324            storage_tier: StorageTier::Lfs,
1325            lfs_threshold: Some(256 * 1024),
1326            metadata_schema: Some(serde_json::json!({
1327                "type": "object",
1328                "properties": {
1329                    "width": {"type": "integer"},
1330                    "height": {"type": "integer"},
1331                    "format": {"type": "string"},
1332                    "color_space": {"type": "string"}
1333                }
1334            })),
1335            structural_diff: false,
1336            component_locking: false,
1337            description: "Raster image asset".into(),
1338        },
1339        ContentType {
1340            id: "media/video".into(),
1341            name: "Video Asset".into(),
1342            domain: ContentDomain::Media,
1343            mime_types: vec![
1344                "video/mp4".into(),
1345                "video/webm".into(),
1346                "video/quicktime".into(),
1347            ],
1348            extensions: vec![
1349                "mp4".into(),
1350                "webm".into(),
1351                "mov".into(),
1352                "mkv".into(),
1353                "avi".into(),
1354            ],
1355            magic_bytes: vec![],
1356            diff_strategy: DiffStrategy::Opaque,
1357            merge_strategy: MergeStrategy::LastWriterWins,
1358            storage_tier: StorageTier::External,
1359            lfs_threshold: Some(10 * 1024 * 1024),
1360            metadata_schema: Some(serde_json::json!({
1361                "type": "object",
1362                "properties": {
1363                    "duration_seconds": {"type": "number"},
1364                    "resolution": {"type": "string"},
1365                    "codec": {"type": "string"}
1366                }
1367            })),
1368            structural_diff: false,
1369            component_locking: false,
1370            description: "Video media asset".into(),
1371        },
1372        ContentType {
1373            id: "media/audio".into(),
1374            name: "Audio Asset".into(),
1375            domain: ContentDomain::Media,
1376            mime_types: vec![
1377                "audio/mpeg".into(),
1378                "audio/wav".into(),
1379                "audio/flac".into(),
1380                "audio/ogg".into(),
1381            ],
1382            extensions: vec![
1383                "mp3".into(),
1384                "wav".into(),
1385                "flac".into(),
1386                "ogg".into(),
1387                "aac".into(),
1388            ],
1389            magic_bytes: vec!["494433".into(), "52494646".into()], // "ID3", "RIFF"
1390            diff_strategy: DiffStrategy::Opaque,
1391            merge_strategy: MergeStrategy::LastWriterWins,
1392            storage_tier: StorageTier::Lfs,
1393            lfs_threshold: Some(1024 * 1024),
1394            metadata_schema: None,
1395            structural_diff: false,
1396            component_locking: false,
1397            description: "Audio media asset".into(),
1398        },
1399        // ── Geospatial ──
1400        ContentType {
1401            id: "geo/geojson".into(),
1402            name: "GeoJSON".into(),
1403            domain: ContentDomain::Geospatial,
1404            mime_types: vec!["application/geo+json".into()],
1405            extensions: vec!["geojson".into()],
1406            magic_bytes: vec![],
1407            diff_strategy: DiffStrategy::Structural,
1408            merge_strategy: MergeStrategy::ComponentLevel,
1409            storage_tier: StorageTier::Standard,
1410            lfs_threshold: Some(50 * 1024 * 1024),
1411            metadata_schema: Some(serde_json::json!({
1412                "type": "object",
1413                "properties": {
1414                    "feature_count": {"type": "integer"},
1415                    "geometry_types": {"type": "array", "items": {"type": "string"}},
1416                    "crs": {"type": "string"}
1417                }
1418            })),
1419            structural_diff: true,
1420            component_locking: false,
1421            description: "RFC 7946 GeoJSON geographic data".into(),
1422        },
1423        ContentType {
1424            id: "geo/shapefile".into(),
1425            name: "Shapefile".into(),
1426            domain: ContentDomain::Geospatial,
1427            mime_types: vec!["application/x-shapefile".into()],
1428            extensions: vec!["shp".into(), "shx".into(), "dbf".into(), "prj".into()],
1429            magic_bytes: vec![],
1430            diff_strategy: DiffStrategy::Opaque,
1431            merge_strategy: MergeStrategy::ManualResolve,
1432            storage_tier: StorageTier::Lfs,
1433            lfs_threshold: Some(5 * 1024 * 1024),
1434            metadata_schema: None,
1435            structural_diff: false,
1436            component_locking: false,
1437            description: "ESRI Shapefile geospatial vector data".into(),
1438        },
1439        // ── Legal / Financial ──
1440        ContentType {
1441            id: "legal/pdf".into(),
1442            name: "PDF Document".into(),
1443            domain: ContentDomain::Legal,
1444            mime_types: vec!["application/pdf".into()],
1445            extensions: vec!["pdf".into()],
1446            magic_bytes: vec!["25504446".into()], // "%PDF"
1447            diff_strategy: DiffStrategy::Opaque,
1448            merge_strategy: MergeStrategy::ManualResolve,
1449            storage_tier: StorageTier::Lfs,
1450            lfs_threshold: Some(1024 * 1024),
1451            metadata_schema: Some(serde_json::json!({
1452                "type": "object",
1453                "properties": {
1454                    "page_count": {"type": "integer"},
1455                    "signed": {"type": "boolean"},
1456                    "version": {"type": "string"}
1457                }
1458            })),
1459            structural_diff: false,
1460            component_locking: false,
1461            description: "Portable Document Format".into(),
1462        },
1463        ContentType {
1464            id: "financial/xlsx".into(),
1465            name: "Excel Spreadsheet".into(),
1466            domain: ContentDomain::Financial,
1467            mime_types: vec![
1468                "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet".into(),
1469            ],
1470            extensions: vec!["xlsx".into()],
1471            magic_bytes: vec!["504B0304".into()], // ZIP
1472            diff_strategy: DiffStrategy::Structural,
1473            merge_strategy: MergeStrategy::SchemaAware,
1474            storage_tier: StorageTier::Lfs,
1475            lfs_threshold: Some(5 * 1024 * 1024),
1476            metadata_schema: Some(serde_json::json!({
1477                "type": "object",
1478                "properties": {
1479                    "sheet_count": {"type": "integer"},
1480                    "row_count": {"type": "integer"},
1481                    "has_macros": {"type": "boolean"}
1482                }
1483            })),
1484            structural_diff: true,
1485            component_locking: true,
1486            description: "Microsoft Excel OOXML spreadsheet".into(),
1487        },
1488        // ── Config / Infrastructure ──
1489        ContentType {
1490            id: "config/terraform".into(),
1491            name: "Terraform HCL".into(),
1492            domain: ContentDomain::Config,
1493            mime_types: vec!["text/x-hcl".into()],
1494            extensions: vec!["tf".into(), "tfvars".into()],
1495            magic_bytes: vec![],
1496            diff_strategy: DiffStrategy::Text,
1497            merge_strategy: MergeStrategy::TextThreeWay,
1498            storage_tier: StorageTier::Standard,
1499            lfs_threshold: None,
1500            metadata_schema: None,
1501            structural_diff: false,
1502            component_locking: false,
1503            description: "HashiCorp Terraform infrastructure-as-code".into(),
1504        },
1505        ContentType {
1506            id: "config/kubernetes".into(),
1507            name: "Kubernetes Manifest".into(),
1508            domain: ContentDomain::Config,
1509            mime_types: vec!["application/x-yaml".into()],
1510            extensions: vec!["yaml".into(), "yml".into()],
1511            magic_bytes: vec![],
1512            diff_strategy: DiffStrategy::Structural,
1513            merge_strategy: MergeStrategy::SchemaAware,
1514            storage_tier: StorageTier::Standard,
1515            lfs_threshold: None,
1516            metadata_schema: None,
1517            structural_diff: true,
1518            component_locking: false,
1519            description: "Kubernetes resource manifests (YAML)".into(),
1520        },
1521        // ── Simulation (FEA / CFD / multiphysics) ──
1522        ContentType {
1523            id: "sim/nastran".into(),
1524            name: "Nastran Bulk Data".into(),
1525            domain: ContentDomain::Simulation,
1526            mime_types: vec!["text/x-nastran".into()],
1527            extensions: vec!["bdf".into(), "nas".into(), "dat".into()],
1528            magic_bytes: vec![],
1529            diff_strategy: DiffStrategy::Text,
1530            merge_strategy: MergeStrategy::ManualResolve,
1531            storage_tier: StorageTier::Standard,
1532            lfs_threshold: Some(50 * 1024 * 1024),
1533            metadata_schema: None,
1534            structural_diff: false,
1535            component_locking: false,
1536            description: "MSC/NX Nastran bulk-data input deck".into(),
1537        },
1538        ContentType {
1539            id: "sim/nastran-op2".into(),
1540            name: "Nastran OP2 Results".into(),
1541            domain: ContentDomain::Simulation,
1542            mime_types: vec!["application/x-nastran-op2".into()],
1543            extensions: vec!["op2".into()],
1544            magic_bytes: vec![],
1545            diff_strategy: DiffStrategy::Opaque,
1546            merge_strategy: MergeStrategy::LastWriterWins,
1547            storage_tier: StorageTier::Lfs,
1548            lfs_threshold: Some(1024 * 1024),
1549            metadata_schema: None,
1550            structural_diff: false,
1551            component_locking: false,
1552            description: "Nastran OUTPUT2 binary results database".into(),
1553        },
1554        ContentType {
1555            id: "sim/abaqus".into(),
1556            name: "Abaqus Input Deck".into(),
1557            domain: ContentDomain::Simulation,
1558            mime_types: vec!["text/x-abaqus".into()],
1559            extensions: vec!["inp".into()],
1560            magic_bytes: vec![],
1561            diff_strategy: DiffStrategy::Text,
1562            merge_strategy: MergeStrategy::ManualResolve,
1563            storage_tier: StorageTier::Standard,
1564            lfs_threshold: Some(50 * 1024 * 1024),
1565            metadata_schema: None,
1566            structural_diff: false,
1567            component_locking: false,
1568            description: "Abaqus/Standard keyword input deck".into(),
1569        },
1570        ContentType {
1571            id: "sim/abaqus-odb".into(),
1572            name: "Abaqus ODB Results".into(),
1573            domain: ContentDomain::Simulation,
1574            mime_types: vec!["application/x-abaqus-odb".into()],
1575            extensions: vec!["odb".into()],
1576            magic_bytes: vec![],
1577            diff_strategy: DiffStrategy::Opaque,
1578            merge_strategy: MergeStrategy::LastWriterWins,
1579            storage_tier: StorageTier::Lfs,
1580            lfs_threshold: Some(1024 * 1024),
1581            metadata_schema: None,
1582            structural_diff: false,
1583            component_locking: false,
1584            description: "Abaqus output database (binary results)".into(),
1585        },
1586        ContentType {
1587            id: "sim/ansys-cdb".into(),
1588            name: "ANSYS CDB Archive".into(),
1589            domain: ContentDomain::Simulation,
1590            mime_types: vec!["text/x-ansys-cdb".into()],
1591            extensions: vec!["cdb".into()],
1592            magic_bytes: vec![],
1593            diff_strategy: DiffStrategy::Text,
1594            merge_strategy: MergeStrategy::ManualResolve,
1595            storage_tier: StorageTier::Standard,
1596            lfs_threshold: Some(50 * 1024 * 1024),
1597            metadata_schema: None,
1598            structural_diff: false,
1599            component_locking: false,
1600            description: "ANSYS APDL CDB model archive".into(),
1601        },
1602        ContentType {
1603            id: "sim/ansys-db".into(),
1604            name: "ANSYS Database / Results".into(),
1605            domain: ContentDomain::Simulation,
1606            mime_types: vec!["application/x-ansys".into()],
1607            extensions: vec!["db".into(), "rst".into(), "rth".into(), "rmg".into()],
1608            magic_bytes: vec![],
1609            diff_strategy: DiffStrategy::Opaque,
1610            merge_strategy: MergeStrategy::LastWriterWins,
1611            storage_tier: StorageTier::Lfs,
1612            lfs_threshold: Some(1024 * 1024),
1613            metadata_schema: None,
1614            structural_diff: false,
1615            component_locking: false,
1616            description: "ANSYS binary database/results files".into(),
1617        },
1618        ContentType {
1619            id: "sim/lsdyna".into(),
1620            name: "LS-DYNA Keyword".into(),
1621            domain: ContentDomain::Simulation,
1622            mime_types: vec!["text/x-lsdyna".into()],
1623            extensions: vec!["k".into(), "key".into(), "dyn".into()],
1624            magic_bytes: vec![],
1625            diff_strategy: DiffStrategy::Text,
1626            merge_strategy: MergeStrategy::ManualResolve,
1627            storage_tier: StorageTier::Standard,
1628            lfs_threshold: Some(50 * 1024 * 1024),
1629            metadata_schema: None,
1630            structural_diff: false,
1631            component_locking: false,
1632            description: "LS-DYNA keyword input deck".into(),
1633        },
1634        ContentType {
1635            id: "sim/openfoam".into(),
1636            name: "OpenFOAM Case".into(),
1637            domain: ContentDomain::Simulation,
1638            mime_types: vec!["text/x-openfoam".into()],
1639            extensions: vec!["foam".into()],
1640            magic_bytes: vec![],
1641            diff_strategy: DiffStrategy::Text,
1642            merge_strategy: MergeStrategy::ManualResolve,
1643            storage_tier: StorageTier::Standard,
1644            lfs_threshold: Some(50 * 1024 * 1024),
1645            metadata_schema: None,
1646            structural_diff: false,
1647            component_locking: false,
1648            description: "OpenFOAM case dictionary / field data".into(),
1649        },
1650        ContentType {
1651            id: "sim/comsol".into(),
1652            name: "COMSOL Model".into(),
1653            domain: ContentDomain::Simulation,
1654            mime_types: vec!["application/x-comsol".into()],
1655            extensions: vec!["mph".into()],
1656            magic_bytes: vec!["504B0304".into()],
1657            diff_strategy: DiffStrategy::Opaque,
1658            merge_strategy: MergeStrategy::ManualResolve,
1659            storage_tier: StorageTier::Lfs,
1660            lfs_threshold: Some(1024 * 1024),
1661            metadata_schema: None,
1662            structural_diff: false,
1663            component_locking: true,
1664            description: "COMSOL Multiphysics model file".into(),
1665        },
1666        ContentType {
1667            id: "sim/gmsh".into(),
1668            name: "Gmsh Mesh / Geometry".into(),
1669            domain: ContentDomain::Simulation,
1670            mime_types: vec!["text/x-gmsh".into()],
1671            extensions: vec!["msh".into(), "geo".into()],
1672            magic_bytes: vec![],
1673            diff_strategy: DiffStrategy::Text,
1674            merge_strategy: MergeStrategy::ManualResolve,
1675            storage_tier: StorageTier::Standard,
1676            lfs_threshold: Some(50 * 1024 * 1024),
1677            metadata_schema: None,
1678            structural_diff: false,
1679            component_locking: false,
1680            description: "Gmsh mesh (.msh) and geometry (.geo)".into(),
1681        },
1682        ContentType {
1683            id: "sim/vtk".into(),
1684            name: "VTK Visualization".into(),
1685            domain: ContentDomain::Simulation,
1686            mime_types: vec!["application/x-vtk".into()],
1687            extensions: vec![
1688                "vtk".into(),
1689                "vtu".into(),
1690                "vtp".into(),
1691                "vti".into(),
1692                "vtr".into(),
1693                "vts".into(),
1694                "pvd".into(),
1695                "pvtu".into(),
1696            ],
1697            magic_bytes: vec![],
1698            diff_strategy: DiffStrategy::Opaque,
1699            merge_strategy: MergeStrategy::LastWriterWins,
1700            storage_tier: StorageTier::Lfs,
1701            lfs_threshold: Some(5 * 1024 * 1024),
1702            metadata_schema: None,
1703            structural_diff: false,
1704            component_locking: false,
1705            description: "VTK/ParaView mesh & field visualization data".into(),
1706        },
1707        ContentType {
1708            id: "sim/cgns".into(),
1709            name: "CGNS".into(),
1710            domain: ContentDomain::Simulation,
1711            mime_types: vec!["application/x-cgns".into()],
1712            extensions: vec!["cgns".into()],
1713            magic_bytes: vec![],
1714            diff_strategy: DiffStrategy::Opaque,
1715            merge_strategy: MergeStrategy::LastWriterWins,
1716            storage_tier: StorageTier::Lfs,
1717            lfs_threshold: Some(5 * 1024 * 1024),
1718            metadata_schema: None,
1719            structural_diff: false,
1720            component_locking: false,
1721            description: "CFD General Notation System (HDF5-based)".into(),
1722        },
1723        ContentType {
1724            id: "sim/exodus".into(),
1725            name: "Exodus II".into(),
1726            domain: ContentDomain::Simulation,
1727            mime_types: vec!["application/x-exodus".into()],
1728            extensions: vec!["exo".into(), "exoii".into()],
1729            magic_bytes: vec![],
1730            diff_strategy: DiffStrategy::Opaque,
1731            merge_strategy: MergeStrategy::LastWriterWins,
1732            storage_tier: StorageTier::Lfs,
1733            lfs_threshold: Some(5 * 1024 * 1024),
1734            metadata_schema: None,
1735            structural_diff: false,
1736            component_locking: false,
1737            description: "Exodus II finite-element results (netCDF-based)".into(),
1738        },
1739        ContentType {
1740            id: "sim/modelica".into(),
1741            name: "Modelica Model".into(),
1742            domain: ContentDomain::Simulation,
1743            mime_types: vec!["text/x-modelica".into()],
1744            extensions: vec!["mo".into()],
1745            magic_bytes: vec![],
1746            diff_strategy: DiffStrategy::Text,
1747            merge_strategy: MergeStrategy::TextThreeWay,
1748            storage_tier: StorageTier::Standard,
1749            lfs_threshold: None,
1750            metadata_schema: None,
1751            structural_diff: false,
1752            component_locking: false,
1753            description: "Modelica equation-based system model source".into(),
1754        },
1755        ContentType {
1756            id: "sim/simulink".into(),
1757            name: "Simulink Model".into(),
1758            domain: ContentDomain::Simulation,
1759            mime_types: vec!["application/x-simulink".into()],
1760            extensions: vec!["slx".into(), "mdl".into()],
1761            magic_bytes: vec![],
1762            diff_strategy: DiffStrategy::Opaque,
1763            merge_strategy: MergeStrategy::ManualResolve,
1764            storage_tier: StorageTier::Lfs,
1765            lfs_threshold: Some(1024 * 1024),
1766            metadata_schema: None,
1767            structural_diff: false,
1768            component_locking: true,
1769            description: "MathWorks Simulink block-diagram model".into(),
1770        },
1771        ContentType {
1772            id: "sim/fmu".into(),
1773            name: "Functional Mock-up Unit".into(),
1774            domain: ContentDomain::Simulation,
1775            mime_types: vec!["application/x-fmu".into()],
1776            extensions: vec!["fmu".into()],
1777            magic_bytes: vec!["504B0304".into()],
1778            diff_strategy: DiffStrategy::Opaque,
1779            merge_strategy: MergeStrategy::ManualResolve,
1780            storage_tier: StorageTier::Lfs,
1781            lfs_threshold: Some(1024 * 1024),
1782            metadata_schema: None,
1783            structural_diff: false,
1784            component_locking: true,
1785            description: "FMI Functional Mock-up Unit (co-simulation)".into(),
1786        },
1787        // ── AI / ML model formats ──
1788        ContentType {
1789            id: "ml/onnx".into(),
1790            name: "ONNX Model".into(),
1791            domain: ContentDomain::MlModel,
1792            mime_types: vec!["application/x-onnx".into()],
1793            extensions: vec!["onnx".into()],
1794            magic_bytes: vec![],
1795            diff_strategy: DiffStrategy::Opaque,
1796            merge_strategy: MergeStrategy::LastWriterWins,
1797            storage_tier: StorageTier::Lfs,
1798            lfs_threshold: Some(1024 * 1024),
1799            metadata_schema: Some(serde_json::json!({
1800                "type": "object",
1801                "properties": {
1802                    "opset_version": {"type": "integer"},
1803                    "producer": {"type": "string"},
1804                    "input_count": {"type": "integer"},
1805                    "output_count": {"type": "integer"},
1806                    "parameter_count": {"type": "integer"}
1807                }
1808            })),
1809            structural_diff: false,
1810            component_locking: false,
1811            description: "Open Neural Network Exchange model (protobuf)".into(),
1812        },
1813        ContentType {
1814            id: "ml/safetensors".into(),
1815            name: "SafeTensors Weights".into(),
1816            domain: ContentDomain::MlModel,
1817            mime_types: vec!["application/x-safetensors".into()],
1818            extensions: vec!["safetensors".into()],
1819            magic_bytes: vec![],
1820            diff_strategy: DiffStrategy::Opaque,
1821            merge_strategy: MergeStrategy::LastWriterWins,
1822            storage_tier: StorageTier::Lfs,
1823            lfs_threshold: Some(1024 * 1024),
1824            metadata_schema: Some(serde_json::json!({
1825                "type": "object",
1826                "properties": {
1827                    "tensor_count": {"type": "integer"},
1828                    "dtype": {"type": "string"},
1829                    "total_parameters": {"type": "integer"}
1830                }
1831            })),
1832            structural_diff: false,
1833            component_locking: false,
1834            description: "SafeTensors safe zero-copy tensor weights".into(),
1835        },
1836        ContentType {
1837            id: "ml/pytorch".into(),
1838            name: "PyTorch Checkpoint".into(),
1839            domain: ContentDomain::MlModel,
1840            mime_types: vec!["application/x-pytorch".into()],
1841            extensions: vec!["pt".into(), "pth".into(), "bin".into()],
1842            magic_bytes: vec!["504B0304".into()],
1843            diff_strategy: DiffStrategy::Opaque,
1844            merge_strategy: MergeStrategy::LastWriterWins,
1845            storage_tier: StorageTier::Lfs,
1846            lfs_threshold: Some(1024 * 1024),
1847            metadata_schema: None,
1848            structural_diff: false,
1849            component_locking: false,
1850            description: "PyTorch serialized model/state-dict (ZIP/pickle)".into(),
1851        },
1852        ContentType {
1853            id: "ml/tensorflow".into(),
1854            name: "TensorFlow SavedModel".into(),
1855            domain: ContentDomain::MlModel,
1856            mime_types: vec!["application/x-tensorflow".into()],
1857            extensions: vec!["pb".into()],
1858            magic_bytes: vec![],
1859            diff_strategy: DiffStrategy::Opaque,
1860            merge_strategy: MergeStrategy::LastWriterWins,
1861            storage_tier: StorageTier::Lfs,
1862            lfs_threshold: Some(1024 * 1024),
1863            metadata_schema: None,
1864            structural_diff: false,
1865            component_locking: false,
1866            description: "TensorFlow GraphDef / SavedModel protobuf".into(),
1867        },
1868        ContentType {
1869            id: "ml/keras".into(),
1870            name: "Keras Model".into(),
1871            domain: ContentDomain::MlModel,
1872            mime_types: vec!["application/x-keras".into()],
1873            extensions: vec!["keras".into()],
1874            magic_bytes: vec![],
1875            diff_strategy: DiffStrategy::Opaque,
1876            merge_strategy: MergeStrategy::LastWriterWins,
1877            storage_tier: StorageTier::Lfs,
1878            lfs_threshold: Some(1024 * 1024),
1879            metadata_schema: None,
1880            structural_diff: false,
1881            component_locking: false,
1882            description: "Keras v3 model archive".into(),
1883        },
1884        ContentType {
1885            id: "ml/gguf".into(),
1886            name: "GGUF / GGML Model".into(),
1887            domain: ContentDomain::MlModel,
1888            mime_types: vec!["application/x-gguf".into()],
1889            extensions: vec!["gguf".into(), "ggml".into()],
1890            magic_bytes: vec!["47475546".into()],
1891            diff_strategy: DiffStrategy::Opaque,
1892            merge_strategy: MergeStrategy::LastWriterWins,
1893            storage_tier: StorageTier::External,
1894            lfs_threshold: Some(1024 * 1024),
1895            metadata_schema: Some(serde_json::json!({
1896                "type": "object",
1897                "properties": {
1898                    "architecture": {"type": "string"},
1899                    "quantization": {"type": "string"},
1900                    "parameter_count": {"type": "integer"},
1901                    "context_length": {"type": "integer"}
1902                }
1903            })),
1904            structural_diff: false,
1905            component_locking: false,
1906            description: "GGUF/GGML quantized LLM weights (llama.cpp)".into(),
1907        },
1908        ContentType {
1909            id: "ml/tensorrt".into(),
1910            name: "TensorRT Engine".into(),
1911            domain: ContentDomain::MlModel,
1912            mime_types: vec!["application/x-tensorrt".into()],
1913            extensions: vec!["engine".into(), "plan".into(), "trt".into()],
1914            magic_bytes: vec![],
1915            diff_strategy: DiffStrategy::Opaque,
1916            merge_strategy: MergeStrategy::LastWriterWins,
1917            storage_tier: StorageTier::Lfs,
1918            lfs_threshold: Some(1024 * 1024),
1919            metadata_schema: None,
1920            structural_diff: false,
1921            component_locking: false,
1922            description: "NVIDIA TensorRT serialized inference engine".into(),
1923        },
1924        ContentType {
1925            id: "ml/coreml".into(),
1926            name: "Core ML Model".into(),
1927            domain: ContentDomain::MlModel,
1928            mime_types: vec!["application/x-coreml".into()],
1929            extensions: vec!["mlmodel".into(), "mlpackage".into(), "mlmodelc".into()],
1930            magic_bytes: vec![],
1931            diff_strategy: DiffStrategy::Opaque,
1932            merge_strategy: MergeStrategy::ManualResolve,
1933            storage_tier: StorageTier::Lfs,
1934            lfs_threshold: Some(1024 * 1024),
1935            metadata_schema: None,
1936            structural_diff: false,
1937            component_locking: false,
1938            description: "Apple Core ML model package".into(),
1939        },
1940        ContentType {
1941            id: "ml/tflite".into(),
1942            name: "TensorFlow Lite".into(),
1943            domain: ContentDomain::MlModel,
1944            mime_types: vec!["application/x-tflite".into()],
1945            extensions: vec!["tflite".into(), "lite".into()],
1946            magic_bytes: vec![],
1947            diff_strategy: DiffStrategy::Opaque,
1948            merge_strategy: MergeStrategy::LastWriterWins,
1949            storage_tier: StorageTier::Lfs,
1950            lfs_threshold: Some(1024 * 1024),
1951            metadata_schema: None,
1952            structural_diff: false,
1953            component_locking: false,
1954            description: "TensorFlow Lite flatbuffer model (edge/mobile)".into(),
1955        },
1956        ContentType {
1957            id: "ml/pickle".into(),
1958            name: "Python Pickle".into(),
1959            domain: ContentDomain::MlModel,
1960            mime_types: vec!["application/x-python-pickle".into()],
1961            extensions: vec!["pkl".into(), "pickle".into()],
1962            magic_bytes: vec![],
1963            diff_strategy: DiffStrategy::Opaque,
1964            merge_strategy: MergeStrategy::LastWriterWins,
1965            storage_tier: StorageTier::Lfs,
1966            lfs_threshold: Some(1024 * 1024),
1967            metadata_schema: None,
1968            structural_diff: false,
1969            component_locking: false,
1970            description: "Python pickle serialized object (untrusted: arbitrary code on load)"
1971                .into(),
1972        },
1973        ContentType {
1974            id: "ml/numpy".into(),
1975            name: "NumPy Array".into(),
1976            domain: ContentDomain::MlModel,
1977            mime_types: vec!["application/x-numpy".into()],
1978            extensions: vec!["npy".into(), "npz".into()],
1979            magic_bytes: vec!["934E554D5059".into()],
1980            diff_strategy: DiffStrategy::Opaque,
1981            merge_strategy: MergeStrategy::LastWriterWins,
1982            storage_tier: StorageTier::Lfs,
1983            lfs_threshold: Some(1024 * 1024),
1984            metadata_schema: None,
1985            structural_diff: false,
1986            component_locking: false,
1987            description: "NumPy .npy/.npz array data".into(),
1988        },
1989        ContentType {
1990            id: "ml/checkpoint".into(),
1991            name: "Model Checkpoint".into(),
1992            domain: ContentDomain::MlModel,
1993            mime_types: vec!["application/x-checkpoint".into()],
1994            extensions: vec!["ckpt".into()],
1995            magic_bytes: vec![],
1996            diff_strategy: DiffStrategy::Opaque,
1997            merge_strategy: MergeStrategy::LastWriterWins,
1998            storage_tier: StorageTier::Lfs,
1999            lfs_threshold: Some(1024 * 1024),
2000            metadata_schema: None,
2001            structural_diff: false,
2002            component_locking: false,
2003            description: "Generic training checkpoint (Lightning/TF/Diffusers)".into(),
2004        },
2005        ContentType {
2006            id: "ml/joblib".into(),
2007            name: "Joblib Model".into(),
2008            domain: ContentDomain::MlModel,
2009            mime_types: vec!["application/x-joblib".into()],
2010            extensions: vec!["joblib".into()],
2011            magic_bytes: vec![],
2012            diff_strategy: DiffStrategy::Opaque,
2013            merge_strategy: MergeStrategy::LastWriterWins,
2014            storage_tier: StorageTier::Lfs,
2015            lfs_threshold: Some(1024 * 1024),
2016            metadata_schema: None,
2017            structural_diff: false,
2018            component_locking: false,
2019            description: "scikit-learn / joblib serialized estimator".into(),
2020        },
2021    ]
2022}
2023
2024// ── Helpers ─────────────────────────────────────────────────────────────────
2025
2026fn types_dir(repo_root: &Path) -> std::path::PathBuf {
2027    repo_root.join(".lit").join("content-types")
2028}
2029
2030fn save_type(repo_root: &Path, ct: &ContentType) -> Result<(), LitError> {
2031    let dir = types_dir(repo_root);
2032    fs::create_dir_all(&dir)
2033        .map_err(|e| LitError::io(format!("Create content-types dir: {}", e)))?;
2034    let safe_id: String = ct.id.replace('/', "_");
2035    let path = dir.join(format!("{}.json", safe_id));
2036    let json = serde_json::to_string_pretty(ct)
2037        .map_err(|e| LitError::general(format!("Serialize content type: {}", e)))?;
2038    fs::write(&path, json).map_err(|e| LitError::io(format!("Write content type: {}", e)))?;
2039    Ok(())
2040}
2041
2042fn load_all_types(repo_root: &Path) -> Result<Vec<ContentType>, LitError> {
2043    let dir = types_dir(repo_root);
2044    let mut types = builtin_types();
2045
2046    // Overlay custom types from repo
2047    if dir.exists() {
2048        for entry in fs::read_dir(&dir).map_err(|e| LitError::io(e.to_string()))? {
2049            let entry = entry.map_err(|e| LitError::io(e.to_string()))?;
2050            if entry
2051                .path()
2052                .extension()
2053                .map(|e| e == "json")
2054                .unwrap_or(false)
2055            {
2056                let json =
2057                    fs::read_to_string(entry.path()).map_err(|e| LitError::io(e.to_string()))?;
2058                if let Ok(ct) = serde_json::from_str::<ContentType>(&json) {
2059                    // Custom types override builtins with the same id
2060                    types.retain(|t| t.id != ct.id);
2061                    types.push(ct);
2062                }
2063            }
2064        }
2065    }
2066    Ok(types)
2067}
2068
2069/// Detect content type for a file by extension, then magic bytes
2070pub fn detect(file_path: &str, first_bytes: Option<&[u8]>) -> Option<ContentType> {
2071    let ext = file_path
2072        .rsplit('.')
2073        .next()
2074        .map(|e| e.to_lowercase())
2075        .unwrap_or_default();
2076
2077    // Try builtins first (avoid needing repo root for detection)
2078    let all = builtin_types();
2079
2080    // Extension match
2081    if let Some(ct) = all.iter().find(|t| t.extensions.contains(&ext)) {
2082        return Some(ct.clone());
2083    }
2084
2085    // Magic bytes match
2086    if let Some(bytes) = first_bytes {
2087        let hex: String = bytes
2088            .iter()
2089            .take(16)
2090            .map(|b| format!("{:02X}", b))
2091            .collect();
2092        if let Some(ct) = all
2093            .iter()
2094            .find(|t| t.magic_bytes.iter().any(|mb| hex.starts_with(mb)))
2095        {
2096            return Some(ct.clone());
2097        }
2098    }
2099
2100    None
2101}
2102
2103// ── Public API ──────────────────────────────────────────────────────────────
2104
2105/// List all registered content types, optionally filtered by domain
2106pub fn execute_list(domain_filter: Option<String>) -> Result<ContentTypeResponse, LitError> {
2107    let repo_root = find_repo_root().unwrap_or_else(|_| std::path::PathBuf::from("."));
2108    let mut types = load_all_types(&repo_root)?;
2109
2110    if let Some(ref domain) = domain_filter {
2111        types.retain(|t| t.domain.to_string() == *domain);
2112    }
2113
2114    let count = types.len();
2115    Ok(ContentTypeResponse {
2116        action: "list".into(),
2117        content_type_id: None,
2118        message: format!("{} content type(s)", count),
2119        details: Some(serde_json::to_value(&types).unwrap_or_default()),
2120    })
2121}
2122
2123/// Show a specific content type
2124pub fn execute_show(type_id: String) -> Result<ContentTypeResponse, LitError> {
2125    let repo_root = find_repo_root().unwrap_or_else(|_| std::path::PathBuf::from("."));
2126    let types = load_all_types(&repo_root)?;
2127
2128    let ct = types
2129        .iter()
2130        .find(|t| t.id == type_id)
2131        .ok_or_else(|| LitError::general(format!("Content type not found: {}", type_id)))?;
2132
2133    Ok(ContentTypeResponse {
2134        action: "show".into(),
2135        content_type_id: Some(ct.id.clone()),
2136        message: format!("{} ({})", ct.name, ct.domain),
2137        details: Some(serde_json::to_value(ct).unwrap_or_default()),
2138    })
2139}
2140
2141/// Register a custom content type
2142pub fn execute_register(
2143    id: String,
2144    name: String,
2145    domain: String,
2146    extensions: Vec<String>,
2147    diff_strategy: Option<String>,
2148    merge_strategy: Option<String>,
2149    storage_tier: Option<String>,
2150) -> Result<ContentTypeResponse, LitError> {
2151    let repo_root = find_repo_root()?;
2152
2153    let domain_enum = match domain.as_str() {
2154        "software" => ContentDomain::Software,
2155        "cad" => ContentDomain::Cad,
2156        "eda" => ContentDomain::Eda,
2157        "cam" => ContentDomain::Cam,
2158        "simulation" | "sim" | "fea" | "cfd" => ContentDomain::Simulation,
2159        "ml-model" | "ml" | "ai" | "model" => ContentDomain::MlModel,
2160        "manuscript" => ContentDomain::Manuscript,
2161        "database" => ContentDomain::Database,
2162        "scientific" => ContentDomain::Scientific,
2163        "media" => ContentDomain::Media,
2164        "geospatial" => ContentDomain::Geospatial,
2165        "legal" => ContentDomain::Legal,
2166        "financial" => ContentDomain::Financial,
2167        "config" => ContentDomain::Config,
2168        "documentation" => ContentDomain::Documentation,
2169        other => ContentDomain::Custom(other.to_string()),
2170    };
2171
2172    let diff = match diff_strategy.as_deref() {
2173        Some("text") => DiffStrategy::Text,
2174        Some("binary") => DiffStrategy::Binary,
2175        Some("structural") => DiffStrategy::Structural,
2176        Some("semantic") => DiffStrategy::Semantic,
2177        Some("opaque") => DiffStrategy::Opaque,
2178        _ => DiffStrategy::Binary,
2179    };
2180
2181    let merge = match merge_strategy.as_deref() {
2182        Some("text-three-way") => MergeStrategy::TextThreeWay,
2183        Some("manual-resolve") => MergeStrategy::ManualResolve,
2184        Some("schema-aware") => MergeStrategy::SchemaAware,
2185        Some("component-level") => MergeStrategy::ComponentLevel,
2186        Some("append-only") => MergeStrategy::AppendOnly,
2187        Some("last-writer-wins") => MergeStrategy::LastWriterWins,
2188        _ => MergeStrategy::ManualResolve,
2189    };
2190
2191    let tier = match storage_tier.as_deref() {
2192        Some("standard") => StorageTier::Standard,
2193        Some("lfs") => StorageTier::Lfs,
2194        Some("chunked") => StorageTier::Chunked,
2195        Some("external") => StorageTier::External,
2196        _ => StorageTier::Lfs,
2197    };
2198
2199    let ct = ContentType {
2200        id: id.clone(),
2201        name: name.clone(),
2202        domain: domain_enum,
2203        mime_types: vec![],
2204        extensions,
2205        magic_bytes: vec![],
2206        diff_strategy: diff,
2207        merge_strategy: merge,
2208        storage_tier: tier,
2209        lfs_threshold: None,
2210        metadata_schema: None,
2211        structural_diff: false,
2212        component_locking: false,
2213        description: format!("Custom content type: {}", name),
2214    };
2215
2216    save_type(&repo_root, &ct)?;
2217
2218    Ok(ContentTypeResponse {
2219        action: "register".into(),
2220        content_type_id: Some(id),
2221        message: format!("Content type '{}' registered", name),
2222        details: Some(serde_json::to_value(&ct).unwrap_or_default()),
2223    })
2224}
2225
2226/// Detect the content type(s) of one or more files
2227pub fn execute_detect(paths: Vec<String>) -> Result<ContentTypeResponse, LitError> {
2228    let mut results: HashMap<String, serde_json::Value> = HashMap::new();
2229
2230    for path in &paths {
2231        let first_bytes = fs::read(path).ok().map(|b| b[..b.len().min(16)].to_vec());
2232        let detected = detect(path, first_bytes.as_deref());
2233        results.insert(
2234            path.clone(),
2235            match detected {
2236                Some(ct) => serde_json::to_value(&ct).unwrap_or_default(),
2237                None => serde_json::json!({"detected": false}),
2238            },
2239        );
2240    }
2241
2242    Ok(ContentTypeResponse {
2243        action: "detect".into(),
2244        content_type_id: None,
2245        message: format!("Detected types for {} file(s)", paths.len()),
2246        details: Some(serde_json::to_value(&results).unwrap_or_default()),
2247    })
2248}