Skip to main content

qualia_core_db/container_10d/
axis_role.rs

1//! Normative axis-role taxonomy for the `.10d` container header.
2//!
3//! Each of the ten tensor axes `[q, v, w, x, y, z, t, alpha, mu, sigma]` is
4//! declared in the header with one of the roles below. The taxonomy is the
5//! prerequisite for honest queryability: a `COORDINATE` participates in
6//! distance, a `SELECTOR` is excluded from every distance sum, and a `CARRIER`
7//! is the in-band provenance lane. `mu` carries a dual role (it is both a
8//! measured coordinate and the provenance carrier) and so gets its own variant
9//! `CoordinateCarrier`.
10//!
11//! **Status: PROPOSED, not yet frozen.** Timothy Charles Holborn confirmed the
12//! Option A assignment on 2026-07-04 as the baseline to encode, but the
13//! taxonomy is not normatively frozen in the `.10d` v1 spec until the P0.7
14//! conformance vectors land. The parser in [`crate::container_10d::header`]
15//! accepts any non-`Undefined` assignment today; a future task can tighten it
16//! to reject deviations from the frozen table once blessed.
17//!
18//! Reference: `docs/plans/native-computational-geometry.md` §4.1, and the
19//! execution plan's ⚑ "Axis-role taxonomy sign-off" curation datum.
20
21/// Canonical axis order — matches the `Tensor10D` field order exactly so a
22/// `Tensor10D` can be reinterpreted as ten `f32` lanes in this order without
23/// re-shuffling. Index `i` here is index `i` into `axis_roles` in the header
24/// and into the `folded_axes` bitmask of [`super::metric_check`].
25///
26/// The three spectral/epistemic axes use their normative Greek letters (`α`,
27/// `μ`, `σ`) — the vocabulary the `.10d` spec and the AGENTS.md bit layout
28/// define — not ASCII substitutes. The `Tensor10D` Rust struct fields are
29/// named `alpha`/`mu`/`sigma` (Rust identifier convention), but the normative
30/// axis names in the header, error messages, and this table are `α`/`μ`/`σ`.
31pub const AXIS_ORDER: [&str; 10] = ["q", "v", "w", "x", "y", "z", "t", "α", "μ", "σ"];
32
33/// Axis indices for the seven COORDINATE lanes (the ones that may participate
34/// in distance). `μ` is included here because it is a coordinate in addition
35/// to being the carrier.
36pub const COORDINATE_AXES: [usize; 7] = [3, 4, 5, 6, 7, 8, 9]; // x,y,z,t,α,μ,σ
37
38/// Axis indices for the three SELECTOR lanes (excluded from every distance sum).
39pub const SELECTOR_AXES: [usize; 3] = [0, 1, 2]; // q,v,w
40
41/// Index of `μ` — the dual-role coordinate + provenance carrier.
42pub const MU_AXIS: usize = 8;
43
44/// The role a single tensor axis plays in the `.10d` distance/query model.
45///
46/// Encoded as a `u8` in the header's `axis_roles[10]` array. `Undefined` is the
47/// sentinel the parser rejects — a header with any axis left undefined fails
48/// closed (the "missing-or-undefined axis role" acceptance gate).
49#[repr(u8)]
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum AxisRole {
52    /// Sentinel for "no role assigned". The parser rejects any header carrying
53    /// this value — every axis must declare a concrete role.
54    Undefined = 0,
55    /// Excluded from every distance sum. `q`, `v`, `w`.
56    Selector = 1,
57    /// Participates in distance. `x`, `y`, `z`, `t`, `α`, `σ`.
58    Coordinate = 2,
59    /// In-band provenance lane only (not a coordinate). Reserved for a future
60    /// taxonomy where `μ` is carrier-only; not used by the proposed table.
61    Carrier = 3,
62    /// Dual-role: a `COORDINATE` that is also the in-band provenance `CARRIER`.
63    /// This is `μ`'s role under the proposed Option A taxonomy — it preserves
64    /// the foundational promise that provenance/consent can exert geometric
65    /// proximity or weight during queries.
66    CoordinateCarrier = 4,
67}
68
69impl AxisRole {
70    /// True if this role participates in distance (pure coordinate or the
71    /// dual-role coordinate+carrier).
72    #[inline]
73    pub const fn is_coordinate(self) -> bool {
74        matches!(self, AxisRole::Coordinate | AxisRole::CoordinateCarrier)
75    }
76
77    /// True if this role carries the provenance lane.
78    #[inline]
79    pub const fn is_carrier(self) -> bool {
80        matches!(self, AxisRole::Carrier | AxisRole::CoordinateCarrier)
81    }
82
83    /// Decode a raw `u8` back to an `AxisRole`, or `None` if the value is not a
84    /// defined variant. Used by the parser.
85    #[inline]
86    pub const fn from_u8(raw: u8) -> Option<AxisRole> {
87        match raw {
88            0 => Some(AxisRole::Undefined),
89            1 => Some(AxisRole::Selector),
90            2 => Some(AxisRole::Coordinate),
91            3 => Some(AxisRole::Carrier),
92            4 => Some(AxisRole::CoordinateCarrier),
93            _ => None,
94        }
95    }
96}
97
98/// The proposed (not-yet-frozen) normative axis-role table, indexed by
99/// `AXIS_ORDER`. This is the Option A assignment Timothy confirmed on
100/// 2026-07-04:
101///
102/// - `q, v, w` → `Selector`
103/// - `x, y, z, t, α, σ` → `Coordinate`
104/// - `μ` → `CoordinateCarrier` (dual-role: coordinate + provenance carrier)
105///
106/// The header's `axis_roles` field is initialised from this table by
107/// [`super::header::Container10dHeader::proposed`].
108pub const PROPOSED_AXIS_ROLES: [AxisRole; 10] = [
109    AxisRole::Selector,          // q
110    AxisRole::Selector,          // v
111    AxisRole::Selector,          // w
112    AxisRole::Coordinate,        // x
113    AxisRole::Coordinate,        // y
114    AxisRole::Coordinate,        // z
115    AxisRole::Coordinate,        // t
116    AxisRole::Coordinate,        // α
117    AxisRole::CoordinateCarrier, // μ
118    AxisRole::Coordinate,        // σ
119];
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn proposed_table_matches_option_a_confirmation() {
127        // q,v,w are selectors
128        for &i in &SELECTOR_AXES {
129            assert_eq!(
130                PROPOSED_AXIS_ROLES[i],
131                AxisRole::Selector,
132                "axis {} should be Selector",
133                AXIS_ORDER[i]
134            );
135        }
136        // x,y,z,t,α,σ are pure coordinates
137        for &i in &[3usize, 4, 5, 6, 7, 9] {
138            assert_eq!(
139                PROPOSED_AXIS_ROLES[i],
140                AxisRole::Coordinate,
141                "axis {} should be Coordinate",
142                AXIS_ORDER[i]
143            );
144        }
145        // μ is the dual-role coordinate+carrier
146        assert_eq!(PROPOSED_AXIS_ROLES[MU_AXIS], AxisRole::CoordinateCarrier);
147        assert!(PROPOSED_AXIS_ROLES[MU_AXIS].is_coordinate());
148        assert!(PROPOSED_AXIS_ROLES[MU_AXIS].is_carrier());
149    }
150
151    #[test]
152    fn axis_role_round_trips_through_u8() {
153        for raw in 0u8..=4 {
154            let role = AxisRole::from_u8(raw).expect("0..=4 are defined");
155            assert_eq!(role as u8, raw);
156        }
157        assert!(AxisRole::from_u8(5).is_none());
158        assert!(AxisRole::from_u8(255).is_none());
159    }
160
161    #[test]
162    fn coordinate_axes_cover_seven_lanes_including_mu() {
163        assert_eq!(COORDINATE_AXES.len(), 7);
164        assert!(
165            COORDINATE_AXES.contains(&MU_AXIS),
166            "mu is a coordinate under Option A"
167        );
168    }
169}