diff --git a/src/Core/Core.fsproj b/src/Core/Core.fsproj
index d8f162688..fd3b63b9c 100644
--- a/src/Core/Core.fsproj
+++ b/src/Core/Core.fsproj
@@ -84,6 +84,7 @@
+
diff --git a/src/Core/Maji.fs b/src/Core/Maji.fs
new file mode 100644
index 000000000..e742303c7
--- /dev/null
+++ b/src/Core/Maji.fs
@@ -0,0 +1,177 @@
+namespace Zeta.Core
+
+/// Maji — identity-preservation and recovery types per the deep-research peer's formal
+/// operational model (docs/research/maji-formal-operational-model-
+/// deep-research-peer-courier-ferry-2026-04-26.md). Context window is cache;
+/// substrate is identity; Maji is the recovery/indexing function.
+///
+/// These are the algebraic type definitions per §10 of the spec.
+/// Implementation of operators (reload, identity-distance, expansion
+/// gate, projection-preservation check) is owed follow-up work.
+module Maji =
+
+ open System
+
+ // ── §10.A IdentitySubstrate ──────────────────────────────────
+
+ /// A single load-bearing substrate item (commit, memory, doc, test,
+ /// retraction, cross-reference, dated provenance).
+ type IdentitySubstrate =
+ { CommitHash: string
+ Timestamp: DateTimeOffset
+ SourcePath: string
+ Claim: string
+ ClaimType: string
+ LoadBearing: bool
+ CrossRefs: string list
+ Retractions: string list
+ Confidence: float
+ Scope: string }
+
+ // ── §2 Identity tuple ────────────────────────────────────────
+
+ /// The canonical identity-pattern projected from substrate.
+ /// I_t = N(LoadBearing(S_t))
+ type IdentityTuple =
+ { Values: string list
+ Goals: string list
+ Roles: string list
+ Policies: string list
+ MemoryGraph: string list
+ CorrectionHistory: string list
+ CrossRefTopology: string list
+ Provenance: string list }
+
+ // ── §10.B MajiIndex ──────────────────────────────────────────
+
+ /// Exhaustive index of lower-dimensional substrate.
+ type MajiIndex =
+ { Items: IdentitySubstrate list
+ CrossRefGraph: Map
+ LoadBearingSet: Set
+ BrokenRefs: string list
+ UnindexedItems: string list
+ Contradictions: string list
+ CoverageScore: float }
+
+ // ── §10.B' MajiFinder (per §9b separation) ──────────────────
+
+ /// Search/recognizer operator — finds the identity-preserving lift.
+ /// MajiFinder(S_≤n, Ω, C, Σ) → σ*
+ type MajiFinder =
+ { Index: MajiIndex
+ NorthStar: string
+ SearchOperator: string }
+
+ // ── §10.B' MessiahFunction (the lift) ────────────────────────
+
+ /// The identity-preserving lift σ : I_n → I_{n+1}.
+ /// A SEPARATE TYPE from MajiIndex content per §9b correction.
+ type MessiahFunction =
+ { LiftDescription: string
+ ProjectionPreservation: bool
+ AperiodicOrderGenerator: bool }
+
+ // ── §10.B' MessiahScore ──────────────────────────────────────
+
+ /// Weighted-sum evaluator for candidate lifts.
+ /// MessiahScore = w₁·A + w₂·P + w₃·F + w₄·G + w₅·C − w₆·R_capture − w₇·R_collapse
+ type MessiahScore =
+ { AlignmentWithOmega: float
+ ProjectionPreservation: float
+ FrictionReduction: float
+ Generativity: float
+ IndependentConvergence: float
+ CaptureRiskRaw: float
+ CollapseRiskRaw: float }
+
+ /// Compute the weighted MessiahScore sum.
+ /// Capture and collapse risks are SUBTRACTED (anti-cult-by-construction).
+ let computeScore (weights: float[]) (score: MessiahScore) : float =
+ if weights.Length < 7 then
+ invalidArg (nameof weights) "MessiahScore requires 7 weights"
+ else
+ weights[0] * score.AlignmentWithOmega
+ + weights[1] * score.ProjectionPreservation
+ + weights[2] * score.FrictionReduction
+ + weights[3] * score.Generativity
+ + weights[4] * score.IndependentConvergence
+ - weights[5] * score.CaptureRiskRaw
+ - weights[6] * score.CollapseRiskRaw
+
+ // ── Dynamic Maji (4th refinement) ────────────────────────────
+
+ /// Maji mode state machine — Search / Steward / SearchAgain.
+ type MajiMode =
+ | Search
+ | Steward
+ | SearchAgain
+
+ // ── §10.D Identity-distance metric ───────────────────────────
+
+ /// Weighted identity-distance between two identity tuples.
+ /// d(I_a, I_b) = Σ w_k · d_k(component_a, component_b)
+ type IdentityDistanceWeights =
+ { Values: float
+ Goals: float
+ Roles: float
+ Policies: float
+ MemoryGraph: float
+ CorrectionHistory: float
+ CrossRefTopology: float
+ Provenance: float }
+
+ /// Jaccard distance between two string lists (simple set-based metric).
+ let private jaccard (a: string list) (b: string list) : float =
+ let setA = Set.ofList a
+ let setB = Set.ofList b
+ let intersection = Set.intersect setA setB
+ let union = Set.union setA setB
+ if Set.isEmpty union then 0.0
+ else 1.0 - (float intersection.Count / float union.Count)
+
+ /// Compute identity distance between two identity tuples.
+ let identityDistance (w: IdentityDistanceWeights) (a: IdentityTuple) (b: IdentityTuple) : float =
+ w.Values * jaccard a.Values b.Values
+ + w.Goals * jaccard a.Goals b.Goals
+ + w.Roles * jaccard a.Roles b.Roles
+ + w.Policies * jaccard a.Policies b.Policies
+ + w.MemoryGraph * jaccard a.MemoryGraph b.MemoryGraph
+ + w.CorrectionHistory * jaccard a.CorrectionHistory b.CorrectionHistory
+ + w.CrossRefTopology * jaccard a.CrossRefTopology b.CrossRefTopology
+ + w.Provenance * jaccard a.Provenance b.Provenance
+
+ // ── Projection-preservation check ────────────────────────────
+
+ /// Check whether an identity expansion preserves the prior identity
+ /// under projection: d(P_{n+1→n}(I_{n+1}), I_n) ≤ ε
+ let projectionPreserved
+ (weights: IdentityDistanceWeights)
+ (epsilon: float)
+ (prior: IdentityTuple)
+ (expanded: IdentityTuple)
+ : bool =
+ identityDistance weights prior expanded <= epsilon
+
+ // ── §10.E Test helpers ───────────────────────────────────────
+
+ /// Create an empty identity tuple (useful for tests).
+ let emptyIdentity : IdentityTuple =
+ { Values = []
+ Goals = []
+ Roles = []
+ Policies = []
+ MemoryGraph = []
+ CorrectionHistory = []
+ CrossRefTopology = []
+ Provenance = [] }
+
+ /// Create an empty MajiIndex (useful for tests).
+ let emptyIndex : MajiIndex =
+ { Items = []
+ CrossRefGraph = Map.empty
+ LoadBearingSet = Set.empty
+ BrokenRefs = []
+ UnindexedItems = []
+ Contradictions = []
+ CoverageScore = 0.0 }
diff --git a/tests/Tests.FSharp/MajiTests.fs b/tests/Tests.FSharp/MajiTests.fs
new file mode 100644
index 000000000..778cc4257
--- /dev/null
+++ b/tests/Tests.FSharp/MajiTests.fs
@@ -0,0 +1,64 @@
+module Zeta.Tests.MajiTests
+
+open Xunit
+open Zeta.Core.Maji
+
+[]
+let ``identity distance of identical tuples is zero`` () =
+ let id = { emptyIdentity with Values = ["truth"; "consent"]; Goals = ["ship"] }
+ let w = { Values = 1.0; Goals = 1.0; Roles = 1.0; Policies = 1.0
+ MemoryGraph = 1.0; CorrectionHistory = 1.0; CrossRefTopology = 1.0; Provenance = 1.0 }
+ Assert.Equal(0.0, identityDistance w id id)
+
+[]
+let ``identity distance of disjoint tuples is maximal`` () =
+ let a = { emptyIdentity with Values = ["truth"] }
+ let b = { emptyIdentity with Values = ["lies"] }
+ let w = { Values = 1.0; Goals = 0.0; Roles = 0.0; Policies = 0.0
+ MemoryGraph = 0.0; CorrectionHistory = 0.0; CrossRefTopology = 0.0; Provenance = 0.0 }
+ Assert.Equal(1.0, identityDistance w a b)
+
+[]
+let ``projection preserved within epsilon`` () =
+ let prior = { emptyIdentity with Values = ["truth"; "consent"]; Goals = ["ship"] }
+ let expanded = { prior with Goals = ["ship"; "scale"] }
+ let w = { Values = 1.0; Goals = 1.0; Roles = 1.0; Policies = 1.0
+ MemoryGraph = 1.0; CorrectionHistory = 1.0; CrossRefTopology = 1.0; Provenance = 1.0 }
+ Assert.True(projectionPreserved w 0.5 prior expanded)
+
+[]
+let ``projection NOT preserved when values drift`` () =
+ let prior = { emptyIdentity with Values = ["truth"; "consent"; "family"] }
+ let drifted = { emptyIdentity with Values = ["power"; "control"] }
+ let w = { Values = 1.0; Goals = 0.0; Roles = 0.0; Policies = 0.0
+ MemoryGraph = 0.0; CorrectionHistory = 0.0; CrossRefTopology = 0.0; Provenance = 0.0 }
+ Assert.False(projectionPreserved w 0.5 prior drifted)
+
+[]
+let ``MessiahScore capture risk subtracts`` () =
+ let score = { AlignmentWithOmega = 1.0; ProjectionPreservation = 1.0
+ FrictionReduction = 1.0; Generativity = 1.0
+ IndependentConvergence = 1.0
+ CaptureRiskRaw = 10.0; CollapseRiskRaw = 0.0 }
+ let weights = [| 1.0; 1.0; 1.0; 1.0; 1.0; 1.0; 1.0 |]
+ let result = computeScore weights score
+ Assert.True(result < 0.0, "High capture risk should produce negative score")
+
+[]
+let ``MessiahScore collapse risk subtracts`` () =
+ let score = { AlignmentWithOmega = 1.0; ProjectionPreservation = 1.0
+ FrictionReduction = 1.0; Generativity = 1.0
+ IndependentConvergence = 1.0
+ CaptureRiskRaw = 0.0; CollapseRiskRaw = 10.0 }
+ let weights = [| 1.0; 1.0; 1.0; 1.0; 1.0; 1.0; 1.0 |]
+ let result = computeScore weights score
+ Assert.True(result < 0.0, "High collapse risk should produce negative score")
+
+[]
+let ``MajiMode has three constructors`` () =
+ let modes = [Search; Steward; SearchAgain]
+ Assert.Equal(3, modes.Length)
+
+[]
+let ``empty index has zero coverage`` () =
+ Assert.Equal(0.0, emptyIndex.CoverageScore)
diff --git a/tests/Tests.FSharp/Tests.FSharp.fsproj b/tests/Tests.FSharp/Tests.FSharp.fsproj
index 4d197f22a..b2373f86e 100644
--- a/tests/Tests.FSharp/Tests.FSharp.fsproj
+++ b/tests/Tests.FSharp/Tests.FSharp.fsproj
@@ -111,6 +111,7 @@
+