diff --git a/docs/native-mobius-comparison.md b/docs/native-mobius-comparison.md new file mode 100644 index 00000000..6865d1f5 --- /dev/null +++ b/docs/native-mobius-comparison.md @@ -0,0 +1,188 @@ +# Native Möbius comparison — derived root-loop law v1 + +## Domain and standing + +`ucns.native-mobius-comparison@1.0.0` names an exact displacement class +between complete framed states in **one already identified native root-loop +chart**. UCNS owns this geometric sense. The implementation is an opt-in derived +geometry module; it neither selects a full UCNS carrier nor changes CANON.md. + +Here **comparison** is relative complete-state displacement. **Transport** is +application of that displacement class using `NativeMobiusState.advance`. +Neither word authorizes a relation between independently rooted application +objects, communication, synchronization, a codec operation, or an inferred +traversal history. Those potentially colliding senses are explicitly excluded. +The domain claim is provisional for downstream adoption; the algebra below is +conditional only on the existing exact root-loop definition. + +The native authority is `src/ucns/direct_mobius.py` at UCNS commit +`5a042416ef62abd9b674523284c2a31193918d66`, Git blob +`14a4cee36b5bbfa72cf3c03703c427abdac7f33d`. Its quotient and motion remain unchanged. +This follows METAPAT's domain restraint: geometry supplies no semantic, +empirical, cryptographic, or protocol standing merely by structural resemblance. + +## Problem and frozen acceptance criteria + +Multiplying canonical endpoint frame signs alone is not invariant under equal +native motion: exactly one endpoint can cross the representative seam. The +comparison must retain complete target reconstruction while removing this +coordinate artifact. Dropping the frame bit is not a repair. + +Acceptance requires exact reconstruction, common-motion invariance, equivalent +representative invariance, orientation-reversal covariance, composition, +inverse, identity, the frame carry cocycle, malformed-input rejection, and +sensitivity to both the naive-product and phase-only ablations. Tests must +retain the 360-degree frame reversal and the 720-degree complete return. +No origin alignment, path winding, higher geometry, or downstream utility is +admitted by these criteria. + +Implementation plan: add `src/ucns/mobius_comparison.py`, its source-owned +contracts, `tests/test_mobius_comparison.py` with accountable witnesses, and this +document plus `docs/work-graphs/native-mobius-comparison-inputs.json`. The dependency is the unchanged native motion implementation. All +state is immutable and exact; there are no network, storage, permission, or +user-data effects. Rollback removes these four files and any explicit future +consumer pins. The package root facade and native law remain unchanged. + +## Derivation + +Write a canonical native state as `s = (p, e)`, where `0 <= p < 1`, `p` is +rational, and `e` is either +1 or -1. The existing quotient is + + (t, e) ~ (t+n, (-1)^n e), n in Z. + +Let `b(e) = (1-e)/2`, and define the complete-state coordinate + + q([t,e]) = t + b(e) (mod 2). + +This is well-defined: replacing `(t,e)` by `(t+n,(-1)^n e)` changes +`t+b(e)` by an even integer. Conversely, equality modulo two implies an +integer difference in `t` with exactly the required frame parity. Thus `q` +is a bijection between the exact native framed state set and `Q / 2Z`. +It is a coordinate on the existing framed root loop, not an untwisted +visible-circle substitute or an assertion about all UCNS geometry. + +For native motion `A_u`, set `k = floor(p+u)`. The existing implementation gives + + A_u(p,e) = (p+u-k, (-1)^k e). + +Therefore + + q(A_u(s)) = q(s) + u (mod 2). + +Define the comparison from `a` to `b` by its canonical representative: + + D(a,b) = (q(b)-q(a)) mod 2, 0 <= D < 2. + +This immediately proves, for every admitted exact rational input: + + A_D(a) = b, + D(A_u(a), A_u(b)) = D(a,b), + D(a,c) = D(a,b) + D(b,c) (mod 2), + D(b,a) = -D(a,b) (mod 2), + D(a,a) = 0. + +The displacement class is unique because `q` is bijective. No new preferred +zero was chosen: simultaneous translation of the existing chart cancels in +the difference. + +### The missing seam correction + +Let `d = (p_b-p_a) mod 1`. Transport over this nonnegative visible arc crosses +the canonical seam exactly when `p_b < p_a`. Consequently the corrected frame +comparison is + + chi(a,b) = e_a e_b (-1)^[p_b < p_a], + D(a,b) = d + (1-chi(a,b))/2. + +The extra sign compares the target frame with the source frame **after** +transport over the specified visible arc. It is not another independent bit +invented from metadata. The implementation stores only `D`; `d` and `chi` are +projections, so they cannot drift apart. + +Composition of the split representation has a carry: + + chi(a,c) = chi(a,b) chi(b,c) (-1)^floor(d(a,b)+d(b,c)). + +Multiplying split signs without this carry would recreate the original defect +at composition rather than endpoint comparison. + +### Coordinate scope + +Common exact rational translations leave `D` invariant. Independently changing +integer representatives of either endpoint leaves their quotient states and +`D` unchanged. Reversing the native chart orientation sends `q` to `-q`, so it +sends `D` to `-D mod 2`: **covariance**, not invariance under reflection. + +These statements concern the declared native affine charts and quotient +representatives. Arbitrary nonlinear reparameterizations or independently +chosen local frame gauges require their own transformation/connection rule; +this module does not silently claim that broader scope. + +## Usage + +```python +from fractions import Fraction +from ucns.direct_mobius import native_mobius_state +from ucns.mobius_comparison import compare_native_mobius + +a = native_mobius_state(Fraction(3, 4)) +b = native_mobius_state(Fraction(1, 4)) +c = native_mobius_state(Fraction(2, 3)) +ab = compare_native_mobius(a, b) +bc = compare_native_mobius(b, c) + +assert ab.relative_turns == Fraction(3, 2) +assert ab.phase_turns == Fraction(1, 2) +assert ab.frame_sign == -1 +assert ab.transport(a) == b +assert ab.inverse().transport(b) == a +assert ab.then(bc) == compare_native_mobius(a, c) +assert compare_native_mobius(a.advance(Fraction(1, 2)), + b.advance(Fraction(1, 2))) == ab +``` + +The constructor `NativeMobiusComparison` accepts only canonical `Fraction` +values in `[0,2)`. Prefer the comparison function for endpoint inputs. Negative +or multi-turn **motion** remains available through the native `advance` method; +comparison intentionally records only the resulting class modulo two. + +Consumers must establish common-chart eligibility **before** calling this +module. Equal coordinate numbers, equal frame labels, or serialization ancestry +alone do not establish alignment of independent origins. + +## Falsification and replay + +Run the complete source, distribution, and replay gates in the repository +README. The six new accountable tests additionally provide: + +- all 16 complete states on the eight-phase grid, all 256 ordered endpoint + pairs, and all 33 motions from -2 to +2 turns in eighth-turn steps: 8,448 + checks; corrected changes 0, naive endpoint-product changes 2,688; +- exact rational non-grid and 80-digit near-seam inputs; independent endpoint + representative changes and orientation-reversal covariance; +- all 4,096 triples on that full grid for composition and the carry cocycle; +- explicit counterexamples to the naive product and phase-only substitutes; +- rejected floats, booleans, invalid ranges/types, and immutable records; +- one-turn versus two-turn return, and the endpoint-only winding limitation. + +Those finite execution counts are implementation witnesses. The general claim +rests on the exact algebra above, not on extrapolating a finite grid. + +## hmmm + +Complete-state comparison is implemented. Alignment between independently +rooted charts, actual path winding beyond modulo two, origin-to-origin protocol +transport, nontrivial holonomy from observed paths, higher-scale geometry, +downstream utility, and full-carrier selection remain outside this result. +In particular, endpoint comparisons around a closed complete-state chain +compose to identity; that tautology is not new evidence of path holonomy. + +## Input work graph + +`docs/work-graphs/native-mobius-comparison-inputs.json` is the shared exact +input-authority graph, not a claim about a future consumer deployment. Its digest +is `bd1059322ef8223d2a6531f84591e76970db67aa3982f1cbfd174d5039b5ecd5`. The output implementation is identified separately by +the delivering commit and source hashes. Consumer receipts must additionally pin +the delivered UCNS implementation; they must not mistake the native-law input +commit in this graph for that later output. Certification and empirical status +also do not transfer. This adds no participant to Stack and changes no global pin. diff --git a/docs/work-graphs/native-mobius-comparison-inputs.json b/docs/work-graphs/native-mobius-comparison-inputs.json new file mode 100644 index 00000000..fd739d78 --- /dev/null +++ b/docs/work-graphs/native-mobius-comparison-inputs.json @@ -0,0 +1,41 @@ +{ + "boundaries": { + "agent_scope": "cross-repository-work-graph", + "authority_transfer": false, + "hmmm": [ + "Independent-origin alignment, absolute winding, and protocol transport are not supplied by complete-state comparison." + ], + "measurement_status_transfer": false, + "proof_status_transfer": false, + "semantic_mapping": "external-provenance" + }, + "repositories": [ + { + "authority": "reusable build and evidence doctrine", + "commit": "b83fa99b4138ece8ecd9cf6a00c82dc9c8b0bcf3", + "relation": "operating doctrine; does not replace the repository-pinned validation snapshot", + "repository": "The-Interdependency/skill-lib" + }, + { + "authority": "domain restraint and semantic boundaries", + "commit": "e4165b0cac9eca41daef9c2f941881028ca55d48", + "relation": "consultation only; no semantic interpretation is imported", + "repository": "The-Interdependency/metapat" + }, + { + "authority": "exact native framed root-loop geometry", + "commit": "5a042416ef62abd9b674523284c2a31193918d66", + "relation": "unchanged native-law input to the derived comparison", + "repository": "The-Interdependency/ucns" + }, + { + "authority": "URPCS experiment and sealed relational-audit evidence", + "commit": "4a03f68c1ea841da5767813c6a65c41620f561f6", + "relation": "problem evidence and prospective separately pinned consumer", + "repository": "The-Interdependency/stack" + } + ], + "schema": "the-interdependency.stack-manifest", + "version": "1.0.0", + "work_graph_sha256": "bd1059322ef8223d2a6531f84591e76970db67aa3982f1cbfd174d5039b5ecd5" +} diff --git a/src/ucns/mobius_comparison.py b/src/ucns/mobius_comparison.py new file mode 100644 index 00000000..2cd63ed7 --- /dev/null +++ b/src/ucns/mobius_comparison.py @@ -0,0 +1,159 @@ +# === MODULE_BUILD === +# id: ucns_native_mobius_comparison +# module_name: mobius_comparison +# module_kind: experiment +# summary: exact relative complete-state displacement derived from the existing framed root-loop quotient +# owner: Erin Spencer +# public_surface: NativeMobiusComparison, MobiusComparisonError, compare_native_mobius +# internal_surface: _state_coordinate, _require_state +# auth_boundary: none +# storage_boundary: none +# network_boundary: none +# user_data_boundary: none +# admin_only: false +# tests: tests/test_mobius_comparison.py +# rollout: opt-in derived geometry module; no change to the native motion law or selected UCNS canon +# rollback: remove this module, its tests, and its documentation +# requires: ucns_native_mobius_geometry +# since: 2026-09-25 +# unresolved: alignment of independently rooted charts and path winding beyond complete-state equivalence +# === END MODULE_BUILD === + +# === CONTRACTS === +# id: mobius_comparison_reconstructs_complete_target +# given: two exact native states in the same identified root-loop chart +# then: their comparison transports the source to the complete target and retains one-turn frame reversal +# class: correctness +# +# id: mobius_comparison_is_native_chart_covariant +# given: common exact motion, equivalent quotient representatives, or a reversal of native chart orientation +# then: comparison is invariant in the first two cases and negates modulo two under orientation reversal +# class: correctness +# +# id: mobius_comparison_composes_exactly +# given: three states in one native chart and their pairwise comparisons +# then: composition, identity, inverse, and the visible-phase carry cocycle agree exactly +# class: correctness +# +# id: mobius_comparison_rejects_inexact_inputs +# given: invalid states or a noncanonical or inexact relative displacement +# then: construction and application fail explicitly without coercing floats or booleans +# class: safety +# +# id: mobius_comparison_preserves_evidence_boundary +# given: endpoints rather than a path witness +# then: comparison records displacement modulo two only and supplies neither absolute winding nor cross-origin alignment +# class: evidence +# === END CONTRACTS === + +"""Comparison on the existing exact framed root loop, not a new wire law. + +Usage:: + + from fractions import Fraction + from ucns.direct_mobius import native_mobius_state + from ucns.mobius_comparison import compare_native_mobius + + a = native_mobius_state(Fraction(3, 4)) + b = native_mobius_state(Fraction(1, 4)) + relation = compare_native_mobius(a, b) + assert relation.relative_turns == Fraction(3, 2) + assert relation.transport(a) == b + assert relation == compare_native_mobius(a.advance(1), b.advance(1)) + +States must already share an identified native root-loop chart. This module +cannot authorize comparison across independently rooted consumer objects. +The result is a displacement class modulo two turns, not a recovered path or +an assertion about total winding. See docs/native-mobius-comparison.md for the +exact derivation, covariance scope, falsifiers, and non-transfer boundary. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from fractions import Fraction + +from .direct_mobius import NativeMobiusFrame, NativeMobiusState + +COMPARISON_LAW_ID = "ucns.native-mobius-comparison" +COMPARISON_LAW_VERSION = "1.0.0" +COMPARISON_STANDING = "derived-root-loop-comparison; not selected full UCNS geometry" + + +class MobiusComparisonError(ValueError): + """Input is outside the exact native comparison domain.""" + + +def _require_state(state: NativeMobiusState) -> None: + if not isinstance(state, NativeMobiusState): + raise MobiusComparisonError("comparison requires a NativeMobiusState") + + +def _state_coordinate(state: NativeMobiusState) -> Fraction: + _require_state(state) + return state.phase_turns + int(state.frame is NativeMobiusFrame.REVERSED) + + +@dataclass(frozen=True, slots=True) +class NativeMobiusComparison: + """A canonical element of Q / 2Z acting on complete native states. + + Construct with an exact Fraction in [0, 2), or use compare_native_mobius. + The frame comparison is measured after transport along the nonnegative + visible arc; it is not the naive product of the endpoint frame signs. + """ + + relative_turns: Fraction + + def __post_init__(self) -> None: + if not isinstance(self.relative_turns, Fraction): + raise MobiusComparisonError("relative_turns must be an exact Fraction") + if not Fraction(0) <= self.relative_turns < Fraction(2): + raise MobiusComparisonError("relative_turns must lie in [0, 2)") + + @property + def phase_turns(self) -> Fraction: + """Return the relative visible phase, retaining the full state separately.""" + return self.relative_turns % 1 + + @property + def frame_sign(self) -> int: + """Compare target frame with source frame transported over the visible arc.""" + return -1 if self.relative_turns >= 1 else 1 + + def transport(self, source: NativeMobiusState) -> NativeMobiusState: + """Apply this displacement class using the unchanged native motion law.""" + _require_state(source) + return source.advance(self.relative_turns) + + def inverse(self) -> NativeMobiusComparison: + """Return the inverse displacement class, not an inferred reverse path.""" + return NativeMobiusComparison((-self.relative_turns) % 2) + + def then(self, after: NativeMobiusComparison) -> NativeMobiusComparison: + """Compose displacement classes; callers own endpoint/chart compatibility.""" + if not isinstance(after, NativeMobiusComparison): + raise MobiusComparisonError("composition requires a NativeMobiusComparison") + return NativeMobiusComparison((self.relative_turns + after.relative_turns) % 2) + + +def compare_native_mobius( + source: NativeMobiusState, + target: NativeMobiusState, +) -> NativeMobiusComparison: + """Return the unique complete-state displacement class from source to target. + + Both states must already use the same identified native chart. No origin + label, authorization, event, wire field, or transport edge is inferred. + """ + return NativeMobiusComparison((_state_coordinate(target) - _state_coordinate(source)) % 2) + + +__all__ = [ + "COMPARISON_LAW_ID", + "COMPARISON_LAW_VERSION", + "COMPARISON_STANDING", + "MobiusComparisonError", + "NativeMobiusComparison", + "compare_native_mobius", +] diff --git a/tests/test_mobius_comparison.py b/tests/test_mobius_comparison.py new file mode 100644 index 00000000..03a8c5d3 --- /dev/null +++ b/tests/test_mobius_comparison.py @@ -0,0 +1,185 @@ +# === CHECKS === +# id: check_mobius_comparison_complete_grid +# proves: mobius_comparison_reconstructs_complete_target, mobius_comparison_is_native_chart_covariant +# call: self::test_complete_eighth_turn_grid +# requires: python3 +# timeout: 30 +# mutates: none +# cleanup: none +# +# id: check_mobius_comparison_chart_changes +# proves: mobius_comparison_is_native_chart_covariant +# call: self::test_exact_rational_chart_changes +# requires: python3 +# timeout: 30 +# mutates: none +# cleanup: none +# +# id: check_mobius_comparison_composition +# proves: mobius_comparison_composes_exactly +# call: self::test_composition_inverse_and_cocycle +# requires: python3 +# timeout: 30 +# mutates: none +# cleanup: none +# +# id: check_mobius_comparison_ablations +# proves: mobius_comparison_reconstructs_complete_target, mobius_comparison_is_native_chart_covariant, mobius_comparison_preserves_evidence_boundary +# call: self::test_seam_and_phase_only_ablations +# requires: python3 +# timeout: 10 +# mutates: none +# cleanup: none +# +# id: check_mobius_comparison_rejections +# proves: mobius_comparison_rejects_inexact_inputs +# call: self::test_invalid_inputs_and_immutability +# requires: python3 +# timeout: 10 +# mutates: none +# cleanup: none +# +# id: check_mobius_comparison_winding_boundary +# proves: mobius_comparison_preserves_evidence_boundary, mobius_comparison_reconstructs_complete_target +# call: self::test_one_turn_two_turn_and_winding_boundary +# requires: python3 +# timeout: 10 +# mutates: none +# cleanup: none +# === END CHECKS === + +"""Exact comparison witnesses. Usage: run the complete README geometry gate. + +The eight-phase grid exhausts BOTH frames, all ordered endpoint pairs, and all +33 declared signed motions. Other rational witnesses complement, rather than +replace, the general algebraic proof in docs/native-mobius-comparison.md. +""" + +from __future__ import annotations + +from dataclasses import FrozenInstanceError +from fractions import Fraction +from itertools import product + +import pytest + +from ucns.direct_mobius import NativeMobiusFrame, NativeMobiusState, native_mobius_state +from ucns.mobius_comparison import ( + MobiusComparisonError, + NativeMobiusComparison, + compare_native_mobius, +) + + +def _grid() -> tuple[NativeMobiusState, ...]: + return tuple(NativeMobiusState(Fraction(k, 8), frame) for k in range(8) for frame in NativeMobiusFrame) + + +def test_complete_eighth_turn_grid() -> None: + states = _grid() + motions = tuple(Fraction(k, 8) for k in range(-16, 17)) + checks = 0 + raw_changes = 0 + for source, target in product(states, repeat=2): + relation = compare_native_mobius(source, target) + assert relation.transport(source) == target + assert relation.inverse().transport(target) == source + visible = (target.phase_turns - source.phase_turns) % 1 + seam_carry = int(target.phase_turns < source.phase_turns) + transported_sign = source.frame.sign * target.frame.sign * (-1) ** seam_carry + assert relation.phase_turns == visible + assert relation.frame_sign == transported_sign + for motion in motions: + moved_source = source.advance(motion) + moved_target = target.advance(motion) + assert compare_native_mobius(moved_source, moved_target) == relation + raw_changes += int(source.frame.sign * target.frame.sign != moved_source.frame.sign * moved_target.frame.sign) + checks += 1 + assert checks == 8448 + assert raw_changes == 2688 + + +def test_exact_rational_chart_changes() -> None: + phases = (Fraction(0), Fraction(1, 3), Fraction(2, 5), Fraction(6, 7), Fraction(10**80 - 1, 10**80)) + states = tuple(NativeMobiusState(phase, frame) for phase in phases for frame in NativeMobiusFrame) + motions = (Fraction(-10**80, 7), Fraction(-19, 3), Fraction(-1, 10**80), Fraction(0), Fraction(1, 10**80), Fraction(37, 13), Fraction(10**80, 11)) + for source, target in product(states, repeat=2): + relation = compare_native_mobius(source, target) + assert relation.transport(source) == target + for motion in motions: + assert compare_native_mobius(source.advance(motion), target.advance(motion)) == relation + reflected_source = native_mobius_state(-source.phase_turns, source.frame) + reflected_target = native_mobius_state(-target.phase_turns, target.frame) + assert compare_native_mobius(reflected_source, reflected_target) == relation.inverse() + for source_shift, target_shift in product(range(-2, 3), repeat=2): + source_frame = source.frame.flipped() if source_shift % 2 else source.frame + target_frame = target.frame.flipped() if target_shift % 2 else target.frame + equivalent_source = native_mobius_state(source.phase_turns + source_shift, source_frame) + equivalent_target = native_mobius_state(target.phase_turns + target_shift, target_frame) + assert equivalent_source == source and equivalent_target == target + assert compare_native_mobius(equivalent_source, equivalent_target) == relation + + +def test_composition_inverse_and_cocycle() -> None: + states = _grid() + identity = NativeMobiusComparison(Fraction(0)) + for a, b, c in product(states, repeat=3): + ab = compare_native_mobius(a, b) + bc = compare_native_mobius(b, c) + ac = compare_native_mobius(a, c) + assert ab.then(bc) == ac + assert ab.then(bc).transport(a) == bc.transport(ab.transport(a)) == c + assert ab.then(ab.inverse()) == identity + assert identity.then(ab) == ab.then(identity) == ab + assert ab.inverse() == compare_native_mobius(b, a) + carry = (ab.phase_turns + bc.phase_turns) // 1 + assert ac.frame_sign == ab.frame_sign * bc.frame_sign * (-1) ** carry + for a, b, c, d in product(states[::4], repeat=4): + ab, bc, cd = compare_native_mobius(a, b), compare_native_mobius(b, c), compare_native_mobius(c, d) + assert ab.then(bc).then(cd) == ab.then(bc.then(cd)) + + +def test_seam_and_phase_only_ablations() -> None: + a = native_mobius_state(Fraction(3, 4)) + b = native_mobius_state(Fraction(1, 4)) + aa, bb = a.advance(Fraction(1, 2)), b.advance(Fraction(1, 2)) + assert a.frame.sign * b.frame.sign == 1 + assert aa.frame.sign * bb.frame.sign == -1 + relation = compare_native_mobius(a, b) + assert relation == compare_native_mobius(aa, bb) + assert relation.relative_turns == Fraction(3, 2) + assert relation.frame_sign == -1 + assert relation.transport(a) == b + assert a.advance(relation.phase_turns) != b # Removing the frame bit loses the target. + opposite = b.advance(1) + assert compare_native_mobius(a, opposite).phase_turns == relation.phase_turns + assert compare_native_mobius(a, opposite) != relation + + +def test_invalid_inputs_and_immutability() -> None: + for value in (0, 1, True, False, 0.5, float("nan"), float("inf"), "0", None, Fraction(-1, 8), Fraction(2), Fraction(17, 8)): + with pytest.raises(MobiusComparisonError): + NativeMobiusComparison(value) + origin = native_mobius_state() + relation = compare_native_mobius(origin, origin) + for value in (None, True, 0, "state", object()): + with pytest.raises(MobiusComparisonError): + compare_native_mobius(value, origin) + with pytest.raises(MobiusComparisonError): + compare_native_mobius(origin, value) + with pytest.raises(MobiusComparisonError): + relation.transport(value) + with pytest.raises(MobiusComparisonError): + relation.then(value) + with pytest.raises(FrozenInstanceError): + relation.relative_turns = Fraction(1) + + +def test_one_turn_two_turn_and_winding_boundary() -> None: + for source in _grid(): + assert compare_native_mobius(source, source.advance(1)).relative_turns == 1 + assert compare_native_mobius(source, source.advance(2)).relative_turns == 0 + assert source.advance(1) != source + assert source.advance(2) == source + assert compare_native_mobius(source, source.advance(1)) == compare_native_mobius(source, source.advance(3)) + assert compare_native_mobius(source, source.advance(-1)) == compare_native_mobius(source, source.advance(1))