Skip to main content

qualia_core_db/container_10d/
metric_check.rs

1//! Metric-completeness descriptor + the "queryability claim == code" gate.
2//!
3//! The `.10d` header carries a [`MetricCompletenessDescriptor`] declaring, per
4//! `v`-branch, which COORDINATE axes the distance metric folds. The parser
5//! rejects any descriptor that diverges from `Tensor10D::full_distance`'s
6//! actual v-branch behaviour — this is the mechanical barrier that enforces
7//! the honest-limitation contract: a developer cannot claim an axis is
8//! queryable under a metric the kernel does not actually fold.
9//!
10//! **Current reality (encoded as the proposed default):**
11//! - `v=0` Euclidean folds `x,y,z,t,α,μ,σ` (all seven COORDINATEs)
12//! - `v=1` Cyclic/Toroidal folds `x,y,z` only
13//! - `v=2` Hyperbolic folds `x,y,z` only
14//! - `v>=3` Boundary clique folds no coordinate axes (byte-equality on `v`)
15//!
16//! This is option (b) "document the limitation" per Timothy's 2026-07-04
17//! decision. P7.9 (making the non-Euclidean metrics axis-complete via product
18//! / warped-product manifolds and a clique-graph) is deferred as future
19//! geometry design work — see the progress log and the P7.9 task entry.
20//!
21//! Reference: `docs/plans/native-computational-geometry.md` §4.1 honest
22//! limitation, and the cross-cutting gate "Honest axis-role taxonomy &
23//! metric-completeness (queryability claim == code)".
24
25use bytemuck::{Pod, Zeroable};
26
27use crate::container_10d::axis_role::COORDINATE_AXES;
28use crate::tensor::Tensor10D;
29use std::fmt;
30
31/// Number of `v`-branch descriptors the header carries: the three explicit
32/// classes (0, 1, 2) plus one catch-all for `v >= 3`.
33pub const METRIC_BRANCH_COUNT: usize = 4;
34
35/// Index of the `v >= 3` catch-all branch in a [`MetricCompletenessDescriptor`].
36pub const BOUNDARY_CLIQUE_BRANCH_INDEX: usize = 3;
37
38/// A `u8` tag identifying the metric kind a branch implements. Mirrors the
39/// dispatch in `Tensor10D::full_distance`.
40#[repr(u8)]
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum MetricKind {
43    /// Sentinel — the parser rejects a branch with this kind.
44    Undefined = 0,
45    Euclidean = 1,
46    CyclicToroidal = 2,
47    Hyperbolic = 3,
48    BoundaryClique = 4,
49}
50
51impl MetricKind {
52    #[inline]
53    pub const fn from_u8(raw: u8) -> Option<MetricKind> {
54        match raw {
55            0 => Some(MetricKind::Undefined),
56            1 => Some(MetricKind::Euclidean),
57            2 => Some(MetricKind::CyclicToroidal),
58            3 => Some(MetricKind::Hyperbolic),
59            4 => Some(MetricKind::BoundaryClique),
60            _ => None,
61        }
62    }
63}
64
65/// One row of the metric-completeness table: the metric used for a single
66/// `v`-class and the bitmask of COORDINATE axes it folds.
67///
68/// `folded_axes` is a bitmask over [`super::axis_role::AXIS_ORDER`] indices —
69/// bit `i` set means axis `i` participates in this branch's distance sum. Only
70/// COORDINATE-axis bits may be set; a SELECTOR-axis bit (0,1,2) is a divergence
71/// the verifier rejects.
72///
73/// Layout: 8 bytes, no padding (`u8` + `u8` + `u16` + `u32`, all naturally
74/// aligned). POD so it embeds directly in the header.
75#[repr(C)]
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Pod, Zeroable)]
77pub struct MetricBranchDescriptor {
78    /// The `v` class this row describes: `0`, `1`, `2`, or `255` for the
79    /// `v >= 3` catch-all.
80    pub v_class: u8,
81    /// The metric kind — see [`MetricKind`].
82    pub metric_kind: u8,
83    /// Bitmask of folded COORDINATE axes (bit `i` = axis `i` in `AXIS_ORDER`).
84    pub folded_axes: u16,
85    /// Reserved, must be zero. Future use (e.g. per-branch scale weights).
86    pub reserved: u32,
87}
88
89impl MetricBranchDescriptor {
90    /// True if axis `i` is declared folded by this branch.
91    #[inline]
92    pub const fn folds_axis(self, i: usize) -> bool {
93        (self.folded_axes >> i) & 1 == 1
94    }
95}
96
97/// The full metric-completeness table carried in the `.10d` header. One row per
98/// `v`-class; `branches[3]` is the `v >= 3` catch-all.
99///
100/// Layout: 4 × 8 = 32 bytes, no padding.
101#[repr(C)]
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Pod, Zeroable)]
103pub struct MetricCompletenessDescriptor {
104    pub branches: [MetricBranchDescriptor; METRIC_BRANCH_COUNT],
105}
106
107/// Error raised when a descriptor diverges from `full_distance`'s actual
108/// behaviour. Carries enough detail to name the offending branch + axis in the
109/// parser's rejection message.
110#[derive(Debug, Clone, PartialEq, Eq)]
111pub struct MetricDivergence {
112    pub v_class: u8,
113    pub axis_index: usize,
114    pub declared_folds: bool,
115    pub actual_folds: bool,
116}
117
118impl fmt::Display for MetricDivergence {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        let axis = super::axis_role::AXIS_ORDER[self.axis_index];
121        write!(
122            f,
123            "metric-completeness divergence: v={} claims axis {} is {}folded but full_distance {}folds it",
124            self.v_class,
125            axis,
126            if self.declared_folds { "" } else { "not " },
127            if self.actual_folds { "" } else { "does not " },
128        )
129    }
130}
131
132impl std::error::Error for MetricDivergence {}
133
134/// Probe `Tensor10D::full_distance` to determine whether axis `axis_index` is
135/// folded under the branch selected by `v_class`. Returns `true` if varying
136/// only that axis (with `v` held at `v_class`) changes the distance.
137///
138/// This is the introspection step that makes the honesty gate mechanical: it
139/// reads the actual kernel behaviour, not a hardcoded claim.
140fn axis_is_folded_by_full_distance(v_class: u8, axis_index: usize) -> bool {
141    // Base tensor: v set to the branch class, all coordinates zero, alpha=1
142    // (the Tensor10D default) so the spectral sum is non-degenerate.
143    let mut base = Tensor10D::default();
144    base.v = v_class as f32;
145    let mut perturbed = base;
146    perturb_axis(&mut perturbed, axis_index);
147    let d_base = base.full_distance(&base);
148    let d_perturbed = base.full_distance(&perturbed);
149    // An axis is "folded" if perturbing it changes the distance away from the
150    // identity distance. Use a small epsilon to tolerate f32 rounding noise.
151    (d_perturbed - d_base).abs() > 1e-6
152}
153
154/// Perturb a single axis lane by a non-trivial delta, leaving the other nine
155/// lanes (including `v`) untouched. The deltas are chosen to be large enough
156/// that f32 rounding cannot mask the distance change, and (for the cyclic
157/// branch) inside the modulo-1 unit cell.
158fn perturb_axis(t: &mut Tensor10D, axis_index: usize) {
159    // A delta of 0.25 is inside the cyclic unit cell (mod-1 wrap) and large
160    // relative to f32 epsilon, so it produces a clear distance change in every
161    // branch that folds the axis.
162    const DELTA: f32 = 0.25;
163    match axis_index {
164        0 => t.q += DELTA,
165        1 => t.v += DELTA,
166        2 => t.w += DELTA,
167        3 => t.x += DELTA,
168        4 => t.y += DELTA,
169        5 => t.z += DELTA,
170        6 => t.t += DELTA,
171        7 => t.alpha += DELTA,
172        8 => t.mu += DELTA,
173        9 => t.sigma += DELTA,
174        _ => unreachable!("axis_index out of range"),
175    }
176}
177
178/// Probe `full_distance` for one branch and return the bitmask of COORDINATE
179/// axes it actually folds. Only COORDINATE axes are probed — SELECTOR axes are
180/// excluded by definition and a descriptor claiming to fold one is a divergence.
181pub fn probe_folded_axes(v_class: u8) -> u16 {
182    let mut mask: u16 = 0;
183    for &i in &COORDINATE_AXES {
184        if axis_is_folded_by_full_distance(v_class, i) {
185            mask |= 1 << i;
186        }
187    }
188    mask
189}
190
191/// Verify a [`MetricCompletenessDescriptor`] against the actual
192/// `Tensor10D::full_distance` behaviour. Returns the first divergence found, or
193/// `Ok(())` if every branch's declared `folded_axes` and `metric_kind` match
194/// reality.
195///
196/// This is the function the parser calls — a diverging descriptor fails the
197/// parse (the P0.1 acceptance gate: "parse rejects ... a metric-completeness
198/// descriptor that a unit test shows diverges from full_distance's actual
199/// v-branch behaviour").
200pub fn verify_descriptor_against_reality(
201    descriptor: &MetricCompletenessDescriptor,
202) -> Result<(), MetricDivergence> {
203    for (row_index, branch) in descriptor.branches.iter().enumerate() {
204        // The catch-all row (index 3) describes v >= 3; probe with v = 3.
205        let v_class = if row_index == BOUNDARY_CLIQUE_BRANCH_INDEX {
206            3
207        } else {
208            branch.v_class
209        };
210
211        // Reject Undefined metric kind.
212        let declared_kind = MetricKind::from_u8(branch.metric_kind);
213        match (declared_kind, v_class) {
214            (Some(MetricKind::Euclidean), 0) => {}
215            (Some(MetricKind::CyclicToroidal), 1) => {}
216            (Some(MetricKind::Hyperbolic), 2) => {}
217            (Some(MetricKind::BoundaryClique), 3) => {}
218            (Some(MetricKind::Undefined), _) => {
219                return Err(MetricDivergence {
220                    v_class,
221                    axis_index: 0,
222                    declared_folds: false,
223                    actual_folds: false,
224                });
225            }
226            _ => {
227                // metric_kind does not match the v_class — divergence.
228                return Err(MetricDivergence {
229                    v_class,
230                    axis_index: 0,
231                    declared_folds: false,
232                    actual_folds: false,
233                });
234            }
235        }
236
237        let actual = probe_folded_axes(v_class);
238        for &i in &COORDINATE_AXES {
239            let declared_folds = branch.folds_axis(i);
240            let actual_folds = (actual >> i) & 1 == 1;
241            if declared_folds != actual_folds {
242                return Err(MetricDivergence {
243                    v_class,
244                    axis_index: i,
245                    declared_folds,
246                    actual_folds,
247                });
248            }
249        }
250    }
251    Ok(())
252}
253
254/// The proposed (not-yet-frozen) metric-completeness descriptor encoding the
255/// current `full_distance` reality — option (b), the documented limitation.
256/// The header's descriptor field is initialised from this by
257/// [`super::header::Container10dHeader::proposed`].
258pub const fn proposed_metric_descriptor() -> MetricCompletenessDescriptor {
259    const fn branch(v: u8, kind: u8, folded: u16) -> MetricBranchDescriptor {
260        MetricBranchDescriptor {
261            v_class: v,
262            metric_kind: kind,
263            folded_axes: folded,
264            reserved: 0,
265        }
266    }
267    // Bitmasks over AXIS_ORDER indices: x=bit3, y=bit4, z=bit5, t=bit6,
268    // α=bit7, μ=bit8, σ=bit9.
269    const XYZ: u16 = (1 << 3) | (1 << 4) | (1 << 5);
270    const ALL_SEVEN: u16 = XYZ | (1 << 6) | (1 << 7) | (1 << 8) | (1 << 9);
271    MetricCompletenessDescriptor {
272        branches: [
273            branch(0, MetricKind::Euclidean as u8, ALL_SEVEN),
274            branch(1, MetricKind::CyclicToroidal as u8, XYZ),
275            branch(2, MetricKind::Hyperbolic as u8, XYZ),
276            branch(255, MetricKind::BoundaryClique as u8, 0),
277        ],
278    }
279}
280
281#[cfg(test)]
282mod tests {
283    use super::*;
284
285    #[test]
286    fn probe_v0_euclidean_folds_all_seven_coordinates() {
287        let mask = probe_folded_axes(0);
288        // x,y,z,t,α,μ,σ
289        assert_eq!(
290            mask,
291            (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7) | (1 << 8) | (1 << 9)
292        );
293    }
294
295    #[test]
296    fn probe_v1_cyclic_folds_xyz_only() {
297        let mask = probe_folded_axes(1);
298        assert_eq!(
299            mask,
300            (1 << 3) | (1 << 4) | (1 << 5),
301            "v=1 must fold only x,y,z; got {mask:#b}"
302        );
303        // explicitly: t, α, μ, σ are NOT folded
304        assert_eq!(mask & (1 << 6), 0, "t must not be folded under v=1");
305        assert_eq!(mask & (1 << 7), 0, "α must not be folded under v=1");
306        assert_eq!(mask & (1 << 8), 0, "μ must not be folded under v=1");
307        assert_eq!(mask & (1 << 9), 0, "σ must not be folded under v=1");
308    }
309
310    #[test]
311    fn probe_v2_hyperbolic_folds_xyz_only() {
312        let mask = probe_folded_axes(2);
313        assert_eq!(
314            mask,
315            (1 << 3) | (1 << 4) | (1 << 5),
316            "v=2 must fold only x,y,z; got {mask:#b}"
317        );
318    }
319
320    #[test]
321    fn probe_v3_boundary_folds_no_coordinate_axes() {
322        let mask = probe_folded_axes(3);
323        assert_eq!(
324            mask, 0,
325            "v>=3 boundary clique must fold no coordinate axes; got {mask:#b}"
326        );
327    }
328
329    #[test]
330    fn proposed_descriptor_matches_reality() {
331        let desc = proposed_metric_descriptor();
332        assert!(
333            verify_descriptor_against_reality(&desc).is_ok(),
334            "the proposed (option b) descriptor must match full_distance's actual behaviour"
335        );
336    }
337
338    #[test]
339    fn diverging_descriptor_claiming_v1_folds_t_is_rejected() {
340        let mut desc = proposed_metric_descriptor();
341        // Claim v=1 (cyclic) folds t (bit 6) — it does not.
342        desc.branches[1].folded_axes |= 1 << 6;
343        let err =
344            verify_descriptor_against_reality(&desc).expect_err("must reject diverging claim");
345        assert_eq!(err.v_class, 1);
346        assert_eq!(err.axis_index, 6, "divergence must name axis t (index 6)");
347        assert!(err.declared_folds);
348        assert!(!err.actual_folds);
349    }
350
351    #[test]
352    fn diverging_descriptor_claiming_v0_ignores_sigma_is_rejected() {
353        let mut desc = proposed_metric_descriptor();
354        // Claim v=0 does NOT fold σ (clear bit 9) — it does.
355        desc.branches[0].folded_axes &= !(1 << 9);
356        let err =
357            verify_descriptor_against_reality(&desc).expect_err("must reject diverging claim");
358        assert_eq!(err.v_class, 0);
359        assert_eq!(err.axis_index, 9, "divergence must name axis σ (index 9)");
360        assert!(!err.declared_folds);
361        assert!(err.actual_folds);
362    }
363
364    #[test]
365    fn diverging_descriptor_claiming_v3_folds_x_is_rejected() {
366        let mut desc = proposed_metric_descriptor();
367        // Claim v>=3 boundary folds x (bit 3) — it folds nothing.
368        desc.branches[BOUNDARY_CLIQUE_BRANCH_INDEX].folded_axes |= 1 << 3;
369        let err =
370            verify_descriptor_against_reality(&desc).expect_err("must reject diverging claim");
371        assert_eq!(err.v_class, 3);
372        assert_eq!(err.axis_index, 3);
373    }
374
375    #[test]
376    fn wrong_metric_kind_for_v_class_is_rejected() {
377        let mut desc = proposed_metric_descriptor();
378        // Claim v=0 uses the hyperbolic metric — wrong.
379        desc.branches[0].metric_kind = MetricKind::Hyperbolic as u8;
380        assert!(verify_descriptor_against_reality(&desc).is_err());
381    }
382
383    #[test]
384    fn undefined_metric_kind_is_rejected() {
385        let mut desc = proposed_metric_descriptor();
386        desc.branches[0].metric_kind = MetricKind::Undefined as u8;
387        assert!(verify_descriptor_against_reality(&desc).is_err());
388    }
389}