The defect
SNMesh.is_same_phase_space (orpheus/sn/mesh/augmented_mesh.py:545-596) is the Problem's identity predicate — the pairing guard Solution.compare (orpheus/sn/solution.py, its only production caller) uses to refuse combining fields from two different discrete problems. It is spelled as CONSTITUENT identity: self.mesh is other.mesh and self.quad is other.quad and <per-material Mixture is> and type(scheme) is type(scheme).
[M] 2026-09-08 (explorer census scratch/_consumers/explorer_problem_solution_census.md §4, probe run at HEAD 7e9b6210), the predicate is wrong in BOTH directions:
- d ≥ 3 — vacuously TRUE for DIFFERENT problems. Axis-native carriers carry
mesh = None (SNMesh.from_axes synthesizes a legacy adapter only at d ≤ 2), so self.mesh is other.mesh reads None is None and two 3-D meshes with DIFFERENT edges (volumes unequal, bulk_space unequal) compare as the same phase space. Solution.compare therefore accepts comparing two different 3-D problems.
- d ≤ 2 — FALSE for the SAME data. Two
from_axes calls with the same edges, the same Quadrature object and the same materials read False, because each call mints a fresh legacy Mesh1D adapter. A saved-and-reloaded Problem can never be "the same phase space" as its original — which defeats the save-state concept the consumers campaign is about to build (.claude/plans/cs4c_binding_design.md §22.5 addendum).
The docstring rules the constituent tier deliberately ("never relax an invariant to a weaker downstream check", L29). The d ≥ 3 arm is not a strong tier, it is no tier: None is None proves nothing. The d ≤ 2 arm compares a per-construction ADAPTER (inbound provenance) rather than the generating datum it adapts (the axes: edges + BC tags; mat_map).
The fix (the consumers campaign's FIRST item — identity is the Problem's first question)
The honest identity is the identity of the GENERATING DATA: axes (edges + boundary-law tags) × mat_map × quadrature × materials × scheme × closure class — content equality where the datum is a value (edges, tags, mat_map), object identity where it is a shared constituent (Quadrature, each Mixture). Comparing axes by content is STRONGER than mesh is, not weaker, so L29 is honoured. Design the predicate together with __eq__/__hash__ of the future SNProblem (structural identity of FunctionSpace landed at CS4c step 6 and is the template: the metric is part of the identity). Gate: a positive control (same data, two constructions → True at d = 1, 2, 3), a negative control per generating datum (edges / a BC tag / mat_map / quadrature / a mixture / the scheme type each flipped → False), and the d = 3 case that reads True today MUST read False.
Blast radius to census (plan-authoring §6b): is_same_phase_space [M] 3 production sites / 1 file + 0 tests?? — re-measure; Solution.compare's callers; the adjoint-weighted collapse (#281) that relies on the guard.
Provenance
Found by the consumers-campaign opener census (2026-09-08), filed after gh issue list --search "is_same_phase_space" → 0 hits. Not fixed inline because the honest predicate IS the campaign's first design question (Problem identity), ruled by the user at the opener; the interim hazard is confined to Solution.compare on d ≥ 3 problems and on d ≤ 2 problems built by two from_axes calls.
The defect
SNMesh.is_same_phase_space(orpheus/sn/mesh/augmented_mesh.py:545-596) is the Problem's identity predicate — the pairing guardSolution.compare(orpheus/sn/solution.py, its only production caller) uses to refuse combining fields from two different discrete problems. It is spelled as CONSTITUENT identity:self.mesh is other.mesh and self.quad is other.quad and <per-material Mixture is> and type(scheme) is type(scheme).[M]2026-09-08 (explorer censusscratch/_consumers/explorer_problem_solution_census.md§4, probe run at HEAD7e9b6210), the predicate is wrong in BOTH directions:mesh = None(SNMesh.from_axessynthesizes a legacy adapter only at d ≤ 2), soself.mesh is other.meshreadsNone is Noneand two 3-D meshes with DIFFERENT edges (volumesunequal,bulk_spaceunequal) compare as the same phase space.Solution.comparetherefore accepts comparing two different 3-D problems.from_axescalls with the same edges, the sameQuadratureobject and the same materials readFalse, because each call mints a fresh legacyMesh1Dadapter. A saved-and-reloaded Problem can never be "the same phase space" as its original — which defeats the save-state concept the consumers campaign is about to build (.claude/plans/cs4c_binding_design.md§22.5 addendum).The docstring rules the constituent tier deliberately ("never relax an invariant to a weaker downstream check", L29). The d ≥ 3 arm is not a strong tier, it is no tier:
None is Noneproves nothing. The d ≤ 2 arm compares a per-construction ADAPTER (inbound provenance) rather than the generating datum it adapts (the axes: edges + BC tags;mat_map).The fix (the consumers campaign's FIRST item — identity is the Problem's first question)
The honest identity is the identity of the GENERATING DATA: axes (edges + boundary-law tags) ×
mat_map× quadrature × materials × scheme × closure class — content equality where the datum is a value (edges, tags,mat_map), object identity where it is a shared constituent (Quadrature, eachMixture). Comparing axes by content is STRONGER thanmesh is, not weaker, so L29 is honoured. Design the predicate together with__eq__/__hash__of the futureSNProblem(structural identity ofFunctionSpacelanded at CS4c step 6 and is the template: the metric is part of the identity). Gate: a positive control (same data, two constructions → True at d = 1, 2, 3), a negative control per generating datum (edges / a BC tag /mat_map/ quadrature / a mixture / the scheme type each flipped → False), and the d = 3 case that readsTruetoday MUST readFalse.Blast radius to census (
plan-authoring§6b):is_same_phase_space[M]3 production sites / 1 file + 0 tests?? — re-measure;Solution.compare's callers; the adjoint-weighted collapse (#281) that relies on the guard.Provenance
Found by the consumers-campaign opener census (2026-09-08), filed after
gh issue list --search "is_same_phase_space"→ 0 hits. Not fixed inline because the honest predicate IS the campaign's first design question (Problem identity), ruled by the user at the opener; the interim hazard is confined toSolution.compareon d ≥ 3 problems and on d ≤ 2 problems built by twofrom_axescalls.