From 9a2b28b3dd4aca89f4d107e81ec51c943ca675ef Mon Sep 17 00:00:00 2001 From: luozhou Date: Mon, 21 Sep 2026 00:29:19 +0800 Subject: [PATCH] Expose reviewed M3 upright SE(2) source implementation --- README.md | 10 + README_EN.md | 12 + docs/api.md | 26 + docs/concepts.md | 13 +- .../_internal/kernels/projected_visibility.py | 1217 ++- src/spatialcf/core/_internal/kernels/so2.py | 590 ++ .../core/_internal/kernels/upright_box.py | 2732 +++++ src/spatialcf/core/backends.py | 29 + src/spatialcf/core/outcome_assembler.py | 440 + src/spatialcf/core/registry.py | 168 + src/spatialcf/core/upright_se2_backend.py | 2880 ++++++ src/spatialcf/core/upright_se2_compiler.py | 5536 ++++++++++ .../core/upright_se2_verification.py | 2541 +++++ src/spatialcf/domain/outcomes.py | 105 + src/spatialcf/domain/upright_se2.py | 8874 +++++++++++++++++ 15 files changed, 25170 insertions(+), 3 deletions(-) create mode 100644 src/spatialcf/core/outcome_assembler.py create mode 100644 src/spatialcf/core/upright_se2_backend.py create mode 100644 src/spatialcf/core/upright_se2_compiler.py create mode 100644 src/spatialcf/core/upright_se2_verification.py create mode 100644 src/spatialcf/domain/upright_se2.py diff --git a/README.md b/README.md index fd01831..db5a870 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,16 @@ general contracts;compatibility backend 验证仍完全属于既有 verifier owner。它不是第二个 solver/verifier,也不是新的 generation route。 +本源码树还包含已逐项验证的 CPU-only +`spatialcf/upright_se2@1` General-IR implementation:它为 direct General-IR callers +提供 world XY translation 与 own/reference-pivot upright yaw。exact cardinal closure +先于 continuous canonical `ARC`/`FULL_CIRCLE` directed interval checking;continuous +请求可能产生有限的未认证 witness 或 `UNKNOWN`,不会被承诺为总能认证。backend 经不重叠的 +`solve_submission` 提交不受信任的 `BackendSubmission`;checker 只产生 +`CheckedProofOutcome`;`core.outcome_assembler` 独占 general checker dispatch、 +certificate 和 terminal-result assembly。它不改变 version-free generation API、现有 +generation route 或 `v0.1.1` tag。 + 当前 public main 与最新 annotated release tag 不同:`v0.1.1` 仍是最新 annotated release tag,尚未发布 `v0.2.0`。 下方 quickstart 因此继续精确使用 `v0.1.1`。 diff --git a/README_EN.md b/README_EN.md index ae67187..35e88e2 100644 --- a/README_EN.md +++ b/README_EN.md @@ -19,6 +19,18 @@ verifier. Existing v2 result/certificate verification remains entirely owned by the existing verifier owner. It is not a second solver or verifier, and it does not create a new generation route. +This source tree also contains the individually verified CPU-only +`spatialcf/upright_se2@1` General-IR implementation. It gives direct +General-IR callers world XY translation and upright yaw about own or reference +pivots. Exact cardinal closure precedes continuous canonical `ARC`/`FULL_CIRCLE` +directed interval checking; a continuous request can yield a limited +uncertified witness or `UNKNOWN` and is not promised universal certification. +Its backend submits untrusted `BackendSubmission` evidence through the disjoint +`solve_submission`; the checker produces only `CheckedProofOutcome`; and +`core.outcome_assembler` solely dispatches general checking and assembles +certificates and terminal results. This does not change the version-free +generation API, the existing generation route, or the `v0.1.1` tag. + Current public `main` is distinct from the latest annotated release tag: `v0.1.1` remains the latest annotated release tag and `v0.2.0` has not been released. The quick start below therefore continues to use the exact `v0.1.1` diff --git a/docs/api.md b/docs/api.md index dd74886..f3ec4f1 100644 --- a/docs/api.md +++ b/docs/api.md @@ -14,6 +14,32 @@ the existing verifier. This is not a second solver or verifier, and it does not add a generation entry point or change the supported `spatialcf.generation` API above. +## Advanced upright SE(2) General-IR implementation + +This source tree contains the CPU-only `spatialcf/upright_se2@1` implementation +for direct General-IR integration. It is an advanced source-level contract, not +a package facade, a new CLI, or an addition to the supported version-free +`spatialcf.generation` API. It does not change the existing generation route or +the `v0.1.1` tag. + +The domain contract is in `spatialcf.domain.upright_se2`; compilation, proposal +submission, fresh checking, and terminal assembly respectively live in +`spatialcf.core.upright_se2_compiler`, `spatialcf.core.upright_se2_backend`, +`spatialcf.core.upright_se2_verification`, and +`spatialcf.core.outcome_assembler`. The profile supports world XY translation +and upright yaw around an own or named reference pivot. Exact cardinal yaw +closes before continuous yaw. Continuous domains use canonical `ARC` or +`FULL_CIRCLE` with exact-dyadic lifted intervals and checked directed bounds. + +`solve_submission` is deliberately disjoint from retained v1 `solve`: it emits +untrusted `BackendSubmission` proposal, complete-domain UNSAT, or UNKNOWN +evidence. The checker produces only `CheckedProofOutcome` and never assembles a +terminal result. `core.outcome_assembler` is the sole general-IR checker +dispatcher, certificate owner, and terminal-result assembler. A finite miss, +numeric gap, unsupported capability, resource exhaustion, or feasible-incomplete +frontier is never fabricated as UNSAT; feasible-incomplete work remains LIMITED +with an uncertified witness. + ## Generate ```python diff --git a/docs/concepts.md b/docs/concepts.md index 6dbc6be..595332b 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -8,8 +8,17 @@ execution. The Schema represents scenes, object geometry, relations, interventions, evidence, and results in canonical coordinates. Core relations are `left`, `right`, `front`, `behind`, `near`, and `far`. A counterfactual moves exactly one -movable object in world X/Y while preserving its height, orientation, scale, -category, camera, lighting, and material. +movable object in world X/Y. The published v2/M2 generation behavior preserves +its height, orientation, scale, category, camera, lighting, and material. + +The separate CPU-only `spatialcf/upright_se2@1` General-IR profile extends only +direct General-IR work with upright yaw around the subject's own pivot or a +named reference pivot; it does not change generation behavior. Exact cardinal +yaw closes before continuous yaw. Continuous requests use canonical +`ARC`/`FULL_CIRCLE` intervals lifted with exact dyadic endpoints and checked +directed bounds. They can remain LIMITED with an uncertified witness or +`UNKNOWN`; finite misses, numeric gaps, unsupported capabilities, and resource +exhaustion are never UNSAT. ## Minimum-cost solver diff --git a/src/spatialcf/core/_internal/kernels/projected_visibility.py b/src/spatialcf/core/_internal/kernels/projected_visibility.py index f68736e..b791913 100644 --- a/src/spatialcf/core/_internal/kernels/projected_visibility.py +++ b/src/spatialcf/core/_internal/kernels/projected_visibility.py @@ -2,11 +2,691 @@ from __future__ import annotations +from dataclasses import dataclass +from enum import StrEnum from fractions import Fraction +from spatialcf.core._internal.kernels import so2 as so2_interval +from spatialcf.core._internal.kernels import upright_box as upright_box_interval +from spatialcf.core._internal.kernels.so2 import ( + CardinalKernelKindV3, + SO2AtomicBudgetExhaustedV2, + SO2AtomicBudgetV2, +) +from spatialcf.core.problem import ( + UprightCameraContextV2_9, + bound_world_point_in_upright_camera, +) + _Interval = tuple[Fraction, Fraction] +@dataclass(frozen=True, slots=True) +class FixedCardinalVisibilityPolicyV3: + """Caller-bound visibility metric and shared-resource values.""" + + metric_definition_id: str + metric_definition_version: str + metric_threshold: Fraction + metric_tolerance: Fraction + metric_comparator: str + metric_boundary: str + atomic_step_limit: int + + def __post_init__(self) -> None: + if ( + type(self.metric_definition_id) is not str + or not self.metric_definition_id + or type(self.metric_definition_version) is not str + or not self.metric_definition_version + or type(self.metric_threshold) is not Fraction + or not Fraction() <= self.metric_threshold <= Fraction(1) + or type(self.metric_tolerance) is not Fraction + or self.metric_tolerance < 0 + or self.metric_comparator != "GEQ" + or self.metric_boundary != "CLOSED" + or type(self.atomic_step_limit) is not int + or self.atomic_step_limit <= 0 + ): + raise ValueError( + "fixed cardinal visibility policy must be a closed caller value" + ) + + +@dataclass(frozen=True, slots=True) +class FixedCardinalProjectionBoxV3: + box_id: str + center_x: Fraction + center_y: Fraction + center_z: Fraction + half_x: Fraction + half_y: Fraction + half_z: Fraction + + def __post_init__(self) -> None: + if ( + type(self.box_id) is not str + or not self.box_id + or any( + type(value) is not Fraction + for value in ( + self.center_x, + self.center_y, + self.center_z, + self.half_x, + self.half_y, + self.half_z, + ) + ) + or min(self.half_x, self.half_y, self.half_z) < 0 + ): + raise ValueError( + "fixed cardinal projection box must be exact and non-negative" + ) + + +@dataclass(frozen=True, slots=True) +class FixedCardinalVisibilityBoundsV3: + cell: tuple[Fraction, Fraction, Fraction, Fraction] + camera_context_sha256: str + projection_convention: str + occluder_roster: tuple[str, ...] + subject_as_occluder: bool + inner_fraction: Fraction + outer_fraction: Fraction + metric_definition_id: str + metric_definition_version: str + metric_threshold: Fraction + metric_tolerance: Fraction + metric_comparator: str + metric_boundary: str + inner_success: bool + outer_failure: bool + gap_unknown: bool + proof_rows: tuple[str, ...] + + def __post_init__(self) -> None: + if ( + type(self.cell) is not tuple + or len(self.cell) != 4 + or any(type(value) is not Fraction for value in self.cell) + or self.cell[0] > self.cell[1] + or self.cell[2] > self.cell[3] + or type(self.camera_context_sha256) is not str + or not self.camera_context_sha256 + or self.projection_convention != "RETAINED_UPRIGHT_CAMERA_CONTEXT_V2_9" + or type(self.occluder_roster) is not tuple + or not self.occluder_roster + or any( + type(value) is not str or not value for value in self.occluder_roster + ) + or self.occluder_roster != tuple(sorted(set(self.occluder_roster))) + or type(self.subject_as_occluder) is not bool + or any( + type(value) is not Fraction + for value in ( + self.inner_fraction, + self.outer_fraction, + self.metric_threshold, + self.metric_tolerance, + ) + ) + or self.inner_fraction < 0 + or self.outer_fraction > 1 + or self.inner_fraction > self.outer_fraction + or type(self.metric_definition_id) is not str + or not self.metric_definition_id + or type(self.metric_definition_version) is not str + or not self.metric_definition_version + or self.metric_tolerance < 0 + or self.metric_comparator != "GEQ" + or self.metric_boundary != "CLOSED" + or type(self.inner_success) is not bool + or type(self.outer_failure) is not bool + or type(self.gap_unknown) is not bool + or self.inner_success + is not ( + self.inner_fraction >= self.metric_threshold - self.metric_tolerance + ) + or self.outer_failure + is not (self.outer_fraction < self.metric_threshold - self.metric_tolerance) + or self.gap_unknown + is not (not self.inner_success and not self.outer_failure) + or type(self.proof_rows) is not tuple + or not self.proof_rows + or any(type(value) is not str or not value for value in self.proof_rows) + or self.proof_rows != tuple(sorted(set(self.proof_rows))) + ): + raise ValueError( + "fixed cardinal visibility bounds are not a closed exact record" + ) + + +@dataclass(frozen=True, slots=True) +class FixedCardinalVisibilityOutcomeV3: + kind: CardinalKernelKindV3 + bounds: FixedCardinalVisibilityBoundsV3 | None = None + atomic_steps_used: int = 0 + proof_rows: tuple[str, ...] = () + finding_codes: tuple[str, ...] = () + + def __post_init__(self) -> None: + if type(self.kind) is not CardinalKernelKindV3: + raise TypeError( + "fixed cardinal visibility outcome must use CardinalKernelKindV3" + ) + if type(self.atomic_steps_used) is not int or self.atomic_steps_used < 0: + raise ValueError( + "fixed cardinal visibility atomic usage must be non-negative" + ) + for field_name in ("proof_rows", "finding_codes"): + values = getattr(self, field_name) + if type(values) is not tuple or any( + type(value) is not str or not value.strip() for value in values + ): + raise ValueError( + f"fixed cardinal visibility {field_name} must be non-blank strings" + ) + object.__setattr__(self, field_name, tuple(sorted(set(values)))) + if self.kind is CardinalKernelKindV3.EXACT: + if ( + type(self.bounds) is not FixedCardinalVisibilityBoundsV3 + or self.finding_codes + or not self.proof_rows + or self.proof_rows != self.bounds.proof_rows + ): + raise ValueError( + "exact fixed cardinal visibility must carry its closed bounds" + ) + return + if self.bounds is not None or not self.proof_rows or not self.finding_codes: + raise ValueError("non-exact fixed cardinal visibility must carry a finding") + prefix = { + CardinalKernelKindV3.NUMERIC_GAP: "NUMERIC_GAP:", + CardinalKernelKindV3.RESOURCE_LIMIT: "RESOURCE_LIMIT:", + CardinalKernelKindV3.UNSUPPORTED: "UNSUPPORTED:", + }[self.kind] + if any(not code.startswith(prefix) for code in self.finding_codes): + raise ValueError("visibility finding must match its typed outcome") + + +@dataclass(frozen=True, slots=True) +class _ProjectedRectangleV3: + x_lower: Fraction + x_upper: Fraction + y_lower: Fraction + y_upper: Fraction + + def __post_init__(self) -> None: + if ( + any( + type(value) is not Fraction + for value in (self.x_lower, self.x_upper, self.y_lower, self.y_upper) + ) + or self.x_lower > self.x_upper + or self.y_lower > self.y_upper + ): + raise ValueError("projected rectangle must be ordered exact Fractions") + + @property + def area(self) -> Fraction: + return (self.x_upper - self.x_lower) * (self.y_upper - self.y_lower) + + +@dataclass(frozen=True, slots=True) +class _ProjectedBoxV3: + inner: _ProjectedRectangleV3 | None + outer: _ProjectedRectangleV3 + depth_lower: Fraction + depth_upper: Fraction + + +def evaluate_fixed_cardinal_visibility_v3( + *, + context: UprightCameraContextV2_9, + cell: tuple[Fraction, Fraction, Fraction, Fraction], + subject: FixedCardinalProjectionBoxV3, + moving_subject_id: str, + occluders: tuple[FixedCardinalProjectionBoxV3, ...], + required_occluder_ids: tuple[str, ...], + policy: FixedCardinalVisibilityPolicyV3, + atomic_budget: SO2AtomicBudgetV2, +) -> FixedCardinalVisibilityOutcomeV3: + """Project exact cardinal box corners via the retained camera owner, never local axes.""" + checked_cell = _require_closed_cell_v3(cell) + checked_subject = _require_projection_box_v3(subject, label="subject") + checked_occluders = _require_complete_occluder_roster_v3( + subject=checked_subject, + occluders=occluders, + required_occluder_ids=required_occluder_ids, + ) + if type(context) is not UprightCameraContextV2_9: + raise TypeError("context must be an UprightCameraContextV2_9") + if type(moving_subject_id) is not str or moving_subject_id not in { + box.box_id for box in checked_occluders + }: + raise ValueError("moving_subject_id must identify a complete-roster member") + if type(policy) is not FixedCardinalVisibilityPolicyV3: + raise TypeError("policy must be a FixedCardinalVisibilityPolicyV3") + if type(atomic_budget) is not SO2AtomicBudgetV2: + raise TypeError("atomic_budget must be an SO2AtomicBudgetV2") + atomic_budget.validate() + if atomic_budget.limit != policy.atomic_step_limit: + raise ValueError("atomic budget limit must match the caller policy") + if ( + policy.metric_definition_id, + policy.metric_definition_version, + ) != ("visibility:image-area-fraction", "definition:1"): + return FixedCardinalVisibilityOutcomeV3( + CardinalKernelKindV3.UNSUPPORTED, + atomic_steps_used=0, + proof_rows=("UNSUPPORTED:VISIBILITY_METRIC:unimplemented-or-unknown",), + finding_codes=("UNSUPPORTED:VISIBILITY_METRIC",), + ) + required_steps = 80 * (1 + len(checked_occluders)) + if atomic_budget.remaining < required_steps: + return _resource_limit_outcome_v3() + initial_used = atomic_budget.used + try: + subject_projection = _project_box_v3( + context=context, + cell=( + checked_cell + if checked_subject.box_id == moving_subject_id + else (Fraction(), Fraction(), Fraction(), Fraction()) + ), + box=checked_subject, + atomic_budget=atomic_budget, + ) + if subject_projection is None: + return _clipped_subject_outcome_v3( + context=context, + cell=checked_cell, + roster=tuple(box.box_id for box in checked_occluders), + policy=policy, + atomic_steps_used=atomic_budget.used - initial_used, + ) + occluder_projections = tuple( + _project_box_v3( + context=context, + cell=( + checked_cell + if box.box_id == moving_subject_id + else (Fraction(), Fraction(), Fraction(), Fraction()) + ), + box=box, + atomic_budget=atomic_budget, + ) + for box in checked_occluders + ) + except SO2AtomicBudgetExhaustedV2: + return _resource_limit_outcome_v3() + except _VisibilityClipGapV3 as error: + return FixedCardinalVisibilityOutcomeV3( + CardinalKernelKindV3.NUMERIC_GAP, + atomic_steps_used=atomic_budget.used - initial_used, + proof_rows=(f"NUMERIC_GAP:{error.clip}:inner-outer",), + finding_codes=(f"NUMERIC_GAP:{error.clip}",), + ) + except ArithmeticError: + return FixedCardinalVisibilityOutcomeV3( + CardinalKernelKindV3.NUMERIC_GAP, + atomic_steps_used=atomic_budget.used - initial_used, + proof_rows=("NUMERIC_GAP:CAMERA_BOUND:directed-owner",), + finding_codes=("NUMERIC_GAP:CAMERA_BOUND",), + ) + if subject_projection.outer.area == 0: + return FixedCardinalVisibilityOutcomeV3( + CardinalKernelKindV3.UNSUPPORTED, + atomic_steps_used=atomic_budget.used - initial_used, + proof_rows=("UNSUPPORTED:DEGENERATE_PROJECTED_SUBJECT:zero-area",), + finding_codes=("UNSUPPORTED:DEGENERATE_PROJECTED_SUBJECT",), + ) + inner_fraction, outer_fraction = _visibility_fraction_bounds_v3( + subject=subject_projection, + subject_id=checked_subject.box_id, + occluder_ids=tuple(box.box_id for box in checked_occluders), + occluders=occluder_projections, + ) + proof_rows = tuple( + sorted( + ( + f"CAMERA:{context.context_sha256}", + "COMMON_CELL", + "CLIP:NEAR_FAR:RETAINED_CONTEXT", + f"OCCLUDERS:COMPLETE:{','.join(box.box_id for box in checked_occluders)}", + "PROJECTION:RETAINED_UPRIGHT_CAMERA_CONTEXT_V2_9", + "SUBJECT_AS_OCCLUDER", + f"METRIC:{policy.metric_definition_id}:{policy.metric_definition_version}", + ( + "VISIBILITY:PROJECTED_AREA_FRACTION:" + f"{policy.metric_boundary}_{policy.metric_comparator}:" + f"{policy.metric_threshold.numerator}/{policy.metric_threshold.denominator}" + ), + ) + ) + ) + bounds = FixedCardinalVisibilityBoundsV3( + cell=checked_cell, + camera_context_sha256=context.context_sha256, + projection_convention="RETAINED_UPRIGHT_CAMERA_CONTEXT_V2_9", + occluder_roster=tuple(box.box_id for box in checked_occluders), + subject_as_occluder=True, + inner_fraction=inner_fraction, + outer_fraction=outer_fraction, + metric_definition_id=policy.metric_definition_id, + metric_definition_version=policy.metric_definition_version, + metric_threshold=policy.metric_threshold, + metric_tolerance=policy.metric_tolerance, + metric_comparator=policy.metric_comparator, + metric_boundary=policy.metric_boundary, + inner_success=inner_fraction + >= policy.metric_threshold - policy.metric_tolerance, + outer_failure=outer_fraction + < policy.metric_threshold - policy.metric_tolerance, + gap_unknown=( + inner_fraction + < policy.metric_threshold - policy.metric_tolerance + <= outer_fraction + ), + proof_rows=proof_rows, + ) + return FixedCardinalVisibilityOutcomeV3( + CardinalKernelKindV3.EXACT, + bounds, + atomic_steps_used=atomic_budget.used - initial_used, + proof_rows=proof_rows, + ) + + +class _VisibilityClipGapV3(ArithmeticError): + def __init__(self, clip: str) -> None: + self.clip = clip + super().__init__(clip) + + +def _require_closed_cell_v3( + value: object, +) -> tuple[Fraction, Fraction, Fraction, Fraction]: + if ( + type(value) is not tuple + or len(value) != 4 + or any(type(endpoint) is not Fraction for endpoint in value) + ): + raise TypeError("cell must be an exact four-Fraction closed XY cell") + x_lower, x_upper, y_lower, y_upper = value + if x_lower > x_upper or y_lower > y_upper: + raise ValueError("cell must have ordered closed XY bounds") + return value + + +def _require_projection_box_v3( + value: object, *, label: str +) -> FixedCardinalProjectionBoxV3: + if type(value) is not FixedCardinalProjectionBoxV3: + raise TypeError(f"{label} must be a FixedCardinalProjectionBoxV3") + return value + + +def _require_complete_occluder_roster_v3( + *, + subject: FixedCardinalProjectionBoxV3, + occluders: object, + required_occluder_ids: object, +) -> tuple[FixedCardinalProjectionBoxV3, ...]: + if type(occluders) is not tuple: + raise TypeError("occluders must be an exact tuple") + checked = tuple( + _require_projection_box_v3(box, label="occluder") for box in occluders + ) + roster_ids = tuple(box.box_id for box in checked) + if roster_ids != tuple(sorted(set(roster_ids))): + raise ValueError("complete occluder roster must be sorted with unique IDs") + if ( + type(required_occluder_ids) is not tuple + or any( + type(box_id) is not str or not box_id for box_id in required_occluder_ids + ) + or tuple(required_occluder_ids) != tuple(sorted(set(required_occluder_ids))) + or tuple(required_occluder_ids) != roster_ids + ): + raise ValueError("complete occluder roster must match required ordered IDs") + if subject.box_id not in roster_ids: + raise ValueError("complete occluder roster must include the subject") + roster_subject = checked[roster_ids.index(subject.box_id)] + if roster_subject != subject: + raise ValueError("subject-as-occluder must bind the exact subject geometry") + return checked + + +def _resource_limit_outcome_v3() -> FixedCardinalVisibilityOutcomeV3: + return FixedCardinalVisibilityOutcomeV3( + CardinalKernelKindV3.RESOURCE_LIMIT, + atomic_steps_used=0, + proof_rows=("RESOURCE:SO2_ATOMIC_STEPS:cap-minus-one",), + finding_codes=("RESOURCE_LIMIT:SO2_ATOMIC_STEPS",), + ) + + +def _clamp_screen_interval_v3( + value: tuple[Fraction, Fraction], *, limit: int +) -> tuple[Fraction, Fraction]: + lower = min(Fraction(limit), max(Fraction(), value[0])) + upper = min(Fraction(limit), max(Fraction(), value[1])) + if lower > upper: + raise RuntimeError("screen clipping reversed a projection interval") + return lower, upper + + +def _divide_positive_interval_v3( + numerator: tuple[Fraction, Fraction], denominator: tuple[Fraction, Fraction] +) -> tuple[Fraction, Fraction]: + if denominator[0] <= 0: + raise _VisibilityClipGapV3("NEAR_CLIP") + quotients = tuple( + numerator_value / denominator_value + for numerator_value in numerator + for denominator_value in denominator + ) + return min(quotients), max(quotients) + + +def _project_coordinate_v3( + *, + focal: Fraction, + principal: Fraction, + camera_axis: tuple[Fraction, Fraction], + depth: tuple[Fraction, Fraction], + screen_limit: int, +) -> tuple[Fraction, Fraction]: + scaled_axis = tuple(focal * value for value in camera_axis) + ratio = _divide_positive_interval_v3(scaled_axis, depth) + return _clamp_screen_interval_v3( + (ratio[0] + principal, ratio[1] + principal), limit=screen_limit + ) + + +def _inner_rectangle_v3( + horizontal: tuple[tuple[Fraction, Fraction], ...], + vertical: tuple[tuple[Fraction, Fraction], ...], +) -> _ProjectedRectangleV3 | None: + # The projected bounding-box minimum is no greater than every upper + # endpoint, while its maximum is no less than every lower endpoint. + # These reversed extrema therefore form an inward rectangle for the + # bounding-box metric (not a claim about the filled object silhouette). + x_lower = min(value[1] for value in horizontal) + x_upper = max(value[0] for value in horizontal) + y_lower = min(value[1] for value in vertical) + y_upper = max(value[0] for value in vertical) + if x_lower >= x_upper or y_lower >= y_upper: + return None + return _ProjectedRectangleV3(x_lower, x_upper, y_lower, y_upper) + + +def _outer_rectangle_v3( + horizontal: tuple[tuple[Fraction, Fraction], ...], + vertical: tuple[tuple[Fraction, Fraction], ...], +) -> _ProjectedRectangleV3: + return _ProjectedRectangleV3( + min(value[0] for value in horizontal), + max(value[1] for value in horizontal), + min(value[0] for value in vertical), + max(value[1] for value in vertical), + ) + + +def _project_box_v3( + *, + context: UprightCameraContextV2_9, + cell: tuple[Fraction, Fraction, Fraction, Fraction], + box: FixedCardinalProjectionBoxV3, + atomic_budget: SO2AtomicBudgetV2, +) -> _ProjectedBoxV3 | None: + """Use only the retained camera owner to bound every closed-cell box corner.""" + + points = tuple( + bound_world_point_in_upright_camera( + context, + world_xyz=(x, y, z), + delta_x=(cell[0], cell[1]), + delta_y=(cell[2], cell[3]), + atomic_budget=atomic_budget, + ) + for x in (box.center_x - box.half_x, box.center_x + box.half_x) + for y in (box.center_y - box.half_y, box.center_y + box.half_y) + for z in (box.center_z - box.half_z, box.center_z + box.half_z) + ) + depth_lower = min(point.z_camera[0] for point in points) + depth_upper = max(point.z_camera[1] for point in points) + if depth_upper <= context.near_clip_m or depth_lower >= context.far_clip_m: + return None + if depth_lower <= context.near_clip_m < depth_upper: + raise _VisibilityClipGapV3("NEAR_CLIP") + if depth_lower < context.far_clip_m <= depth_upper: + raise _VisibilityClipGapV3("FAR_CLIP") + fx, _, cx, _, fy, cy, *_ = context.intrinsics + horizontal = tuple( + _project_coordinate_v3( + focal=fx, + principal=cx, + camera_axis=point.x_camera, + depth=point.z_camera, + screen_limit=context.width_px, + ) + for point in points + ) + vertical = tuple( + _project_coordinate_v3( + focal=fy, + principal=cy, + camera_axis=point.y_camera, + depth=point.z_camera, + screen_limit=context.height_px, + ) + for point in points + ) + return _ProjectedBoxV3( + inner=_inner_rectangle_v3(horizontal, vertical), + outer=_outer_rectangle_v3(horizontal, vertical), + depth_lower=depth_lower, + depth_upper=depth_upper, + ) + + +def _intersection_area_v3( + left: _ProjectedRectangleV3 | None, right: _ProjectedRectangleV3 | None +) -> Fraction: + if left is None or right is None: + return Fraction() + width = min(left.x_upper, right.x_upper) - max(left.x_lower, right.x_lower) + height = min(left.y_upper, right.y_upper) - max(left.y_lower, right.y_lower) + return max(Fraction(), width) * max(Fraction(), height) + + +def _visibility_fraction_bounds_v3( + *, + subject: _ProjectedBoxV3, + subject_id: str, + occluder_ids: tuple[str, ...], + occluders: tuple[_ProjectedBoxV3 | None, ...], +) -> tuple[Fraction, Fraction]: + subject_inner_area = Fraction() if subject.inner is None else subject.inner.area + subject_outer_area = subject.outer.area + possible_cover = Fraction() + certain_cover = Fraction() + for occluder_id, occluder in zip(occluder_ids, occluders, strict=True): + if occluder_id == subject_id or occluder is None: + continue + if occluder.depth_lower <= subject.depth_upper: + possible_cover += _intersection_area_v3(subject.inner, occluder.outer) + if occluder.depth_upper <= subject.depth_lower: + certain_cover = max( + certain_cover, _intersection_area_v3(subject.outer, occluder.inner) + ) + inner_visible = max(Fraction(), subject_inner_area - possible_cover) + inner_fraction = inner_visible / subject_outer_area + if subject_inner_area == 0: + return inner_fraction, Fraction(1) + outer_visible = max(Fraction(), subject_outer_area - certain_cover) + outer_fraction = min(Fraction(1), outer_visible / subject_inner_area) + if inner_fraction > outer_fraction: + raise RuntimeError("visibility fraction enclosure is reversed") + return inner_fraction, outer_fraction + + +def _clipped_subject_outcome_v3( + *, + context: UprightCameraContextV2_9, + cell: tuple[Fraction, Fraction, Fraction, Fraction], + roster: tuple[str, ...], + policy: FixedCardinalVisibilityPolicyV3, + atomic_steps_used: int, +) -> FixedCardinalVisibilityOutcomeV3: + proof_rows = tuple( + sorted( + ( + f"CAMERA:{context.context_sha256}", + "CLIP:OUTSIDE_NEAR_FAR", + "COMMON_CELL", + f"OCCLUDERS:COMPLETE:{','.join(roster)}", + "PROJECTION:RETAINED_UPRIGHT_CAMERA_CONTEXT_V2_9", + "SUBJECT_AS_OCCLUDER", + f"METRIC:{policy.metric_definition_id}:{policy.metric_definition_version}", + ( + "VISIBILITY:PROJECTED_AREA_FRACTION:" + f"{policy.metric_boundary}_{policy.metric_comparator}:" + f"{policy.metric_threshold.numerator}/{policy.metric_threshold.denominator}" + ), + ) + ) + ) + bounds = FixedCardinalVisibilityBoundsV3( + cell=cell, + camera_context_sha256=context.context_sha256, + projection_convention="RETAINED_UPRIGHT_CAMERA_CONTEXT_V2_9", + occluder_roster=roster, + subject_as_occluder=True, + inner_fraction=Fraction(), + outer_fraction=Fraction(), + metric_definition_id=policy.metric_definition_id, + metric_definition_version=policy.metric_definition_version, + metric_threshold=policy.metric_threshold, + metric_tolerance=policy.metric_tolerance, + metric_comparator=policy.metric_comparator, + metric_boundary=policy.metric_boundary, + inner_success=Fraction() >= policy.metric_threshold - policy.metric_tolerance, + outer_failure=Fraction() < policy.metric_threshold - policy.metric_tolerance, + gap_unknown=False, + proof_rows=proof_rows, + ) + return FixedCardinalVisibilityOutcomeV3( + CardinalKernelKindV3.EXACT, + bounds, + atomic_steps_used=atomic_steps_used, + proof_rows=proof_rows, + ) + + def projected_bounding_box_area_fraction_lower_bound_v2_9( *, projected_u: tuple[_Interval, ...], @@ -40,6 +720,530 @@ def projected_bounding_box_area_fraction_lower_bound_v2_9( return width_lower * height_lower / Fraction(image_width_px * image_height_px) +class ContinuousYawVisibilityClassificationV4(StrEnum): + """The only whole-pose-cell visibility dispositions this owner can prove.""" + + INWARD = "INWARD" + OUTWARD = "OUTWARD" + UNKNOWN = "UNKNOWN" + + +@dataclass(frozen=True, slots=True) +class ContinuousYawVisibilityBoundsV4: + """Camera-owner bounds for a full closed XY-times-lifted-yaw pose cell.""" + + cell: tuple[Fraction, Fraction, Fraction, Fraction] + lifted_turn_bounds: tuple[Fraction, Fraction] + continuous_yaw_lift_sha256: str + camera_context_sha256: str + projection_convention: str + subject_box_id: str + occluder_roster: tuple[str, ...] + subject_as_occluder: bool + inner_fraction: Fraction + outer_fraction: Fraction + metric_definition_id: str + metric_definition_version: str + metric_threshold: Fraction + metric_tolerance: Fraction + metric_comparator: str + metric_boundary: str + classification: ContinuousYawVisibilityClassificationV4 + outer_subject_rectangle: tuple[Fraction, Fraction, Fraction, Fraction] | None + subject_depth_bounds: tuple[Fraction, Fraction] | None + proof_rows: tuple[str, ...] + + def __post_init__(self) -> None: + if ( + type(self.cell) is not tuple + or len(self.cell) != 4 + or any(type(value) is not Fraction for value in self.cell) + or self.cell[0] > self.cell[1] + or self.cell[2] > self.cell[3] + or type(self.lifted_turn_bounds) is not tuple + or len(self.lifted_turn_bounds) != 2 + or any(type(value) is not Fraction for value in self.lifted_turn_bounds) + or self.lifted_turn_bounds[0] > self.lifted_turn_bounds[1] + or type(self.continuous_yaw_lift_sha256) is not str + or len(self.continuous_yaw_lift_sha256) != 64 + or type(self.camera_context_sha256) is not str + or not self.camera_context_sha256 + or self.projection_convention != "RETAINED_UPRIGHT_CAMERA_CONTEXT_V2_9" + or type(self.subject_box_id) is not str + or not self.subject_box_id + or type(self.occluder_roster) is not tuple + or not self.occluder_roster + or self.occluder_roster != tuple(sorted(set(self.occluder_roster))) + or self.subject_box_id not in self.occluder_roster + or type(self.subject_as_occluder) is not bool + or not self.subject_as_occluder + or any( + type(value) is not Fraction + for value in ( + self.inner_fraction, + self.outer_fraction, + self.metric_threshold, + self.metric_tolerance, + ) + ) + or self.inner_fraction < 0 + or self.outer_fraction > 1 + or self.inner_fraction > self.outer_fraction + or type(self.metric_definition_id) is not str + or not self.metric_definition_id + or type(self.metric_definition_version) is not str + or not self.metric_definition_version + or self.metric_tolerance < 0 + or self.metric_comparator != "GEQ" + or self.metric_boundary != "CLOSED" + or type(self.classification) is not ContinuousYawVisibilityClassificationV4 + or type(self.proof_rows) is not tuple + or not self.proof_rows + or any(type(value) is not str or not value for value in self.proof_rows) + or self.proof_rows != tuple(sorted(set(self.proof_rows))) + ): + raise ValueError( + "continuous visibility bounds are not a closed exact record" + ) + int(self.continuous_yaw_lift_sha256, 16) + expected_classification = _continuous_visibility_classification_v4( + self.inner_fraction, + self.outer_fraction, + self.metric_threshold, + self.metric_tolerance, + ) + if self.classification is not expected_classification: + raise ValueError( + "continuous visibility classification does not match bounds" + ) + for value in (self.outer_subject_rectangle, self.subject_depth_bounds): + if value is None: + continue + if type(value) is not tuple or any( + type(item) is not Fraction for item in value + ): + raise TypeError( + "continuous projection endpoints must be exact Fractions" + ) + if self.outer_subject_rectangle is not None and ( + len(self.outer_subject_rectangle) != 4 + or self.outer_subject_rectangle[0] > self.outer_subject_rectangle[1] + or self.outer_subject_rectangle[2] > self.outer_subject_rectangle[3] + ): + raise ValueError("continuous outer subject rectangle must be ordered") + if self.subject_depth_bounds is not None and ( + len(self.subject_depth_bounds) != 2 + or self.subject_depth_bounds[0] > self.subject_depth_bounds[1] + ): + raise ValueError("continuous subject depth bounds must be ordered") + + +@dataclass(frozen=True, slots=True) +class ContinuousYawVisibilityOutcomeV4: + """Typed completion of one continuous pose-cell camera evaluation.""" + + kind: so2_interval.ContinuousYawIntervalKindV4 + bounds: ContinuousYawVisibilityBoundsV4 | None = None + atomic_steps_used: int = 0 + proof_rows: tuple[str, ...] = () + finding_codes: tuple[str, ...] = () + + def __post_init__(self) -> None: + if type(self.kind) is not so2_interval.ContinuousYawIntervalKindV4: + raise TypeError("kind must be a ContinuousYawIntervalKindV4") + if type(self.atomic_steps_used) is not int or self.atomic_steps_used < 0: + raise ValueError("continuous visibility atomic usage must be non-negative") + for field_name in ("proof_rows", "finding_codes"): + values = getattr(self, field_name) + if type(values) is not tuple or any( + type(value) is not str or not value.strip() for value in values + ): + raise ValueError( + f"continuous visibility {field_name} must be non-blank strings" + ) + object.__setattr__(self, field_name, tuple(sorted(set(values)))) + if self.kind is so2_interval.ContinuousYawIntervalKindV4.EXACT: + if ( + type(self.bounds) is not ContinuousYawVisibilityBoundsV4 + or self.finding_codes + or not self.proof_rows + or self.proof_rows != self.bounds.proof_rows + ): + raise ValueError("exact continuous visibility must carry closed bounds") + return + if self.bounds is not None or not self.proof_rows or not self.finding_codes: + raise ValueError("non-exact continuous visibility must carry a finding") + expected_prefix = { + so2_interval.ContinuousYawIntervalKindV4.NUMERIC_GAP: "NUMERIC_GAP:", + so2_interval.ContinuousYawIntervalKindV4.RESOURCE_LIMIT: "RESOURCE_LIMIT:", + so2_interval.ContinuousYawIntervalKindV4.UNSUPPORTED: "UNSUPPORTED:", + }[self.kind] + if any(not code.startswith(expected_prefix) for code in self.finding_codes): + raise ValueError("continuous visibility finding must match outcome kind") + + +def evaluate_continuous_yaw_visibility_v4( + *, + context: UprightCameraContextV2_9, + subject: upright_box_interval.ContinuousYawBoxBoundsV4, + moving_subject_id: str, + occluders: tuple[upright_box_interval.ContinuousYawBoxBoundsV4, ...], + required_occluder_ids: tuple[str, ...], + policy: FixedCardinalVisibilityPolicyV3, + atomic_budget: SO2AtomicBudgetV2, +) -> ContinuousYawVisibilityOutcomeV4: + """Project a complete continuous pose cell through the retained camera owner. + + A cardinal point cell delegates byte-for-byte geometry evaluation to the + reviewed v3 entry point. A nondegenerate yaw or translation cell projects + the full outer envelope; it can establish an outside result, but all + visible/occlusion ambiguity remains explicitly ``UNKNOWN``. + """ + + if type(context) is not UprightCameraContextV2_9: + raise TypeError("context must be an UprightCameraContextV2_9") + if type(subject) is not upright_box_interval.ContinuousYawBoxBoundsV4: + raise TypeError("subject must be a ContinuousYawBoxBoundsV4") + checked_occluders = _require_complete_continuous_occluder_roster_v4( + subject=subject, + occluders=occluders, + required_occluder_ids=required_occluder_ids, + ) + if type(moving_subject_id) is not str or moving_subject_id not in { + value.box.box_id for value in checked_occluders + }: + raise ValueError("moving_subject_id must identify a complete-roster member") + if type(policy) is not FixedCardinalVisibilityPolicyV3: + raise TypeError("policy must be a FixedCardinalVisibilityPolicyV3") + if type(atomic_budget) is not SO2AtomicBudgetV2: + raise TypeError("atomic_budget must be an SO2AtomicBudgetV2") + atomic_budget.validate() + if atomic_budget.limit != policy.atomic_step_limit: + raise ValueError("atomic budget limit must match the caller policy") + if ( + policy.metric_definition_id, + policy.metric_definition_version, + ) != ("visibility:image-area-fraction", "definition:1"): + return _continuous_visibility_failure_v4( + so2_interval.ContinuousYawIntervalKindV4.UNSUPPORTED, + "UNSUPPORTED:VISIBILITY_METRIC", + atomic_steps_used=0, + proof_rows=("UNSUPPORTED:VISIBILITY_METRIC:unimplemented-or-unknown",), + ) + if _all_continuous_boxes_are_cardinal_points_v4((subject, *checked_occluders)): + return _delegate_cardinal_point_visibility_v4( + context=context, + subject=subject, + moving_subject_id=moving_subject_id, + occluders=checked_occluders, + required_occluder_ids=required_occluder_ids, + policy=policy, + atomic_budget=atomic_budget, + ) + required_steps = 80 * (1 + len(checked_occluders)) + if atomic_budget.remaining < required_steps: + return _continuous_visibility_failure_v4( + so2_interval.ContinuousYawIntervalKindV4.RESOURCE_LIMIT, + "RESOURCE_LIMIT:SO2_ATOMIC_STEPS", + atomic_steps_used=0, + proof_rows=("RESOURCE:SO2_ATOMIC_STEPS:cap-minus-one",), + ) + initial_used = atomic_budget.used + try: + subject_projection = _project_box_v3( + context=context, + cell=(Fraction(), Fraction(), Fraction(), Fraction()), + box=_continuous_outer_projection_box_v4(subject), + atomic_budget=atomic_budget, + ) + occluder_projections = tuple( + _project_box_v3( + context=context, + cell=(Fraction(), Fraction(), Fraction(), Fraction()), + box=_continuous_outer_projection_box_v4(value), + atomic_budget=atomic_budget, + ) + for value in checked_occluders + ) + except SO2AtomicBudgetExhaustedV2: + return _continuous_visibility_failure_v4( + so2_interval.ContinuousYawIntervalKindV4.RESOURCE_LIMIT, + "RESOURCE_LIMIT:SO2_ATOMIC_STEPS", + atomic_steps_used=atomic_budget.used - initial_used, + proof_rows=("RESOURCE:SO2_ATOMIC_STEPS:continuous-outer-envelope",), + ) + except _VisibilityClipGapV3 as error: + return _continuous_visibility_failure_v4( + so2_interval.ContinuousYawIntervalKindV4.NUMERIC_GAP, + f"NUMERIC_GAP:{error.clip}", + atomic_steps_used=atomic_budget.used - initial_used, + proof_rows=(f"NUMERIC_GAP:{error.clip}:continuous-outer-envelope",), + ) + except ArithmeticError: + return _continuous_visibility_failure_v4( + so2_interval.ContinuousYawIntervalKindV4.NUMERIC_GAP, + "NUMERIC_GAP:CAMERA_BOUND", + atomic_steps_used=atomic_budget.used - initial_used, + proof_rows=("NUMERIC_GAP:CAMERA_BOUND:directed-owner",), + ) + if subject_projection is not None and subject_projection.outer.area == 0: + return _continuous_visibility_failure_v4( + so2_interval.ContinuousYawIntervalKindV4.UNSUPPORTED, + "UNSUPPORTED:DEGENERATE_PROJECTED_SUBJECT", + atomic_steps_used=atomic_budget.used - initial_used, + proof_rows=("UNSUPPORTED:DEGENERATE_PROJECTED_SUBJECT:zero-area",), + ) + if subject_projection is None: + inner_fraction = Fraction() + outer_fraction = Fraction() + rectangle = None + depth = None + classification_row = "CLASSIFICATION:OUTER_CAMERA_CLIPPED" + else: + # The envelope can contain every pose, but does not provide a common + # visible silhouette or a complete occlusion proof for a nonpoint cell. + inner_fraction = Fraction() + outer_fraction = Fraction(1) + rectangle = ( + subject_projection.outer.x_lower, + subject_projection.outer.x_upper, + subject_projection.outer.y_lower, + subject_projection.outer.y_upper, + ) + depth = (subject_projection.depth_lower, subject_projection.depth_upper) + classification_row = "CLASSIFICATION:NONDEGENERATE_OUTER_ENVELOPE_UNKNOWN" + proof_rows = tuple( + sorted( + ( + f"CAMERA:{context.context_sha256}", + "CONTINUOUS_POSE_CELL:OUTER_ENVELOPE", + f"OCCLUDERS:COMPLETE:{','.join(value.box.box_id for value in checked_occluders)}", + "PROJECTION:RETAINED_UPRIGHT_CAMERA_CONTEXT_V2_9", + "SUBJECT_AS_OCCLUDER", + f"METRIC:{policy.metric_definition_id}:{policy.metric_definition_version}", + classification_row, + f"PROJECTED_OBJECTS:{1 + len(occluder_projections)}", + ) + ) + ) + bounds = _continuous_visibility_bounds_v4( + context=context, + subject=subject, + occluders=checked_occluders, + policy=policy, + inner_fraction=inner_fraction, + outer_fraction=outer_fraction, + rectangle=rectangle, + depth=depth, + proof_rows=proof_rows, + ) + return ContinuousYawVisibilityOutcomeV4( + so2_interval.ContinuousYawIntervalKindV4.EXACT, + bounds=bounds, + atomic_steps_used=atomic_budget.used - initial_used, + proof_rows=proof_rows, + ) + + +def _delegate_cardinal_point_visibility_v4( + *, + context: UprightCameraContextV2_9, + subject: upright_box_interval.ContinuousYawBoxBoundsV4, + moving_subject_id: str, + occluders: tuple[upright_box_interval.ContinuousYawBoxBoundsV4, ...], + required_occluder_ids: tuple[str, ...], + policy: FixedCardinalVisibilityPolicyV3, + atomic_budget: SO2AtomicBudgetV2, +) -> ContinuousYawVisibilityOutcomeV4: + """Reuse the Task 3A point camera path exactly at cardinal pose points.""" + + retained = evaluate_fixed_cardinal_visibility_v3( + context=context, + cell=(Fraction(), Fraction(), Fraction(), Fraction()), + subject=_continuous_outer_projection_box_v4(subject), + moving_subject_id=moving_subject_id, + occluders=tuple( + _continuous_outer_projection_box_v4(value) for value in occluders + ), + required_occluder_ids=required_occluder_ids, + policy=policy, + atomic_budget=atomic_budget, + ) + kind = so2_interval.ContinuousYawIntervalKindV4(retained.kind.value) + if kind is not so2_interval.ContinuousYawIntervalKindV4.EXACT: + return ContinuousYawVisibilityOutcomeV4( + kind, + atomic_steps_used=retained.atomic_steps_used, + proof_rows=retained.proof_rows, + finding_codes=retained.finding_codes, + ) + if type(retained.bounds) is not FixedCardinalVisibilityBoundsV3: + raise RuntimeError("exact retained point visibility is missing bounds") + bounds = _continuous_visibility_bounds_v4( + context=context, + subject=subject, + occluders=occluders, + policy=policy, + inner_fraction=retained.bounds.inner_fraction, + outer_fraction=retained.bounds.outer_fraction, + rectangle=None, + depth=None, + proof_rows=retained.proof_rows, + ) + return ContinuousYawVisibilityOutcomeV4( + kind, + bounds=bounds, + atomic_steps_used=retained.atomic_steps_used, + proof_rows=retained.proof_rows, + ) + + +def _continuous_visibility_bounds_v4( + *, + context: UprightCameraContextV2_9, + subject: upright_box_interval.ContinuousYawBoxBoundsV4, + occluders: tuple[upright_box_interval.ContinuousYawBoxBoundsV4, ...], + policy: FixedCardinalVisibilityPolicyV3, + inner_fraction: Fraction, + outer_fraction: Fraction, + rectangle: tuple[Fraction, Fraction, Fraction, Fraction] | None, + depth: tuple[Fraction, Fraction] | None, + proof_rows: tuple[str, ...], +) -> ContinuousYawVisibilityBoundsV4: + lift = subject.yaw_bounds.lift_bounds + return ContinuousYawVisibilityBoundsV4( + cell=subject.cell.canonical_bounds, + lifted_turn_bounds=(lift.lower, lift.upper), + continuous_yaw_lift_sha256=lift.coverage_sha256, + camera_context_sha256=context.context_sha256, + projection_convention="RETAINED_UPRIGHT_CAMERA_CONTEXT_V2_9", + subject_box_id=subject.box.box_id, + occluder_roster=tuple(value.box.box_id for value in occluders), + subject_as_occluder=True, + inner_fraction=inner_fraction, + outer_fraction=outer_fraction, + metric_definition_id=policy.metric_definition_id, + metric_definition_version=policy.metric_definition_version, + metric_threshold=policy.metric_threshold, + metric_tolerance=policy.metric_tolerance, + metric_comparator=policy.metric_comparator, + metric_boundary=policy.metric_boundary, + classification=_continuous_visibility_classification_v4( + inner_fraction, + outer_fraction, + policy.metric_threshold, + policy.metric_tolerance, + ), + outer_subject_rectangle=rectangle, + subject_depth_bounds=depth, + proof_rows=proof_rows, + ) + + +def _continuous_visibility_classification_v4( + inner_fraction: Fraction, + outer_fraction: Fraction, + threshold: Fraction, + tolerance: Fraction, +) -> ContinuousYawVisibilityClassificationV4: + required = threshold - tolerance + if inner_fraction >= required: + return ContinuousYawVisibilityClassificationV4.INWARD + if outer_fraction < required: + return ContinuousYawVisibilityClassificationV4.OUTWARD + return ContinuousYawVisibilityClassificationV4.UNKNOWN + + +def _require_complete_continuous_occluder_roster_v4( + *, + subject: upright_box_interval.ContinuousYawBoxBoundsV4, + occluders: object, + required_occluder_ids: object, +) -> tuple[upright_box_interval.ContinuousYawBoxBoundsV4, ...]: + if type(occluders) is not tuple: + raise TypeError("occluders must be an exact tuple") + checked = tuple( + _require_continuous_box_v4(value, label="occluder") for value in occluders + ) + roster_ids = tuple(value.box.box_id for value in checked) + if roster_ids != tuple(sorted(set(roster_ids))): + raise ValueError("complete occluder roster must be sorted with unique IDs") + if ( + type(required_occluder_ids) is not tuple + or any(type(value) is not str or not value for value in required_occluder_ids) + or tuple(required_occluder_ids) != tuple(sorted(set(required_occluder_ids))) + or tuple(required_occluder_ids) != roster_ids + ): + raise ValueError("complete occluder roster must match required ordered IDs") + if subject.box.box_id not in roster_ids: + raise ValueError("complete occluder roster must include the subject") + if checked[roster_ids.index(subject.box.box_id)] != subject: + raise ValueError("subject-as-occluder must bind the exact continuous geometry") + return checked + + +def _require_continuous_box_v4( + value: object, *, label: str +) -> upright_box_interval.ContinuousYawBoxBoundsV4: + if type(value) is not upright_box_interval.ContinuousYawBoxBoundsV4: + raise TypeError(f"{label} must be a ContinuousYawBoxBoundsV4") + return value + + +def _all_continuous_boxes_are_cardinal_points_v4( + boxes: tuple[upright_box_interval.ContinuousYawBoxBoundsV4, ...], +) -> bool: + return all( + value.cell.x_lower == value.cell.x_upper + and value.cell.y_lower == value.cell.y_upper + and value.yaw_bounds.lift_bounds.lower == value.yaw_bounds.lift_bounds.upper + and so2_interval._cardinal_quarter_turn_v4(value.yaw_bounds.lift_bounds.lower) + is not None + for value in boxes + ) + + +def _continuous_outer_projection_box_v4( + value: upright_box_interval.ContinuousYawBoxBoundsV4, +) -> FixedCardinalProjectionBoxV3: + x_lower, x_upper = ( + value.aabb_x.rational_lower, + value.aabb_x.rational_upper, + ) + y_lower, y_upper = ( + value.aabb_y.rational_lower, + value.aabb_y.rational_upper, + ) + z_lower, z_upper = ( + value.aabb_z.rational_lower, + value.aabb_z.rational_upper, + ) + return FixedCardinalProjectionBoxV3( + value.box.box_id, + (x_lower + x_upper) / 2, + (y_lower + y_upper) / 2, + (z_lower + z_upper) / 2, + (x_upper - x_lower) / 2, + (y_upper - y_lower) / 2, + (z_upper - z_lower) / 2, + ) + + +def _continuous_visibility_failure_v4( + kind: so2_interval.ContinuousYawIntervalKindV4, + finding_code: str, + *, + atomic_steps_used: int, + proof_rows: tuple[str, ...], +) -> ContinuousYawVisibilityOutcomeV4: + return ContinuousYawVisibilityOutcomeV4( + kind, + atomic_steps_used=atomic_steps_used, + proof_rows=proof_rows, + finding_codes=(finding_code,), + ) + + def _require_intervals(value: object, label: str) -> None: if type(value) is not tuple or not value: raise TypeError(f"{label} must be a non-empty exact tuple") @@ -54,4 +1258,15 @@ def _require_intervals(value: object, label: str) -> None: raise ValueError(f"{label} interval endpoints are reversed") -__all__ = ("projected_bounding_box_area_fraction_lower_bound_v2_9",) +__all__ = ( + "ContinuousYawVisibilityBoundsV4", + "ContinuousYawVisibilityClassificationV4", + "ContinuousYawVisibilityOutcomeV4", + "FixedCardinalProjectionBoxV3", + "FixedCardinalVisibilityBoundsV3", + "FixedCardinalVisibilityOutcomeV3", + "FixedCardinalVisibilityPolicyV3", + "evaluate_continuous_yaw_visibility_v4", + "evaluate_fixed_cardinal_visibility_v3", + "projected_bounding_box_area_fraction_lower_bound_v2_9", +) diff --git a/src/spatialcf/core/_internal/kernels/so2.py b/src/spatialcf/core/_internal/kernels/so2.py index 948130d..8d01f2f 100644 --- a/src/spatialcf/core/_internal/kernels/so2.py +++ b/src/spatialcf/core/_internal/kernels/so2.py @@ -9,6 +9,8 @@ from fractions import Fraction from typing import Self +from spatialcf.domain import upright_se2 as upright + SO2_INTERVAL_KERNEL_ID_V2 = "geometry-kernel:rational-so2-upright-box-directed-v2" SO2_INTERVAL_KERNEL_VERSION_V2 = "kernel:2.2-continuous-yaw-upright-box" SO2_INTERVAL_KERNEL_SOUNDNESS_V2 = "DIRECTED_OUTWARD_BOUNDS" @@ -30,6 +32,149 @@ class SO2AtomicBudgetExhaustedV2(RuntimeError): """The shared directed-kernel ledger has no remaining capacity.""" +class CardinalKernelKindV3(StrEnum): + """Typed result class for additive exact-cardinal owner seams.""" + + EXACT = "EXACT" + NUMERIC_GAP = "NUMERIC_GAP" + RESOURCE_LIMIT = "RESOURCE_LIMIT" + UNSUPPORTED = "UNSUPPORTED" + + +@dataclass(frozen=True, slots=True) +class CardinalKernelOutcomeV3: + kind: CardinalKernelKindV3 + value: int | tuple[Fraction, Fraction] | None = None + atomic_steps_used: int = 0 + proof_rows: tuple[str, ...] = () + finding_codes: tuple[str, ...] = () + + def __post_init__(self) -> None: + if type(self.kind) is not CardinalKernelKindV3: + raise TypeError("cardinal kernel outcome must use CardinalKernelKindV3") + if type(self.atomic_steps_used) is not int or self.atomic_steps_used < 0: + raise ValueError("cardinal kernel atomic usage must be non-negative") + for field_name in ("proof_rows", "finding_codes"): + values = getattr(self, field_name) + if type(values) is not tuple or any( + type(value) is not str or not value.strip() for value in values + ): + raise ValueError( + f"cardinal kernel {field_name} must be non-blank strings" + ) + object.__setattr__(self, field_name, tuple(sorted(set(values)))) + if self.kind is CardinalKernelKindV3.EXACT: + if not ( + type(self.value) is int + or ( + type(self.value) is tuple + and len(self.value) == 2 + and all(type(component) is Fraction for component in self.value) + ) + ): + raise ValueError("exact cardinal outcome must carry an exact value") + if self.finding_codes or not self.proof_rows: + raise ValueError( + "exact cardinal outcome must have proof and no findings" + ) + return + if self.value is not None or not self.finding_codes or not self.proof_rows: + raise ValueError("non-exact cardinal outcome must have finding and proof") + expected_prefix = { + CardinalKernelKindV3.NUMERIC_GAP: "NUMERIC_GAP:", + CardinalKernelKindV3.RESOURCE_LIMIT: "RESOURCE_LIMIT:", + CardinalKernelKindV3.UNSUPPORTED: "UNSUPPORTED:", + }[self.kind] + if any(not code.startswith(expected_prefix) for code in self.finding_codes): + raise ValueError("cardinal outcome finding does not match its typed kind") + + +def _require_cardinal_q_v3(value: object, *, label: str) -> int: + if type(value) is not int: + raise TypeError(f"{label} must be an exact cardinal quarter turn") + if value not in (0, 1, 2, 3): + raise ValueError(f"{label} must be in 0..3") + return value + + +def _require_cardinal_budget_v3(value: object) -> SO2AtomicBudgetV2: + if type(value) is not SO2AtomicBudgetV2: + raise TypeError("atomic_budget must be an SO2AtomicBudgetV2") + value.validate() + return value + + +def _cardinal_resource_limit_outcome_v3() -> CardinalKernelOutcomeV3: + return CardinalKernelOutcomeV3( + CardinalKernelKindV3.RESOURCE_LIMIT, + atomic_steps_used=0, + proof_rows=("RESOURCE:SO2_ATOMIC_STEPS:cap-minus-one",), + finding_codes=("RESOURCE_LIMIT:SO2_ATOMIC_STEPS",), + ) + + +def compose_cardinal_quarter_turns_v3( + left_q: object, right_q: object, *, atomic_budget: SO2AtomicBudgetV2 +) -> CardinalKernelOutcomeV3: + """Compose exact CCW quarter turns on the caller-owned atomic ledger.""" + checked_budget = _require_cardinal_budget_v3(atomic_budget) + checked_left = _require_cardinal_q_v3(left_q, label="left_q") + checked_right = _require_cardinal_q_v3(right_q, label="right_q") + try: + checked_budget.consume() + except SO2AtomicBudgetExhaustedV2: + return _cardinal_resource_limit_outcome_v3() + return CardinalKernelOutcomeV3( + CardinalKernelKindV3.EXACT, + value=(checked_left + checked_right) % 4, + atomic_steps_used=1, + proof_rows=(f"CARDINAL_COMPOSE:q={checked_left}+q={checked_right}:mod-4",), + ) + + +def inverse_cardinal_quarter_turn_v3( + q: object, *, atomic_budget: SO2AtomicBudgetV2 +) -> CardinalKernelOutcomeV3: + checked_budget = _require_cardinal_budget_v3(atomic_budget) + checked_q = _require_cardinal_q_v3(q, label="q") + return _inverse_cardinal_v3(checked_q, checked_budget) + + +def _inverse_cardinal_v3( + q: int, atomic_budget: SO2AtomicBudgetV2 +) -> CardinalKernelOutcomeV3: + try: + atomic_budget.consume() + except SO2AtomicBudgetExhaustedV2: + return _cardinal_resource_limit_outcome_v3() + return CardinalKernelOutcomeV3( + CardinalKernelKindV3.EXACT, + value=(-q) % 4, + atomic_steps_used=1, + proof_rows=(f"CARDINAL_INVERSE:q={q}:mod-4",), + ) + + +def rotate_cardinal_fraction_xy_v3( + x: object, y: object, q: object, *, atomic_budget: SO2AtomicBudgetV2 +) -> CardinalKernelOutcomeV3: + """Apply one exact signed coordinate permutation; no float/trig path exists.""" + checked_budget = _require_cardinal_budget_v3(atomic_budget) + if type(x) is not Fraction or type(y) is not Fraction: + raise TypeError("cardinal XY operands must be exact Fractions") + checked_q = _require_cardinal_q_v3(q, label="q") + try: + checked_budget.consume() + except SO2AtomicBudgetExhaustedV2: + return _cardinal_resource_limit_outcome_v3() + return CardinalKernelOutcomeV3( + CardinalKernelKindV3.EXACT, + value=((x, y), (-y, x), (-x, -y), (y, -x))[checked_q], + atomic_steps_used=1, + proof_rows=(f"CARDINAL_XY:q={checked_q}:exact-signed-permutation",), + ) + + @dataclass(frozen=True, slots=True) class SO2AtomicBudgetV2: """One mutable-usage ledger shared across all SO(2)/OBB calls.""" @@ -551,6 +696,439 @@ def _require_fraction_cap(value: Fraction) -> None: raise ValueError("rational value exceeds the SO2 Fraction bit cap") +class ContinuousYawIntervalKindV4(StrEnum): + """Typed result classes for additive lifted-turn interval owner seams.""" + + EXACT = "EXACT" + NUMERIC_GAP = "NUMERIC_GAP" + RESOURCE_LIMIT = "RESOURCE_LIMIT" + UNSUPPORTED = "UNSUPPORTED" + + +@dataclass(frozen=True, slots=True) +class ContinuousYawLiftBoundsV4: + """One canonical closed lifted-turn domain plus its unique coverage root.""" + + lift: upright.ContinuousYawLift + lower: Fraction + upper: Fraction + seam_ownership: str + unrolled_branch_cut: bool + upper_is_proof_closure_alias: bool + coverage_sha256: str + atomic_steps_used: int + + def __post_init__(self) -> None: + if type(self.lift) is not upright.ContinuousYawLift: + raise TypeError("lift must be a ContinuousYawLift") + if type(self.lower) is not Fraction or type(self.upper) is not Fraction: + raise TypeError("lifted turn bounds must be exact Fractions") + _require_fraction_cap(self.lower) + _require_fraction_cap(self.upper) + if self.lower > self.upper: + raise ValueError("lifted turn bounds must be ordered") + interval = self.lift.intervals[0] + if (self.lower, self.upper, self.seam_ownership) != ( + interval.lower.as_fraction, + interval.upper.as_fraction, + interval.seam_ownership, + ): + raise ValueError("lift bounds must reproduce the sealed domain lift") + if type(self.unrolled_branch_cut) is not bool: + raise TypeError("unrolled_branch_cut must be bool") + if type(self.upper_is_proof_closure_alias) is not bool: + raise TypeError("upper_is_proof_closure_alias must be bool") + if type(self.coverage_sha256) is not str or len(self.coverage_sha256) != 64: + raise ValueError("coverage_sha256 must be a SHA-256 digest") + int(self.coverage_sha256, 16) + if self.coverage_sha256 != self.lift.continuous_yaw_lift_sha256: + raise ValueError("coverage digest must be the sealed lift digest") + is_full_circle = type(self.lift.yaw_domain) is upright.ContinuousYawFullCircle + if is_full_circle: + if ( + (self.lower, self.upper, self.seam_ownership) + != (Fraction(-1, 2), Fraction(1, 2), "LOWER_OWNS_SEAM") + or self.unrolled_branch_cut + or not self.upper_is_proof_closure_alias + ): + raise ValueError("full circle must have one lower-owned seam alias") + elif self.upper_is_proof_closure_alias: + raise ValueError("only the full circle may alias its upper endpoint") + if type(self.atomic_steps_used) is not int or self.atomic_steps_used <= 0: + raise ValueError("lift atomic usage must be a positive exact int") + + +@dataclass(frozen=True, slots=True) +class ContinuousYawLiftOutcomeV4: + """Typed completion of canonical turn-domain lifting.""" + + kind: ContinuousYawIntervalKindV4 + bounds: ContinuousYawLiftBoundsV4 | None = None + finding_codes: tuple[str, ...] = () + + def __post_init__(self) -> None: + if type(self.kind) is not ContinuousYawIntervalKindV4: + raise TypeError("kind must be a ContinuousYawIntervalKindV4") + if type(self.finding_codes) is not tuple or any( + type(code) is not str or not code.strip() for code in self.finding_codes + ): + raise ValueError("finding_codes must be non-blank strings") + object.__setattr__( + self, "finding_codes", tuple(sorted(set(self.finding_codes))) + ) + if self.kind is ContinuousYawIntervalKindV4.EXACT: + if type(self.bounds) is not ContinuousYawLiftBoundsV4 or self.finding_codes: + raise ValueError("exact lift outcome requires bounds and no findings") + return + if self.bounds is not None or not self.finding_codes: + raise ValueError("non-exact lift outcome requires findings and no bounds") + expected_prefix = { + ContinuousYawIntervalKindV4.NUMERIC_GAP: "NUMERIC_GAP:", + ContinuousYawIntervalKindV4.RESOURCE_LIMIT: "RESOURCE_LIMIT:", + ContinuousYawIntervalKindV4.UNSUPPORTED: "UNSUPPORTED:", + }[self.kind] + if any(not code.startswith(expected_prefix) for code in self.finding_codes): + raise ValueError("lift finding does not match its typed outcome") + + +def _require_continuous_yaw_domain_v4( + value: object, +) -> upright.ContinuousYawArc | upright.ContinuousYawFullCircle: + if type(value) not in (upright.ContinuousYawArc, upright.ContinuousYawFullCircle): + raise TypeError("yaw_domain must be a closed ContinuousYawDomain wire") + return value + + +def _exact_dyadic_v4(value: Fraction) -> upright.ExactDyadic: + _require_numeric_fraction_cap(value, "NUMERIC_GAP:LIFTED_TURN_FRACTION_BIT_CAP") + denominator = value.denominator + if denominator & (denominator - 1): + raise _SO2NumericGapV2("NUMERIC_GAP:LIFTED_TURN_NOT_DYADIC") + return upright.ExactDyadic(numerator=value.numerator, denominator=denominator) + + +def compile_continuous_yaw_lift_v4( + yaw_domain: object, + *, + atomic_budget: SO2AtomicBudgetV2, +) -> ContinuousYawLiftOutcomeV4: + """Lift one closed M3 yaw wire without splitting or rounded turn addition.""" + + domain = _require_continuous_yaw_domain_v4(yaw_domain) + checked_budget = _require_cardinal_budget_v3(atomic_budget) + required_steps = 2 + if checked_budget.remaining < required_steps: + return ContinuousYawLiftOutcomeV4( + ContinuousYawIntervalKindV4.RESOURCE_LIMIT, + finding_codes=("RESOURCE_LIMIT:SO2_ATOMIC_STEPS",), + ) + start_used = checked_budget.used + try: + if type(domain) is upright.ContinuousYawFullCircle: + lower = Fraction(-1, 2) + upper = Fraction(1, 2) + seam_ownership = "LOWER_OWNS_SEAM" + unrolled_branch_cut = False + upper_is_proof_closure_alias = True + else: + lower = Fraction.from_float(domain.start_angle.turns) + sweep = Fraction.from_float(domain.ccw_sweep_turns) + upper = lower + sweep + _require_numeric_fraction_cap( + upper, + "NUMERIC_GAP:LIFTED_TURN_FRACTION_BIT_CAP", + ) + seam_ownership = ( + "UPPER_OWNS_ENDPOINT" if upper == Fraction(1, 2) else "NONE" + ) + unrolled_branch_cut = upper > Fraction(1, 2) + upper_is_proof_closure_alias = False + checked_budget.consume(required_steps) + lift = upright.ContinuousYawLift.seal( + yaw_domain=domain, + lift_origin=_exact_dyadic_v4(Fraction(-1, 2)), + intervals=( + upright.LiftedYawInterval( + lower=_exact_dyadic_v4(lower), + upper=_exact_dyadic_v4(upper), + seam_ownership=seam_ownership, + ), + ), + ) + return ContinuousYawLiftOutcomeV4( + ContinuousYawIntervalKindV4.EXACT, + bounds=ContinuousYawLiftBoundsV4( + lift=lift, + lower=lower, + upper=upper, + seam_ownership=seam_ownership, + unrolled_branch_cut=unrolled_branch_cut, + upper_is_proof_closure_alias=upper_is_proof_closure_alias, + coverage_sha256=lift.continuous_yaw_lift_sha256, + atomic_steps_used=checked_budget.used - start_used, + ), + ) + except SO2AtomicBudgetExhaustedV2: + raise RuntimeError( + "preflighted lift budget was unexpectedly exhausted" + ) from None + except _SO2NumericGapV2 as error: + return ContinuousYawLiftOutcomeV4( + ContinuousYawIntervalKindV4.NUMERIC_GAP, + finding_codes=(error.finding_code,), + ) + + +@dataclass(frozen=True, slots=True) +class ContinuousYawSinCosBoundsV4: + """Outward trigonometric extrema over one sealed lifted-turn interval.""" + + lift_bounds: ContinuousYawLiftBoundsV4 + sine: RationalEnclosureV2 + cosine: RationalEnclosureV2 + critical_turns: tuple[Fraction, ...] + atomic_steps_used: int + + def __post_init__(self) -> None: + if type(self.lift_bounds) is not ContinuousYawLiftBoundsV4: + raise TypeError("lift_bounds must be a ContinuousYawLiftBoundsV4") + for field_name in ("sine", "cosine"): + value = getattr(self, field_name) + if type(value) is not RationalEnclosureV2: + raise TypeError(f"{field_name} must be a RationalEnclosureV2") + if not (Fraction(-1) <= value.rational_lower <= value.rational_upper <= 1): + raise ValueError( + f"{field_name} must lie in the trigonometric unit interval" + ) + if type(self.critical_turns) is not tuple or any( + type(turn) is not Fraction for turn in self.critical_turns + ): + raise TypeError("critical_turns must be exact Fractions") + if self.critical_turns != tuple(sorted(set(self.critical_turns))): + raise ValueError("critical_turns must be sorted and unique") + if any( + turn < self.lift_bounds.lower or turn > self.lift_bounds.upper + for turn in self.critical_turns + ): + raise ValueError("critical turns must lie in the sealed lifted interval") + if type(self.atomic_steps_used) is not int or self.atomic_steps_used <= 0: + raise ValueError("trigonometric atomic usage must be a positive exact int") + + +@dataclass(frozen=True, slots=True) +class ContinuousYawSinCosOutcomeV4: + """Typed completion of a whole-cell directed trigonometric enclosure.""" + + kind: ContinuousYawIntervalKindV4 + bounds: ContinuousYawSinCosBoundsV4 | None = None + finding_codes: tuple[str, ...] = () + + def __post_init__(self) -> None: + if type(self.kind) is not ContinuousYawIntervalKindV4: + raise TypeError("kind must be a ContinuousYawIntervalKindV4") + if type(self.finding_codes) is not tuple or any( + type(code) is not str or not code.strip() for code in self.finding_codes + ): + raise ValueError("finding_codes must be non-blank strings") + object.__setattr__( + self, "finding_codes", tuple(sorted(set(self.finding_codes))) + ) + if self.kind is ContinuousYawIntervalKindV4.EXACT: + if ( + type(self.bounds) is not ContinuousYawSinCosBoundsV4 + or self.finding_codes + ): + raise ValueError( + "exact sin/cos outcome requires bounds and no findings" + ) + return + if self.bounds is not None or not self.finding_codes: + raise ValueError( + "non-exact sin/cos outcome requires findings and no bounds" + ) + expected_prefix = { + ContinuousYawIntervalKindV4.NUMERIC_GAP: "NUMERIC_GAP:", + ContinuousYawIntervalKindV4.RESOURCE_LIMIT: "RESOURCE_LIMIT:", + ContinuousYawIntervalKindV4.UNSUPPORTED: "UNSUPPORTED:", + }[self.kind] + if any(not code.startswith(expected_prefix) for code in self.finding_codes): + raise ValueError("sin/cos finding does not match its typed outcome") + + +def compile_lifted_turn_sin_cos_bounds_v4( + lift_bounds: object, + *, + atomic_budget: SO2AtomicBudgetV2, +) -> ContinuousYawSinCosOutcomeV4: + """Bound sine/cosine on one closed turn cell without sampling or libm trig.""" + + if type(lift_bounds) is not ContinuousYawLiftBoundsV4: + raise TypeError("lift_bounds must be a ContinuousYawLiftBoundsV4") + budget = _require_cardinal_budget_v3(atomic_budget) + start_used = budget.used + try: + with warnings.catch_warnings(): + warnings.simplefilter("error", Warning) + lower_sine, lower_cosine = _turn_point_sin_cos_v4( + lift_bounds.lower, + budget, + ) + upper_sine, upper_cosine = _turn_point_sin_cos_v4( + lift_bounds.upper, + budget, + ) + critical_turns = _turn_quarter_critical_angles_v4( + lift_bounds.lower, + lift_bounds.upper, + ) + budget.consume(len(critical_turns)) + sine_lower = min(lower_sine[0], upper_sine[0]) + sine_upper = max(lower_sine[1], upper_sine[1]) + cosine_lower = min(lower_cosine[0], upper_cosine[0]) + cosine_upper = max(lower_cosine[1], upper_cosine[1]) + for turn in critical_turns: + quarter = _cardinal_quarter_turn_v4(turn) + if quarter is None: + raise RuntimeError("turn-quarter critical angle was not cardinal") + exact_sine, exact_cosine = _exact_cardinal_sin_cos_v4(quarter) + sine_lower = min(sine_lower, exact_sine) + sine_upper = max(sine_upper, exact_sine) + cosine_lower = min(cosine_lower, exact_cosine) + cosine_upper = max(cosine_upper, exact_cosine) + return ContinuousYawSinCosOutcomeV4( + ContinuousYawIntervalKindV4.EXACT, + bounds=ContinuousYawSinCosBoundsV4( + lift_bounds=lift_bounds, + sine=_publish_rational_enclosure(sine_lower, sine_upper), + cosine=_publish_rational_enclosure(cosine_lower, cosine_upper), + critical_turns=critical_turns, + atomic_steps_used=budget.used - start_used, + ), + ) + except SO2AtomicBudgetExhaustedV2: + return ContinuousYawSinCosOutcomeV4( + ContinuousYawIntervalKindV4.RESOURCE_LIMIT, + finding_codes=("RESOURCE_LIMIT:SO2_ATOMIC_STEPS",), + ) + except _SO2NumericGapV2 as error: + return ContinuousYawSinCosOutcomeV4( + ContinuousYawIntervalKindV4.NUMERIC_GAP, + finding_codes=(error.finding_code,), + ) + except (OverflowError, FloatingPointError): + return ContinuousYawSinCosOutcomeV4( + ContinuousYawIntervalKindV4.NUMERIC_GAP, + finding_codes=("NUMERIC_GAP:SO2_ARITHMETIC",), + ) + except RuntimeWarning: + return ContinuousYawSinCosOutcomeV4( + ContinuousYawIntervalKindV4.NUMERIC_GAP, + finding_codes=("NUMERIC_GAP:SO2_RUNTIME_WARNING",), + ) + + +def _turn_point_sin_cos_v4( + turn: Fraction, + budget: SO2AtomicBudgetV2, +) -> tuple[tuple[Fraction, Fraction], tuple[Fraction, Fraction]]: + """Use the retained rational Taylor enclosure after exact turn reduction.""" + + if type(turn) is not Fraction: + raise TypeError("turn must be an exact Fraction") + _require_numeric_fraction_cap(turn, "NUMERIC_GAP:LIFTED_TURN_FRACTION_BIT_CAP") + budget.consume() + quarter = _cardinal_quarter_turn_v4(turn) + if quarter is not None: + sine, cosine = _exact_cardinal_sin_cos_v4(quarter) + return (sine, sine), (cosine, cosine) + nearest_quarter = _floor_fraction(4 * turn + Fraction(1, 2)) + reduced_turn = turn - Fraction(nearest_quarter, 4) + if not Fraction(-1, 8) <= reduced_turn <= Fraction(1, 8): + raise RuntimeError("exact turn range reduction invariant failed") + pi_lower, pi_upper = _machin_pi_enclosure_v4(budget) + if reduced_turn >= 0: + reduced_lower = 2 * reduced_turn * pi_lower + reduced_upper = 2 * reduced_turn * pi_upper + else: + reduced_lower = 2 * reduced_turn * pi_upper + reduced_upper = 2 * reduced_turn * pi_lower + _require_numeric_fraction_cap( + reduced_lower, + "NUMERIC_GAP:TURN_REDUCED_ARGUMENT_FRACTION_BIT_CAP", + ) + _require_numeric_fraction_cap( + reduced_upper, + "NUMERIC_GAP:TURN_REDUCED_ARGUMENT_FRACTION_BIT_CAP", + ) + reduced = _publish_rational_enclosure(reduced_lower, reduced_upper) + base_sine, base_cosine = _reduced_sin_cos_v2(reduced, budget) + return _restore_quadrant_v2( + base_sine, + base_cosine, + nearest_quarter % 4, + negative=False, + budget=budget, + ) + + +def _machin_pi_enclosure_v4( + budget: SO2AtomicBudgetV2, +) -> tuple[Fraction, Fraction]: + """Return a fixed-order exact Machin enclosure for turn-domain reduction.""" + + atan5 = _AlternatingAtanStateV2.create(5) + atan239 = _AlternatingAtanStateV2.create(239) + for _ in range(SO2_INTERVAL_TAYLOR_TERMS_V2): + atan5.extend_one(budget) + atan239.extend_one(budget) + atan5_lower, atan5_upper = atan5.enclosure() + atan239_lower, atan239_upper = atan239.enclosure() + pi_lower = 16 * atan5_lower - 4 * atan239_upper + pi_upper = 16 * atan5_upper - 4 * atan239_lower + _require_numeric_fraction_cap(pi_lower, "NUMERIC_GAP:PI_FRACTION_BIT_CAP") + _require_numeric_fraction_cap(pi_upper, "NUMERIC_GAP:PI_FRACTION_BIT_CAP") + if pi_lower <= 0 or pi_lower > pi_upper: + raise RuntimeError("Machin enclosure invariant failed") + return pi_lower, pi_upper + + +def _turn_quarter_critical_angles_v4( + lower: Fraction, + upper: Fraction, +) -> tuple[Fraction, ...]: + """Enumerate all exact n/4 extrema in deterministic lifted-turn order.""" + + if type(lower) is not Fraction or type(upper) is not Fraction or lower > upper: + raise ValueError("lifted turn endpoints must be ordered exact Fractions") + first = _floor_fraction(4 * lower) + last = _floor_fraction(4 * upper) + return tuple( + Fraction(index, 4) + for index in range(first, last + 1) + if lower <= Fraction(index, 4) <= upper + ) + + +def _cardinal_quarter_turn_v4(turn: Fraction) -> int | None: + """Return the exact cardinal index when a lifted turn is an n/4 point.""" + + quarter_turns = 4 * turn + if quarter_turns.denominator != 1: + return None + return quarter_turns.numerator % 4 + + +def _exact_cardinal_sin_cos_v4(quarter: int) -> tuple[Fraction, Fraction]: + if quarter not in (0, 1, 2, 3): + raise ValueError("quarter must be a cardinal index") + return ( + (Fraction(), Fraction(1)), + (Fraction(1), Fraction()), + (Fraction(), Fraction(-1)), + (Fraction(-1), Fraction()), + )[quarter] + + __all__ = ( "SO2_INTERVAL_KERNEL_CERTIFIED_OUTWARD_ERROR_M_V2", "SO2_INTERVAL_KERNEL_ID_V2", @@ -559,11 +1137,23 @@ def _require_fraction_cap(value: Fraction) -> None: "SO2_INTERVAL_MAX_FRACTION_BITS_V2", "SO2_INTERVAL_MAX_MACHIN_TERMS_V2", "SO2_INTERVAL_TAYLOR_TERMS_V2", + "CardinalKernelKindV3", + "CardinalKernelOutcomeV3", + "ContinuousYawIntervalKindV4", + "ContinuousYawLiftBoundsV4", + "ContinuousYawLiftOutcomeV4", + "ContinuousYawSinCosBoundsV4", + "ContinuousYawSinCosOutcomeV4", "DirectedSinCosBoundsV2", "DirectedSinCosOutcomeV2", "RationalEnclosureV2", "SO2AtomicBudgetExhaustedV2", "SO2AtomicBudgetV2", "SO2IntervalKindV2", + "compile_continuous_yaw_lift_v4", "compile_directed_sin_cos_v2", + "compile_lifted_turn_sin_cos_bounds_v4", + "compose_cardinal_quarter_turns_v3", + "inverse_cardinal_quarter_turn_v3", + "rotate_cardinal_fraction_xy_v3", ) diff --git a/src/spatialcf/core/_internal/kernels/upright_box.py b/src/spatialcf/core/_internal/kernels/upright_box.py index ffecab3..41aa6cb 100644 --- a/src/spatialcf/core/_internal/kernels/upright_box.py +++ b/src/spatialcf/core/_internal/kernels/upright_box.py @@ -13,6 +13,7 @@ from spatialcf.core._internal.kernels import so2 as so2_interval from spatialcf.core._internal.kernels.so2 import ( + CardinalKernelKindV3, DirectedSinCosBoundsV2, RationalEnclosureV2, SO2AtomicBudgetExhaustedV2, @@ -941,6 +942,1001 @@ def _copy_pair_bounds( ) +@dataclass(frozen=True, slots=True) +class ClosedXYCellV3: + x_lower: Fraction + x_upper: Fraction + y_lower: Fraction + y_upper: Fraction + + def __post_init__(self) -> None: + if ( + any( + type(v) is not Fraction + for v in (self.x_lower, self.x_upper, self.y_lower, self.y_upper) + ) + or self.x_lower > self.x_upper + or self.y_lower > self.y_upper + ): + raise ValueError("closed XY cell requires ordered exact Fraction bounds") + + @property + def canonical_bounds(self) -> tuple[Fraction, Fraction, Fraction, Fraction]: + return (self.x_lower, self.x_upper, self.y_lower, self.y_upper) + + +@dataclass(frozen=True, slots=True) +class FixedCardinalBoxV3: + box_id: str + center_x: Fraction + center_y: Fraction + center_z: Fraction + half_x: Fraction + half_y: Fraction + half_z: Fraction + + def __post_init__(self) -> None: + if ( + type(self.box_id) is not str + or not self.box_id + or any( + type(v) is not Fraction + for v in ( + self.center_x, + self.center_y, + self.center_z, + self.half_x, + self.half_y, + self.half_z, + ) + ) + or min(self.half_x, self.half_y, self.half_z) < 0 + ): + raise ValueError("fixed cardinal box must be exact and non-negative") + + +@dataclass(frozen=True, slots=True) +class SupportSurfaceV3: + x_lower: Fraction + x_upper: Fraction + y_lower: Fraction + y_upper: Fraction + z: Fraction + + def __post_init__(self) -> None: + if ( + any( + type(value) is not Fraction + for value in ( + self.x_lower, + self.x_upper, + self.y_lower, + self.y_upper, + self.z, + ) + ) + or self.x_lower > self.x_upper + or self.y_lower > self.y_upper + ): + raise ValueError("support surface must use ordered exact Fraction bounds") + + +_FIXED_CARDINAL_OBJECTIVE_TERM_ROSTER_V3: dict[str, tuple[str, str, str, str, str]] = { + "T": ( + "definition:spatialcf/upright-se2/objective-selector-subject-pivot-displacement/1.0", + "definition:spatialcf/upright-se2/objective-subject-pivot-displacement", + "1.0", + "definition:spatialcf/upright-se2/metre/1.0", + "definition:spatialcf/upright-se2/objective-weighted-normalized-sum/1.0", + ), + "A": ( + "definition:spatialcf/upright-se2/objective-selector-angular-geodesic/1.0", + "definition:spatialcf/upright-se2/objective-angular-geodesic", + "1.0", + "definition:spatialcf/upright-se2/turn/1.0", + "definition:spatialcf/upright-se2/objective-weighted-normalized-sum/1.0", + ), + "R": ( + "definition:spatialcf/upright-se2/objective-selector-nontarget-relation-damage/1.0", + "definition:spatialcf/upright-se2/objective-nontarget-relation-damage", + "1.0", + "definition:spatialcf/upright-se2/dimensionless/1.0", + "definition:spatialcf/upright-se2/objective-weighted-normalized-sum/1.0", + ), + "V": ( + "definition:spatialcf/upright-se2/objective-selector-visibility-change/1.0", + "definition:spatialcf/upright-se2/objective-visibility-change", + "1.0", + "definition:spatialcf/upright-se2/dimensionless/1.0", + "definition:spatialcf/upright-se2/objective-weighted-normalized-sum/1.0", + ), + "S": ( + "definition:spatialcf/upright-se2/objective-selector-safety-margin-penalty/1.0", + "definition:spatialcf/upright-se2/objective-safety-margin-penalty", + "1.0", + "definition:spatialcf/upright-se2/dimensionless/1.0", + "definition:spatialcf/upright-se2/objective-weighted-normalized-sum/1.0", + ), +} + + +@dataclass(frozen=True, slots=True) +class FixedCardinalObjectiveTermV3: + """One caller-owned normalized term of the common exact cell objective.""" + + term_id: str + selector: str + metric_definition_id: str + metric_definition_version: str + unit: str + weight: Fraction + normalizer: Fraction + aggregation: str + + def __post_init__(self) -> None: + expected = ( + _FIXED_CARDINAL_OBJECTIVE_TERM_ROSTER_V3.get(self.term_id) + if type(self.term_id) is str + else None + ) + if ( + type(self.term_id) is not str + or expected is None + or any( + type(value) is not str or not value + for value in ( + self.selector, + self.metric_definition_id, + self.metric_definition_version, + self.unit, + self.aggregation, + ) + ) + or type(self.weight) is not Fraction + or self.weight < 0 + or (self.term_id in ("T", "A") and self.weight <= 0) + or type(self.normalizer) is not Fraction + or self.normalizer <= 0 + or ( + self.selector, + self.metric_definition_id, + self.metric_definition_version, + self.unit, + self.aggregation, + ) + != expected + ): + raise ValueError( + "objective term must match the closed request-bound roster" + ) + + +@dataclass(frozen=True, slots=True) +class FixedCardinalCellPolicyV3: + """Complete domain-neutral caller policy for one exact cardinal cell.""" + + policy_id: str + policy_version: str + relation_threshold: Fraction + relation_tolerance: Fraction + relation_comparator: str + relation_boundary: str + safety_penalty_scale: Fraction + safety_constraint_slack_target: Fraction + safety_rule: str + collision_clearance: Fraction + collision_contact_comparator: str + collision_boundary: str + support_accepted_contact_gap: tuple[Fraction, Fraction] + support_stability_margin: Fraction + support_containment_comparator: str + support_boundary: str + support_frame: str + support_normal: tuple[Fraction, Fraction, Fraction] + relation_definition_id: str + relation_definition_version: str + relation_symbol: str + relation_measurement: str + relation_operand: str + relation_geometry: str + visibility_cell: tuple[Fraction, Fraction, Fraction, Fraction] + visibility_bounds: tuple[Fraction, Fraction] + objective_terms: tuple[FixedCardinalObjectiveTermV3, ...] + atomic_step_limit: int + + def __post_init__(self) -> None: + if ( + type(self.policy_id) is not str + or not self.policy_id + or type(self.policy_version) is not str + or not self.policy_version + or type(self.relation_threshold) is not Fraction + or self.relation_threshold < 0 + or type(self.relation_tolerance) is not Fraction + or self.relation_tolerance < 0 + or self.relation_comparator not in ("GE", "LE") + or self.relation_boundary != "CLOSED" + or type(self.safety_penalty_scale) is not Fraction + or self.safety_penalty_scale < 0 + or type(self.safety_constraint_slack_target) is not Fraction + or self.safety_rule != "PENALIZE_BELOW_TARGET" + or type(self.collision_clearance) is not Fraction + or self.collision_clearance < 0 + or self.collision_contact_comparator != "GE" + or self.collision_boundary != "CLOSED" + or type(self.support_accepted_contact_gap) is not tuple + or len(self.support_accepted_contact_gap) != 2 + or any( + type(value) is not Fraction + for value in self.support_accepted_contact_gap + ) + or self.support_accepted_contact_gap[0] + > self.support_accepted_contact_gap[1] + or type(self.support_stability_margin) is not Fraction + or self.support_stability_margin < 0 + or self.support_containment_comparator != "GE" + or self.support_boundary != "CLOSED" + or self.support_frame != "WORLD_XY_Z_UP" + or self.support_normal != (Fraction(), Fraction(), Fraction(1)) + or self.relation_definition_id != "spatial-relation:fixed-cardinal" + or self.relation_definition_version != "definition:1" + or self.relation_symbol + not in ("LEFT", "RIGHT", "FRONT", "BEHIND", "NEAR", "FAR") + or self.relation_measurement + not in ( + "EXTENT_AWARE_SIGNED_AXIS_GAP", + "EXTENT_AWARE_EUCLIDEAN_SEPARATION", + ) + or self.relation_operand != "SUBJECT_COMPOUND_TO_REFERENCE" + or self.relation_geometry != "UPRIGHT_AABB_EXTENTS" + or type(self.visibility_cell) is not tuple + or len(self.visibility_cell) != 4 + or any(type(value) is not Fraction for value in self.visibility_cell) + or self.visibility_cell[0] > self.visibility_cell[1] + or self.visibility_cell[2] > self.visibility_cell[3] + or type(self.visibility_bounds) is not tuple + or len(self.visibility_bounds) != 2 + or any(type(value) is not Fraction for value in self.visibility_bounds) + or not Fraction() + <= self.visibility_bounds[0] + <= self.visibility_bounds[1] + <= Fraction(1) + or type(self.objective_terms) is not tuple + or len(self.objective_terms) != 5 + or any( + type(term) is not FixedCardinalObjectiveTermV3 + for term in self.objective_terms + ) + or tuple(term.term_id for term in self.objective_terms) + != ("T", "A", "R", "V", "S") + or type(self.atomic_step_limit) is not int + or self.atomic_step_limit <= 0 + ): + raise ValueError("fixed cardinal cell policy must be a closed caller value") + + +@dataclass(frozen=True, slots=True) +class FixedCardinalCellBoundsV3: + cell_id: str + cell: ClosedXYCellV3 + quarter_turns_ccw: int + collision_constraint_present: bool + collision_inner_slack: Fraction + collision_outer_slack: Fraction + support_contact_gap: tuple[Fraction, Fraction] + support_clearance_inner: Fraction + support_clearance_outer: Fraction + support_normal: tuple[Fraction, Fraction, Fraction] + support_frame: str + closed_containment: bool + stability_margin_inner: Fraction + relation_measurement: str + relation_inner_slack: Fraction + relation_outer_slack: Fraction + relation_comparator: str + relation_threshold: Fraction + relation_tolerance: Fraction + relation_boundary: str + relation_squared_distance_bounds: tuple[Fraction, Fraction] | None + relation_inner_success: bool + relation_outer_failure: bool + common_cell_semantic_objective_terms: tuple[tuple[str, Fraction, Fraction], ...] + common_cell_objective_terms: tuple[tuple[str, Fraction, Fraction], ...] + inner_hard_constraint_slack: Fraction + outer_hard_constraint_slack: Fraction + safety_penalty_outer: Fraction + + def __post_init__(self) -> None: + if ( + type(self.cell_id) is not str + or not self.cell_id + or type(self.cell) is not ClosedXYCellV3 + or type(self.quarter_turns_ccw) is not int + or self.quarter_turns_ccw not in (0, 1, 2, 3) + or type(self.collision_constraint_present) is not bool + or any( + type(value) is not Fraction + for value in ( + self.collision_inner_slack, + self.collision_outer_slack, + self.support_clearance_inner, + self.support_clearance_outer, + self.stability_margin_inner, + self.relation_inner_slack, + self.relation_outer_slack, + self.relation_threshold, + self.relation_tolerance, + self.inner_hard_constraint_slack, + self.outer_hard_constraint_slack, + self.safety_penalty_outer, + ) + ) + or self.inner_hard_constraint_slack > self.outer_hard_constraint_slack + or self.relation_tolerance < 0 + or self.safety_penalty_outer < 0 + or self.support_frame != "WORLD_XY_Z_UP" + or self.support_normal != (Fraction(), Fraction(), Fraction(1)) + or self.relation_boundary != "CLOSED" + or type(self.relation_inner_success) is not bool + or type(self.relation_outer_failure) is not bool + ): + raise ValueError("fixed cardinal cell bounds are not a closed exact record") + if ( + type(self.support_contact_gap) is not tuple + or len(self.support_contact_gap) != 2 + or any(type(value) is not Fraction for value in self.support_contact_gap) + or self.support_contact_gap[0] > self.support_contact_gap[1] + ): + raise ValueError("support contact gap must be an ordered exact interval") + if self.relation_squared_distance_bounds is not None and ( + type(self.relation_squared_distance_bounds) is not tuple + or len(self.relation_squared_distance_bounds) != 2 + or any( + type(value) is not Fraction + for value in self.relation_squared_distance_bounds + ) + or self.relation_squared_distance_bounds[0] + > self.relation_squared_distance_bounds[1] + ): + raise ValueError( + "squared distance bounds must be an ordered exact interval" + ) + for objective_terms in ( + self.common_cell_semantic_objective_terms, + self.common_cell_objective_terms, + ): + if tuple(term[0] for term in objective_terms) != ( + "T", + "A", + "R", + "V", + "S", + ): + raise ValueError( + "common-cell objective bounds must contain ordered T/A/R/V/S" + ) + if any( + type(value) is not Fraction + for _, lower, upper in objective_terms + for value in (lower, upper) + ) or any(lower > upper for _, lower, upper in objective_terms): + raise ValueError( + "objective term bounds must be ordered exact Fractions" + ) + + @property + def collision_slack_lower(self) -> Fraction: + """Backward-compatible lower collision slack view for the additive seam.""" + + return self.collision_inner_slack + + @property + def support_slack_lower(self) -> Fraction: + """Backward-compatible lower support slack view for the additive seam.""" + + return self.support_clearance_inner + + @property + def relation_slack_lower(self) -> Fraction: + """Backward-compatible lower relation slack view for the additive seam.""" + + return self.relation_inner_slack + + @property + def safety_penalty_upper(self) -> Fraction: + """Backward-compatible upper safety penalty view for the additive seam.""" + + return self.safety_penalty_outer + + +@dataclass(frozen=True, slots=True) +class FixedCardinalCellOutcomeV3: + kind: CardinalKernelKindV3 + bounds: FixedCardinalCellBoundsV3 | None = None + atomic_steps_used: int = 0 + proof_rows: tuple[str, ...] = () + finding_codes: tuple[str, ...] = () + + def __post_init__(self) -> None: + if type(self.kind) is not CardinalKernelKindV3: + raise TypeError("fixed cardinal outcome must use CardinalKernelKindV3") + if type(self.atomic_steps_used) is not int or self.atomic_steps_used < 0: + raise ValueError("fixed cardinal outcome usage must be non-negative") + for field_name in ("proof_rows", "finding_codes"): + values = getattr(self, field_name) + if type(values) is not tuple or any( + type(value) is not str or not value.strip() for value in values + ): + raise ValueError( + f"fixed cardinal {field_name} must be non-blank strings" + ) + object.__setattr__(self, field_name, tuple(sorted(set(values)))) + if self.kind is CardinalKernelKindV3.EXACT: + if ( + type(self.bounds) is not FixedCardinalCellBoundsV3 + or self.finding_codes + or not self.proof_rows + ): + raise ValueError( + "exact fixed cardinal outcome must carry only bounds and proof" + ) + return + if self.bounds is not None or not self.finding_codes or not self.proof_rows: + raise ValueError( + "non-exact fixed cardinal outcome must carry finding and proof" + ) + prefix = { + CardinalKernelKindV3.NUMERIC_GAP: "NUMERIC_GAP:", + CardinalKernelKindV3.RESOURCE_LIMIT: "RESOURCE_LIMIT:", + CardinalKernelKindV3.UNSUPPORTED: "UNSUPPORTED:", + }[self.kind] + if any(not code.startswith(prefix) for code in self.finding_codes): + raise ValueError("fixed cardinal finding must match outcome kind") + + +_CardinalExtentV3 = tuple[Fraction, Fraction, Fraction, Fraction, Fraction, Fraction] + + +def _require_fixed_cardinal_box_v3(value: object, *, label: str) -> FixedCardinalBoxV3: + if type(value) is not FixedCardinalBoxV3: + raise TypeError(f"{label} must be a FixedCardinalBoxV3") + return value + + +def _require_fixed_cardinal_roster_v3( + value: object, *, label: str, nonempty: bool +) -> tuple[FixedCardinalBoxV3, ...]: + if type(value) is not tuple or (nonempty and not value): + raise ValueError( + f"{label} must be an exact {'non-empty ' if nonempty else ''}tuple" + ) + boxes = tuple(_require_fixed_cardinal_box_v3(box, label=label) for box in value) + ids = tuple(box.box_id for box in boxes) + if ids != tuple(sorted(ids)) or len(set(ids)) != len(ids): + raise ValueError(f"{label} must be sorted and have unique box IDs") + return boxes + + +def _fixed_cardinal_extent_v3( + box: FixedCardinalBoxV3, + *, + cell: ClosedXYCellV3, + q: int, + pivot_xy: tuple[Fraction, Fraction], + atomic_budget: SO2AtomicBudgetV2, +) -> _CardinalExtentV3: + relative_x = box.center_x - pivot_xy[0] + relative_y = box.center_y - pivot_xy[1] + rotated = so2_interval.rotate_cardinal_fraction_xy_v3( + relative_x, + relative_y, + q, + atomic_budget=atomic_budget, + ) + if ( + rotated.kind is not CardinalKernelKindV3.EXACT + or type(rotated.value) is not tuple + or any(type(value) is not Fraction for value in rotated.value) + ): + raise RuntimeError( + "prevalidated cardinal SO2 delegation did not return an exact XY pair" + ) + rotated_x, rotated_y = rotated.value + center_x = pivot_xy[0] + rotated_x + center_y = pivot_xy[1] + rotated_y + half_x, half_y = ( + (box.half_x, box.half_y) if q % 2 == 0 else (box.half_y, box.half_x) + ) + return ( + center_x + cell.x_lower - half_x, + center_x + cell.x_upper + half_x, + center_y + cell.y_lower - half_y, + center_y + cell.y_upper + half_y, + box.center_z - box.half_z, + box.center_z + box.half_z, + ) + + +def _compound_extent_v3(extents: tuple[_CardinalExtentV3, ...]) -> _CardinalExtentV3: + return ( + min(extent[0] for extent in extents), + max(extent[1] for extent in extents), + min(extent[2] for extent in extents), + max(extent[3] for extent in extents), + min(extent[4] for extent in extents), + max(extent[5] for extent in extents), + ) + + +def _pair_clearance_bounds_v3( + left: _CardinalExtentV3, right: _CardinalExtentV3 +) -> tuple[Fraction, Fraction]: + lower_gaps = ( + right[0] - left[1], + left[0] - right[1], + right[2] - left[3], + left[2] - right[3], + right[4] - left[5], + left[4] - right[5], + ) + upper_gaps = ( + right[1] - left[0], + left[1] - right[0], + right[3] - left[2], + left[3] - right[2], + right[5] - left[4], + left[5] - right[4], + ) + return max(lower_gaps), max(upper_gaps) + + +def _absolute_fraction_v3(value: Fraction) -> Fraction: + return value if value >= 0 else -value + + +def _axis_separation_v3( + left_lower: Fraction, + left_upper: Fraction, + right_lower: Fraction, + right_upper: Fraction, +) -> Fraction: + return max(Fraction(), right_lower - left_upper, left_lower - right_upper) + + +def _squared_extent_separation_v3( + left: _CardinalExtentV3, right: _CardinalExtentV3 +) -> Fraction: + x = _axis_separation_v3(left[0], left[1], right[0], right[1]) + y = _axis_separation_v3(left[2], left[3], right[2], right[3]) + z = _axis_separation_v3(left[4], left[5], right[4], right[5]) + return x * x + y * y + z * z + + +def _translate_extent_xy_v3( + extent: _CardinalExtentV3, x: Fraction, y: Fraction +) -> _CardinalExtentV3: + return ( + extent[0] + x, + extent[1] + x, + extent[2] + y, + extent[3] + y, + extent[4], + extent[5], + ) + + +def _squared_distance_bounds_over_cell_v3( + *, + subject_base_extents: tuple[_CardinalExtentV3, ...], + subject_cell_extents: tuple[_CardinalExtentV3, ...], + reference_extent: _CardinalExtentV3, + cell: ClosedXYCellV3, +) -> tuple[Fraction, Fraction]: + """Sound exact bounds for compound-box separation on a closed translation cell. + + The lower bound expands each subject extent by the entire cell. The upper + bound evaluates all closed-cell corners for every body; taking the minimum + over bodies remains a sound compound-union upper bound. Both are squared + distances, so no irrational square root or sampling is needed. + """ + + lower = min( + _squared_extent_separation_v3(extent, reference_extent) + for extent in subject_cell_extents + ) + corners = ( + (cell.x_lower, cell.y_lower), + (cell.x_lower, cell.y_upper), + (cell.x_upper, cell.y_lower), + (cell.x_upper, cell.y_upper), + ) + upper = min( + max( + _squared_extent_separation_v3( + _translate_extent_xy_v3(extent, x, y), reference_extent + ) + for x, y in corners + ) + for extent in subject_base_extents + ) + if lower > upper: + raise RuntimeError("squared distance bounds must be ordered") + return lower, upper + + +def _fixed_cardinal_cell_id_v3(cell: ClosedXYCellV3, q: int) -> str: + values = ",".join( + f"{value.numerator}/{value.denominator}" for value in cell.canonical_bounds + ) + return f"cardinal-cell:q={q}:xy={values}" + + +def evaluate_fixed_cardinal_cell_v3( + *, + cell: ClosedXYCellV3, + quarter_turns_ccw: int, + subject_boxes: tuple[FixedCardinalBoxV3, ...], + obstacle_boxes: tuple[FixedCardinalBoxV3, ...], + support_surface: SupportSurfaceV3, + relation: str, + reference_box: FixedCardinalBoxV3, + near_far_threshold: Fraction, + policy: FixedCardinalCellPolicyV3, + atomic_budget: SO2AtomicBudgetV2, + subject_pivot_xy: tuple[Fraction, Fraction] = (Fraction(), Fraction()), + objective_subject_pivot_xy: tuple[Fraction, Fraction] | None = None, +) -> FixedCardinalCellOutcomeV3: + """Bound compound collision/support/relation/safety over one closed cardinal XY cell.""" + if type(cell) is not ClosedXYCellV3: + raise TypeError("cell must be a ClosedXYCellV3") + if type(quarter_turns_ccw) is not int: + raise TypeError("quarter_turns_ccw must be an exact int") + if quarter_turns_ccw not in (0, 1, 2, 3): + raise ValueError("quarter_turns_ccw must be in 0..3") + subjects = _require_fixed_cardinal_roster_v3( + subject_boxes, + label="subject_boxes", + nonempty=True, + ) + obstacles = _require_fixed_cardinal_roster_v3( + obstacle_boxes, + label="obstacle_boxes", + nonempty=False, + ) + if type(support_surface) is not SupportSurfaceV3: + raise TypeError("support_surface must be a SupportSurfaceV3") + if relation not in ("LEFT", "RIGHT", "FRONT", "BEHIND", "NEAR", "FAR"): + raise ValueError("relation must be a registered fixed-cardinal relation") + reference = _require_fixed_cardinal_box_v3(reference_box, label="reference_box") + if type(near_far_threshold) is not Fraction or near_far_threshold < 0: + raise ValueError("near_far_threshold must be a non-negative exact Fraction") + if type(policy) is not FixedCardinalCellPolicyV3: + raise TypeError("policy must be a FixedCardinalCellPolicyV3") + if near_far_threshold != policy.relation_threshold: + raise ValueError("near_far_threshold must match the caller policy") + if policy.relation_symbol != relation: + raise ValueError("relation must match the caller-bound registered symbol") + expected_measurement = ( + "EXTENT_AWARE_EUCLIDEAN_SEPARATION" + if relation in ("NEAR", "FAR") + else "EXTENT_AWARE_SIGNED_AXIS_GAP" + ) + if policy.relation_measurement != expected_measurement: + raise ValueError("relation measurement must match its registered symbol") + if policy.visibility_cell != cell.canonical_bounds: + raise ValueError("visibility bounds must be supplied for this exact cell") + if ( + type(subject_pivot_xy) is not tuple + or len(subject_pivot_xy) != 2 + or any(type(value) is not Fraction for value in subject_pivot_xy) + ): + raise TypeError("subject_pivot_xy must contain two exact Fractions") + if objective_subject_pivot_xy is None: + objective_subject_pivot_xy = subject_pivot_xy + if ( + type(objective_subject_pivot_xy) is not tuple + or len(objective_subject_pivot_xy) != 2 + or any(type(value) is not Fraction for value in objective_subject_pivot_xy) + ): + raise TypeError("objective_subject_pivot_xy must contain two exact Fractions") + if type(atomic_budget) is not SO2AtomicBudgetV2: + raise TypeError("atomic_budget must be an SO2AtomicBudgetV2") + atomic_budget.validate() + if atomic_budget.limit != policy.atomic_step_limit: + raise ValueError("atomic budget limit must match the caller policy") + delegated_rotation_steps = 2 * len(subjects) + len(obstacles) + 2 + required_steps = max( + 4 + len(subjects) + len(obstacles), + delegated_rotation_steps, + ) + if atomic_budget.remaining < required_steps: + return FixedCardinalCellOutcomeV3( + CardinalKernelKindV3.RESOURCE_LIMIT, + atomic_steps_used=0, + proof_rows=("RESOURCE:SO2_ATOMIC_STEPS:cap-minus-one",), + finding_codes=("RESOURCE_LIMIT:SO2_ATOMIC_STEPS",), + ) + + zero_cell = ClosedXYCellV3(Fraction(), Fraction(), Fraction(), Fraction()) + subject_base_extents = tuple( + _fixed_cardinal_extent_v3( + box, + cell=zero_cell, + q=quarter_turns_ccw, + pivot_xy=subject_pivot_xy, + atomic_budget=atomic_budget, + ) + for box in subjects + ) + subject_extents = tuple( + _fixed_cardinal_extent_v3( + box, + cell=cell, + q=quarter_turns_ccw, + pivot_xy=subject_pivot_xy, + atomic_budget=atomic_budget, + ) + for box in subjects + ) + obstacle_extents = tuple( + _fixed_cardinal_extent_v3( + box, + cell=zero_cell, + q=0, + pivot_xy=(Fraction(), Fraction()), + atomic_budget=atomic_budget, + ) + for box in obstacles + ) + collision_pairs = tuple( + _pair_clearance_bounds_v3(subject_extent, obstacle_extent) + for subject_extent in subject_extents + for obstacle_extent in obstacle_extents + ) + collision_inner, collision_outer = ( + ( + min(pair[0] for pair in collision_pairs) - policy.collision_clearance, + min(pair[1] for pair in collision_pairs) - policy.collision_clearance, + ) + if collision_pairs + else (Fraction(), Fraction()) + ) + if cell.x_lower == cell.x_upper and cell.y_lower == cell.y_upper: + collision_outer = collision_inner + base_compound = _compound_extent_v3(subject_base_extents) + containment_inner = min( + base_compound[0] + cell.x_lower - support_surface.x_lower, + support_surface.x_upper - (base_compound[1] + cell.x_upper), + base_compound[2] + cell.y_lower - support_surface.y_lower, + support_surface.y_upper - (base_compound[3] + cell.y_upper), + ) + containment_outer = min( + base_compound[0] + cell.x_upper - support_surface.x_lower, + support_surface.x_upper - (base_compound[1] + cell.x_lower), + base_compound[2] + cell.y_upper - support_surface.y_lower, + support_surface.y_upper - (base_compound[3] + cell.y_lower), + ) + contact_gaps = tuple(extent[4] - support_surface.z for extent in subject_extents) + contact_gap = (min(contact_gaps), max(contact_gaps)) + accepted_lower, accepted_upper = policy.support_accepted_contact_gap + contact_inner = min( + min(contact_gaps) - accepted_lower, + accepted_upper - max(contact_gaps), + ) + contact_outer = min( + max(contact_gaps) - accepted_lower, + accepted_upper - min(contact_gaps), + ) + support_inner = min( + containment_inner - policy.support_stability_margin, contact_inner + ) + support_outer = min( + containment_outer - policy.support_stability_margin, contact_outer + ) + reference_extent = _fixed_cardinal_extent_v3( + reference, + cell=zero_cell, + q=0, + pivot_xy=(Fraction(), Fraction()), + atomic_budget=atomic_budget, + ) + if relation == "LEFT": + raw_lower = base_compound[1] + cell.x_lower - reference_extent[0] + raw_upper = base_compound[1] + cell.x_upper - reference_extent[0] + comparator = "LE" + measurement = "EXTENT_AWARE_SIGNED_AXIS_GAP" + squared_distance_bounds = None + elif relation == "RIGHT": + raw_lower = base_compound[0] + cell.x_lower - reference_extent[1] + raw_upper = base_compound[0] + cell.x_upper - reference_extent[1] + comparator = "GE" + measurement = "EXTENT_AWARE_SIGNED_AXIS_GAP" + squared_distance_bounds = None + elif relation == "FRONT": + raw_lower = base_compound[3] + cell.y_lower - reference_extent[2] + raw_upper = base_compound[3] + cell.y_upper - reference_extent[2] + comparator = "LE" + measurement = "EXTENT_AWARE_SIGNED_AXIS_GAP" + squared_distance_bounds = None + elif relation == "BEHIND": + raw_lower = base_compound[2] + cell.y_lower - reference_extent[3] + raw_upper = base_compound[2] + cell.y_upper - reference_extent[3] + comparator = "GE" + measurement = "EXTENT_AWARE_SIGNED_AXIS_GAP" + squared_distance_bounds = None + else: + squared_distance_bounds = _squared_distance_bounds_over_cell_v3( + subject_base_extents=subject_base_extents, + subject_cell_extents=subject_extents, + reference_extent=reference_extent, + cell=cell, + ) + squared_lower, squared_upper = squared_distance_bounds + if relation == "NEAR": + raw_lower, raw_upper = squared_lower, squared_upper + threshold = policy.relation_threshold + policy.relation_tolerance + comparator = "LE" + else: + raw_lower, raw_upper = squared_lower, squared_upper + threshold = max( + Fraction(), policy.relation_threshold - policy.relation_tolerance + ) + comparator = "GE" + threshold_squared = threshold * threshold + measurement = "EXTENT_AWARE_EUCLIDEAN_SEPARATION" + if comparator != policy.relation_comparator: + raise ValueError("relation comparator must match the registered relation") + if comparator == "LE": + relation_inner = ( + policy.relation_threshold + policy.relation_tolerance - raw_upper + ) + relation_outer = ( + policy.relation_threshold + policy.relation_tolerance - raw_lower + ) + if relation in ("NEAR",): + relation_inner = threshold_squared - raw_upper + relation_outer = threshold_squared - raw_lower + else: + relation_inner = raw_lower - ( + policy.relation_threshold - policy.relation_tolerance + ) + relation_outer = raw_upper - ( + policy.relation_threshold - policy.relation_tolerance + ) + if relation == "FAR": + relation_inner = raw_lower - threshold_squared + relation_outer = raw_upper - threshold_squared + relation_inner_success = relation_inner >= 0 + relation_outer_failure = relation_outer < 0 + hard_inner = min( + *( + (collision_inner, support_inner, relation_inner) + if collision_pairs + else (support_inner, relation_inner) + ) + ) + hard_outer = min( + *( + (collision_outer, support_outer, relation_outer) + if collision_pairs + else (support_outer, relation_outer) + ) + ) + safety = policy.safety_penalty_scale * max( + Fraction(), policy.safety_constraint_slack_target - hard_inner + ) + subject_relative_x = objective_subject_pivot_xy[0] - subject_pivot_xy[0] + subject_relative_y = objective_subject_pivot_xy[1] - subject_pivot_xy[1] + rotated_subject_pivot = so2_interval.rotate_cardinal_fraction_xy_v3( + subject_relative_x, + subject_relative_y, + quarter_turns_ccw, + atomic_budget=atomic_budget, + ) + if ( + rotated_subject_pivot.kind is not CardinalKernelKindV3.EXACT + or type(rotated_subject_pivot.value) is not tuple + or any(type(value) is not Fraction for value in rotated_subject_pivot.value) + ): + raise RuntimeError( + "prevalidated cardinal SO2 delegation did not rotate the subject pivot" + ) + rotated_pivot_x, rotated_pivot_y = rotated_subject_pivot.value + orbital_x = subject_pivot_xy[0] + rotated_pivot_x - objective_subject_pivot_xy[0] + orbital_y = subject_pivot_xy[1] + rotated_pivot_y - objective_subject_pivot_xy[1] + displacement_x = (orbital_x + cell.x_lower, orbital_x + cell.x_upper) + displacement_y = (orbital_y + cell.y_lower, orbital_y + cell.y_upper) + translation_lower = max( + _axis_separation_v3( + displacement_x[0], displacement_x[1], Fraction(), Fraction() + ), + _axis_separation_v3( + displacement_y[0], displacement_y[1], Fraction(), Fraction() + ), + ) + translation_upper = max( + _absolute_fraction_v3(displacement_x[0]), + _absolute_fraction_v3(displacement_x[1]), + ) + max( + _absolute_fraction_v3(displacement_y[0]), + _absolute_fraction_v3(displacement_y[1]), + ) + remaining_steps = required_steps - delegated_rotation_steps + if remaining_steps: + atomic_budget.consume(remaining_steps) + semantic_objective_terms = ( + ("T", translation_lower, translation_upper), + ( + "A", + Fraction(min(quarter_turns_ccw, 4 - quarter_turns_ccw), 4), + Fraction(min(quarter_turns_ccw, 4 - quarter_turns_ccw), 4), + ), + ("R", max(Fraction(), -relation_outer), max(Fraction(), -relation_inner)), + ("V", policy.visibility_bounds[0], policy.visibility_bounds[1]), + ("S", max(Fraction(), -hard_outer), safety), + ) + objective_terms = tuple( + ( + term_id, + lower * record.weight / record.normalizer, + upper * record.weight / record.normalizer, + ) + for (term_id, lower, upper), record in zip( + semantic_objective_terms, policy.objective_terms, strict=True + ) + ) + return FixedCardinalCellOutcomeV3( + CardinalKernelKindV3.EXACT, + FixedCardinalCellBoundsV3( + cell_id=_fixed_cardinal_cell_id_v3(cell, quarter_turns_ccw), + cell=cell, + quarter_turns_ccw=quarter_turns_ccw, + collision_constraint_present=bool(collision_pairs), + collision_inner_slack=collision_inner, + collision_outer_slack=collision_outer, + support_contact_gap=contact_gap, + support_clearance_inner=support_inner, + support_clearance_outer=support_outer, + support_normal=(Fraction(), Fraction(), Fraction(1)), + support_frame="WORLD_XY_Z_UP", + closed_containment=True, + stability_margin_inner=( + containment_inner - policy.support_stability_margin + ), + relation_measurement=measurement, + relation_inner_slack=relation_inner, + relation_outer_slack=relation_outer, + relation_comparator=policy.relation_comparator, + relation_threshold=policy.relation_threshold, + relation_tolerance=policy.relation_tolerance, + relation_boundary=policy.relation_boundary, + relation_squared_distance_bounds=squared_distance_bounds, + relation_inner_success=relation_inner_success, + relation_outer_failure=relation_outer_failure, + common_cell_semantic_objective_terms=semantic_objective_terms, + common_cell_objective_terms=objective_terms, + inner_hard_constraint_slack=hard_inner, + outer_hard_constraint_slack=hard_outer, + safety_penalty_outer=safety, + ), + atomic_steps_used=required_steps, + proof_rows=( + f"CELL:{_fixed_cardinal_cell_id_v3(cell, quarter_turns_ccw)}", + "SO2:CARDINAL_XY:DELEGATED", + ( + "COLLISION:COMPLETE_COMPOUND_CLOSED_CONTACT" + if collision_pairs + else "COLLISION:UNCONSTRAINED:NO_OBSTACLES" + ), + "SUPPORT:WORLD_XY_Z_UP:CLOSED_CONTAINMENT", + f"RELATION:{relation}:{measurement}:{comparator}", + f"POLICY:{policy.policy_id}:{policy.policy_version}", + "OBJECTIVE:COMMON_CELL:T-A-R-V-S", + "SAFETY:HARD_CONSTRAINT_INNER_OUTER", + ), + ) + + def _failure( kind: SO2IntervalKindV2, finding_code: str, @@ -958,7 +1954,1738 @@ def _pair_failure( ) +@dataclass(frozen=True, slots=True) +class ContinuousYawBoxBoundsV4: + """Directed axes and outer AABB for every pose in one XY-times-yaw cell.""" + + box: FixedCardinalBoxV3 + cell: ClosedXYCellV3 + pivot_xy: tuple[Fraction, Fraction] + yaw_bounds: so2_interval.ContinuousYawSinCosBoundsV4 + local_x_axis: OrientedAxisEnclosureV2 + local_y_axis: OrientedAxisEnclosureV2 + center_x: RationalEnclosureV2 + center_y: RationalEnclosureV2 + center_z: Fraction + half_x: Fraction + half_y: Fraction + half_z: Fraction + x_radius: RationalEnclosureV2 + y_radius: RationalEnclosureV2 + aabb_x: RationalEnclosureV2 + aabb_y: RationalEnclosureV2 + aabb_z: RationalEnclosureV2 + atomic_steps_used: int + + def __post_init__(self) -> None: + if type(self.box) is not FixedCardinalBoxV3: + raise TypeError("box must be a FixedCardinalBoxV3") + if type(self.cell) is not ClosedXYCellV3: + raise TypeError("cell must be a ClosedXYCellV3") + if ( + type(self.pivot_xy) is not tuple + or len(self.pivot_xy) != 2 + or any(type(value) is not Fraction for value in self.pivot_xy) + ): + raise TypeError("pivot_xy must contain two exact Fractions") + if type(self.yaw_bounds) is not so2_interval.ContinuousYawSinCosBoundsV4: + raise TypeError("yaw_bounds must be ContinuousYawSinCosBoundsV4") + for field_name in ("local_x_axis", "local_y_axis"): + value = getattr(self, field_name) + if type(value) is not OrientedAxisEnclosureV2: + raise TypeError(f"{field_name} must be an OrientedAxisEnclosureV2") + object.__setattr__( + self, + field_name, + OrientedAxisEnclosureV2(x=value.x, y=value.y), + ) + if not ( + self.local_x_axis.x == self.yaw_bounds.cosine + and self.local_x_axis.y == self.yaw_bounds.sine + and self.local_y_axis.x.rational_lower + == -self.local_x_axis.y.rational_upper + and self.local_y_axis.x.rational_upper + == -self.local_x_axis.y.rational_lower + and self.local_y_axis.y == self.local_x_axis.x + ): + raise ValueError( + "continuous box axes must reproduce the directed yaw bounds" + ) + for field_name in ( + "center_x", + "center_y", + "x_radius", + "y_radius", + "aabb_x", + "aabb_y", + "aabb_z", + ): + value = getattr(self, field_name) + if type(value) is not RationalEnclosureV2: + raise TypeError(f"{field_name} must be a RationalEnclosureV2") + object.__setattr__(self, field_name, _copy_enclosure(value)) + for field_name, expected in ( + ("center_z", self.box.center_z), + ("half_x", self.box.half_x), + ("half_y", self.box.half_y), + ("half_z", self.box.half_z), + ): + value = getattr(self, field_name) + if type(value) is not Fraction: + raise TypeError(f"{field_name} must be an exact Fraction") + if value != expected: + raise ValueError(f"{field_name} must reproduce the fixed source box") + if self.x_radius.rational_lower < 0 or self.y_radius.rational_lower < 0: + raise ValueError("continuous AABB radii must be non-negative") + absolute_cosine = _absolute_interval_no_budget( + self.local_x_axis.x.rational_lower, + self.local_x_axis.x.rational_upper, + ) + absolute_sine = _absolute_interval_no_budget( + self.local_x_axis.y.rational_lower, + self.local_x_axis.y.rational_upper, + ) + if _enclosure_tuple(self.x_radius) != ( + absolute_cosine[0] * self.half_x + absolute_sine[0] * self.half_y, + absolute_cosine[1] * self.half_x + absolute_sine[1] * self.half_y, + ) or _enclosure_tuple(self.y_radius) != ( + absolute_sine[0] * self.half_x + absolute_cosine[0] * self.half_y, + absolute_sine[1] * self.half_x + absolute_cosine[1] * self.half_y, + ): + raise ValueError("continuous AABB radii must match axes and source extents") + if ( + _enclosure_tuple(self.aabb_x) + != ( + self.center_x.rational_lower - self.x_radius.rational_upper, + self.center_x.rational_upper + self.x_radius.rational_upper, + ) + or _enclosure_tuple(self.aabb_y) + != ( + self.center_y.rational_lower - self.y_radius.rational_upper, + self.center_y.rational_upper + self.y_radius.rational_upper, + ) + or _enclosure_tuple(self.aabb_z) + != ( + self.center_z - self.half_z, + self.center_z + self.half_z, + ) + ): + raise ValueError("continuous AABB must enclose its center and radii") + if type(self.atomic_steps_used) is not int or self.atomic_steps_used <= 0: + raise ValueError("continuous box atomic usage must be a positive exact int") + + +@dataclass(frozen=True, slots=True) +class ContinuousYawBoxOutcomeV4: + """Typed completion of one continuous-yaw upright-box outer bound.""" + + kind: so2_interval.ContinuousYawIntervalKindV4 + bounds: ContinuousYawBoxBoundsV4 | None = None + finding_codes: tuple[str, ...] = () + + def __post_init__(self) -> None: + if type(self.kind) is not so2_interval.ContinuousYawIntervalKindV4: + raise TypeError("kind must be a ContinuousYawIntervalKindV4") + if type(self.finding_codes) is not tuple or any( + type(code) is not str or not code.strip() for code in self.finding_codes + ): + raise ValueError("finding_codes must be non-blank strings") + object.__setattr__( + self, "finding_codes", tuple(sorted(set(self.finding_codes))) + ) + if self.kind is so2_interval.ContinuousYawIntervalKindV4.EXACT: + if type(self.bounds) is not ContinuousYawBoxBoundsV4 or self.finding_codes: + raise ValueError("exact continuous box outcome requires bounds only") + return + if self.bounds is not None or not self.finding_codes: + raise ValueError("non-exact continuous box outcome requires findings only") + expected_prefix = { + so2_interval.ContinuousYawIntervalKindV4.NUMERIC_GAP: "NUMERIC_GAP:", + so2_interval.ContinuousYawIntervalKindV4.RESOURCE_LIMIT: "RESOURCE_LIMIT:", + so2_interval.ContinuousYawIntervalKindV4.UNSUPPORTED: "UNSUPPORTED:", + }[self.kind] + if any(not code.startswith(expected_prefix) for code in self.finding_codes): + raise ValueError("continuous box finding does not match its typed outcome") + + +def compile_continuous_yaw_box_bounds_v4( + box: object, + *, + cell: object, + pivot_xy: object, + yaw_bounds: object, + atomic_budget: SO2AtomicBudgetV2, +) -> ContinuousYawBoxOutcomeV4: + """Enclose one upright box over a closed XY cell and closed lifted yaw cell.""" + + if type(box) is not FixedCardinalBoxV3: + raise TypeError("box must be a FixedCardinalBoxV3") + if type(cell) is not ClosedXYCellV3: + raise TypeError("cell must be a ClosedXYCellV3") + if ( + type(pivot_xy) is not tuple + or len(pivot_xy) != 2 + or any(type(value) is not Fraction for value in pivot_xy) + ): + raise TypeError("pivot_xy must contain two exact Fractions") + if type(yaw_bounds) is not so2_interval.ContinuousYawSinCosBoundsV4: + raise TypeError("yaw_bounds must be ContinuousYawSinCosBoundsV4") + if type(atomic_budget) is not SO2AtomicBudgetV2: + raise TypeError("atomic_budget must be an SO2AtomicBudgetV2") + atomic_budget.validate() + start_used = atomic_budget.used + try: + with warnings.catch_warnings(): + warnings.simplefilter("error", Warning) + return _compile_continuous_yaw_box_bounds_checked_v4( + box, + cell, + pivot_xy, + yaw_bounds, + atomic_budget, + start_used, + ) + except SO2AtomicBudgetExhaustedV2: + return _continuous_box_failure_v4("RESOURCE_LIMIT:SO2_ATOMIC_STEPS") + except so2_interval._SO2NumericGapV2 as error: + return _continuous_box_failure_v4(error.finding_code) + except (OverflowError, FloatingPointError): + return _continuous_box_failure_v4("NUMERIC_GAP:CONTINUOUS_YAW_BOX_ARITHMETIC") + except RuntimeWarning: + return _continuous_box_failure_v4( + "NUMERIC_GAP:CONTINUOUS_YAW_BOX_RUNTIME_WARNING" + ) + + +def _compile_continuous_yaw_box_bounds_checked_v4( + box: FixedCardinalBoxV3, + cell: ClosedXYCellV3, + pivot_xy: tuple[Fraction, Fraction], + yaw_bounds: so2_interval.ContinuousYawSinCosBoundsV4, + budget: SO2AtomicBudgetV2, + start_used: int, +) -> ContinuousYawBoxOutcomeV4: + cosine = _enclosure_tuple(yaw_bounds.cosine) + sine = _enclosure_tuple(yaw_bounds.sine) + negative_sine = _interval_negate(yaw_bounds.sine, budget) + relative_x = box.center_x - pivot_xy[0] + relative_y = box.center_y - pivot_xy[1] + for value in (*pivot_xy, relative_x, relative_y, box.center_z): + so2_interval._require_numeric_fraction_cap( + value, + "NUMERIC_GAP:CONTINUOUS_YAW_BOX_FRACTION_BIT_CAP", + ) + rotated_x = _interval_add( + _interval_scale_signed(cosine, relative_x, budget), + _interval_scale_signed(negative_sine, relative_y, budget), + budget, + ) + rotated_y = _interval_add( + _interval_scale_signed(sine, relative_x, budget), + _interval_scale_signed(cosine, relative_y, budget), + budget, + ) + center_x = _interval_add( + _interval_add(rotated_x, (pivot_xy[0], pivot_xy[0]), budget), + (cell.x_lower, cell.x_upper), + budget, + ) + center_y = _interval_add( + _interval_add(rotated_y, (pivot_xy[1], pivot_xy[1]), budget), + (cell.y_lower, cell.y_upper), + budget, + ) + absolute_cosine = _interval_absolute(yaw_bounds.cosine, budget) + absolute_sine = _interval_absolute(yaw_bounds.sine, budget) + x_radius = _interval_add( + _interval_scale(absolute_cosine, box.half_x, budget), + _interval_scale(absolute_sine, box.half_y, budget), + budget, + ) + y_radius = _interval_add( + _interval_scale(absolute_sine, box.half_x, budget), + _interval_scale(absolute_cosine, box.half_y, budget), + budget, + ) + aabb_x = _interval_add( + center_x, + (-x_radius[1], x_radius[1]), + budget, + ) + aabb_y = _interval_add( + center_y, + (-y_radius[1], y_radius[1]), + budget, + ) + aabb_z = (box.center_z - box.half_z, box.center_z + box.half_z) + for interval in (center_x, center_y, x_radius, y_radius, aabb_x, aabb_y, aabb_z): + _checked_interval(interval) + return ContinuousYawBoxOutcomeV4( + so2_interval.ContinuousYawIntervalKindV4.EXACT, + bounds=ContinuousYawBoxBoundsV4( + box=box, + cell=cell, + pivot_xy=pivot_xy, + yaw_bounds=yaw_bounds, + local_x_axis=OrientedAxisEnclosureV2( + x=yaw_bounds.cosine, + y=yaw_bounds.sine, + ), + local_y_axis=OrientedAxisEnclosureV2( + x=so2_interval._publish_rational_enclosure(*negative_sine), + y=yaw_bounds.cosine, + ), + center_x=_publish_interval(center_x, budget), + center_y=_publish_interval(center_y, budget), + center_z=box.center_z, + half_x=box.half_x, + half_y=box.half_y, + half_z=box.half_z, + x_radius=_publish_interval(x_radius, budget), + y_radius=_publish_interval(y_radius, budget), + aabb_x=_publish_interval(aabb_x, budget), + aabb_y=_publish_interval(aabb_y, budget), + aabb_z=_publish_interval(aabb_z, budget), + atomic_steps_used=budget.used - start_used, + ), + ) + + +def _continuous_box_failure_v4(finding_code: str) -> ContinuousYawBoxOutcomeV4: + if finding_code.startswith("RESOURCE_LIMIT:"): + kind = so2_interval.ContinuousYawIntervalKindV4.RESOURCE_LIMIT + elif finding_code.startswith("NUMERIC_GAP:"): + kind = so2_interval.ContinuousYawIntervalKindV4.NUMERIC_GAP + else: + raise ValueError( + "continuous box failures must be typed resource or numeric gaps" + ) + return ContinuousYawBoxOutcomeV4(kind, finding_codes=(finding_code,)) + + +class ContinuousYawBoxContactKindV4(StrEnum): + """Only whole-cell separation is certifiable from outer AABB envelopes.""" + + PROVEN_SEPARATED = "PROVEN_SEPARATED" + UNKNOWN = "UNKNOWN" + + +@dataclass(frozen=True, slots=True) +class ContinuousYawBoxPairBoundsV4: + """Canonical outer-AABB contact and clearance bounds for two yaw cells.""" + + left: ContinuousYawBoxBoundsV4 + right: ContinuousYawBoxBoundsV4 + x_gap: RationalEnclosureV2 + y_gap: RationalEnclosureV2 + z_gap: RationalEnclosureV2 + squared_clearance: RationalEnclosureV2 + contact_kind: ContinuousYawBoxContactKindV4 + atomic_steps_used: int + + def __post_init__(self) -> None: + if type(self.left) is not ContinuousYawBoxBoundsV4: + raise TypeError("left must be a ContinuousYawBoxBoundsV4") + if type(self.right) is not ContinuousYawBoxBoundsV4: + raise TypeError("right must be a ContinuousYawBoxBoundsV4") + if _continuous_box_operand_key_v4(self.left) > _continuous_box_operand_key_v4( + self.right + ): + raise ValueError("continuous box pair operands are not canonical") + for field_name in ("x_gap", "y_gap", "z_gap", "squared_clearance"): + value = getattr(self, field_name) + if type(value) is not RationalEnclosureV2: + raise TypeError(f"{field_name} must be a RationalEnclosureV2") + checked = _copy_enclosure(value) + if checked.rational_lower < 0: + raise ValueError(f"{field_name} must be non-negative") + object.__setattr__(self, field_name, checked) + expected_x = _continuous_axis_gap_no_budget_v4( + _enclosure_tuple(self.left.aabb_x), + _enclosure_tuple(self.right.aabb_x), + ) + expected_y = _continuous_axis_gap_no_budget_v4( + _enclosure_tuple(self.left.aabb_y), + _enclosure_tuple(self.right.aabb_y), + ) + expected_z = _continuous_axis_gap_no_budget_v4( + _enclosure_tuple(self.left.aabb_z), + _enclosure_tuple(self.right.aabb_z), + ) + if ( + _enclosure_tuple(self.x_gap), + _enclosure_tuple(self.y_gap), + _enclosure_tuple(self.z_gap), + ) != (expected_x, expected_y, expected_z): + raise ValueError("continuous pair gaps must match the outer AABBs") + expected_clearance = ( + expected_x[0] * expected_x[0] + + expected_y[0] * expected_y[0] + + expected_z[0] * expected_z[0], + expected_x[1] * expected_x[1] + + expected_y[1] * expected_y[1] + + expected_z[1] * expected_z[1], + ) + if _enclosure_tuple(self.squared_clearance) != expected_clearance: + raise ValueError("continuous pair clearance must match its axis gaps") + if type(self.contact_kind) is not ContinuousYawBoxContactKindV4: + raise TypeError("contact_kind must be a ContinuousYawBoxContactKindV4") + expected_kind = ( + ContinuousYawBoxContactKindV4.PROVEN_SEPARATED + if any(value[0] > 0 for value in (expected_x, expected_y, expected_z)) + else ContinuousYawBoxContactKindV4.UNKNOWN + ) + if self.contact_kind is not expected_kind: + raise ValueError("continuous pair contact classification is not sound") + if type(self.atomic_steps_used) is not int or self.atomic_steps_used <= 0: + raise ValueError( + "continuous pair atomic usage must be a positive exact int" + ) + + +@dataclass(frozen=True, slots=True) +class ContinuousYawBoxPairOutcomeV4: + """Typed completion of whole-cell outer contact and clearance evaluation.""" + + kind: so2_interval.ContinuousYawIntervalKindV4 + bounds: ContinuousYawBoxPairBoundsV4 | None = None + finding_codes: tuple[str, ...] = () + + def __post_init__(self) -> None: + if type(self.kind) is not so2_interval.ContinuousYawIntervalKindV4: + raise TypeError("kind must be a ContinuousYawIntervalKindV4") + if type(self.finding_codes) is not tuple or any( + type(code) is not str or not code.strip() for code in self.finding_codes + ): + raise ValueError("finding_codes must be non-blank strings") + object.__setattr__( + self, "finding_codes", tuple(sorted(set(self.finding_codes))) + ) + if self.kind is so2_interval.ContinuousYawIntervalKindV4.EXACT: + if ( + type(self.bounds) is not ContinuousYawBoxPairBoundsV4 + or self.finding_codes + ): + raise ValueError("exact continuous pair outcome requires bounds only") + return + if self.bounds is not None or not self.finding_codes: + raise ValueError("non-exact continuous pair outcome requires findings only") + expected_prefix = { + so2_interval.ContinuousYawIntervalKindV4.NUMERIC_GAP: "NUMERIC_GAP:", + so2_interval.ContinuousYawIntervalKindV4.RESOURCE_LIMIT: "RESOURCE_LIMIT:", + so2_interval.ContinuousYawIntervalKindV4.UNSUPPORTED: "UNSUPPORTED:", + }[self.kind] + if any(not code.startswith(expected_prefix) for code in self.finding_codes): + raise ValueError("continuous pair finding does not match its typed outcome") + + +def evaluate_continuous_yaw_box_pair_bounds_v4( + left: object, + right: object, + *, + atomic_budget: SO2AtomicBudgetV2, +) -> ContinuousYawBoxPairOutcomeV4: + """Certify only outer-AABB separation over two complete continuous pose cells.""" + + if type(left) is not ContinuousYawBoxBoundsV4: + raise TypeError("left must be a ContinuousYawBoxBoundsV4") + if type(right) is not ContinuousYawBoxBoundsV4: + raise TypeError("right must be a ContinuousYawBoxBoundsV4") + if type(atomic_budget) is not SO2AtomicBudgetV2: + raise TypeError("atomic_budget must be an SO2AtomicBudgetV2") + atomic_budget.validate() + if _continuous_box_operand_key_v4(right) < _continuous_box_operand_key_v4(left): + left, right = right, left + start_used = atomic_budget.used + try: + with warnings.catch_warnings(): + warnings.simplefilter("error", Warning) + x_gap = _continuous_axis_gap_v4( + _enclosure_tuple(left.aabb_x), + _enclosure_tuple(right.aabb_x), + atomic_budget, + ) + y_gap = _continuous_axis_gap_v4( + _enclosure_tuple(left.aabb_y), + _enclosure_tuple(right.aabb_y), + atomic_budget, + ) + z_gap = _continuous_axis_gap_v4( + _enclosure_tuple(left.aabb_z), + _enclosure_tuple(right.aabb_z), + atomic_budget, + ) + squared_clearance = ( + x_gap[0] * x_gap[0] + y_gap[0] * y_gap[0] + z_gap[0] * z_gap[0], + x_gap[1] * x_gap[1] + y_gap[1] * y_gap[1] + z_gap[1] * z_gap[1], + ) + _checked_interval(squared_clearance) + contact_kind = ( + ContinuousYawBoxContactKindV4.PROVEN_SEPARATED + if any(value[0] > 0 for value in (x_gap, y_gap, z_gap)) + else ContinuousYawBoxContactKindV4.UNKNOWN + ) + return ContinuousYawBoxPairOutcomeV4( + so2_interval.ContinuousYawIntervalKindV4.EXACT, + bounds=ContinuousYawBoxPairBoundsV4( + left=left, + right=right, + x_gap=_publish_interval(x_gap, atomic_budget), + y_gap=_publish_interval(y_gap, atomic_budget), + z_gap=_publish_interval(z_gap, atomic_budget), + squared_clearance=_publish_interval( + squared_clearance, + atomic_budget, + ), + contact_kind=contact_kind, + atomic_steps_used=atomic_budget.used - start_used, + ), + ) + except SO2AtomicBudgetExhaustedV2: + return _continuous_pair_failure_v4("RESOURCE_LIMIT:SO2_ATOMIC_STEPS") + except so2_interval._SO2NumericGapV2 as error: + return _continuous_pair_failure_v4(error.finding_code) + except (OverflowError, FloatingPointError): + return _continuous_pair_failure_v4("NUMERIC_GAP:CONTINUOUS_YAW_PAIR_ARITHMETIC") + except RuntimeWarning: + return _continuous_pair_failure_v4( + "NUMERIC_GAP:CONTINUOUS_YAW_PAIR_RUNTIME_WARNING" + ) + + +def _continuous_box_operand_key_v4( + value: ContinuousYawBoxBoundsV4, +) -> tuple[object, ...]: + return ( + value.box.box_id, + value.box.center_x, + value.box.center_y, + value.box.center_z, + value.box.half_x, + value.box.half_y, + value.box.half_z, + value.cell.canonical_bounds, + value.pivot_xy, + value.yaw_bounds.lift_bounds.lower, + value.yaw_bounds.lift_bounds.upper, + value.yaw_bounds.lift_bounds.coverage_sha256, + ) + + +def _continuous_axis_gap_no_budget_v4( + left: tuple[Fraction, Fraction], + right: tuple[Fraction, Fraction], +) -> tuple[Fraction, Fraction]: + return _checked_interval( + ( + max(Fraction(), right[0] - left[1], left[0] - right[1]), + max(Fraction(), right[1] - left[0], left[1] - right[0]), + ) + ) + + +def _continuous_axis_gap_v4( + left: tuple[Fraction, Fraction], + right: tuple[Fraction, Fraction], + budget: SO2AtomicBudgetV2, +) -> tuple[Fraction, Fraction]: + budget.consume() + return _continuous_axis_gap_no_budget_v4(left, right) + + +def _continuous_pair_failure_v4(finding_code: str) -> ContinuousYawBoxPairOutcomeV4: + if finding_code.startswith("RESOURCE_LIMIT:"): + kind = so2_interval.ContinuousYawIntervalKindV4.RESOURCE_LIMIT + elif finding_code.startswith("NUMERIC_GAP:"): + kind = so2_interval.ContinuousYawIntervalKindV4.NUMERIC_GAP + else: + raise ValueError( + "continuous pair failures must be typed resource or numeric gaps" + ) + return ContinuousYawBoxPairOutcomeV4(kind, finding_codes=(finding_code,)) + + +@dataclass(frozen=True, slots=True) +class ContinuousYawCellPolicyV4: + """One request-bound policy bundle for a continuous compound pose cell. + + The retained V3 record remains the sole owner of the request's semantic + thresholds, safety rule, objective weights, and resource limit. This + versioned wrapper deliberately adds no ambient continuous defaults. + """ + + cardinal_policy: FixedCardinalCellPolicyV3 + + def __post_init__(self) -> None: + if type(self.cardinal_policy) is not FixedCardinalCellPolicyV3: + raise TypeError("cardinal_policy must be a FixedCardinalCellPolicyV3") + + +@dataclass(frozen=True, slots=True) +class ContinuousYawCellBoundsV4: + """Directed semantic and objective bounds over one continuous compound cell.""" + + cell_id: str + cell: ClosedXYCellV3 + yaw_bounds: so2_interval.ContinuousYawSinCosBoundsV4 + subject_box_ids: tuple[str, ...] + obstacle_box_ids: tuple[str, ...] + collision_constraint_present: bool + collision_inner_slack: Fraction + collision_outer_slack: Fraction + collision_inward_proven: bool + collision_outer_failure: bool + support_contact_gap: tuple[Fraction, Fraction] + support_clearance_inner: Fraction + support_clearance_outer: Fraction + support_normal: tuple[Fraction, Fraction, Fraction] + support_frame: str + closed_containment: bool + stability_margin_inner: Fraction + relation_symbol: str + relation_measurement: str + relation_inner_slack: Fraction + relation_outer_slack: Fraction + relation_comparator: str + relation_threshold: Fraction + relation_tolerance: Fraction + relation_boundary: str + relation_squared_distance_bounds: tuple[Fraction, Fraction] | None + relation_inner_success: bool + relation_outer_failure: bool + visibility_inner_fraction: Fraction + visibility_outer_fraction: Fraction + visibility_classification: str + semantic_objective_terms: tuple[tuple[str, Fraction, Fraction], ...] + weighted_objective_terms: tuple[tuple[str, Fraction, Fraction], ...] + inner_hard_constraint_slack: Fraction + outer_hard_constraint_slack: Fraction + inner_hard_constraint_proven: bool + outer_hard_constraint_failure: bool + safety_penalty_outer: Fraction + + def __post_init__(self) -> None: + if ( + type(self.cell_id) is not str + or not self.cell_id + or type(self.cell) is not ClosedXYCellV3 + or type(self.yaw_bounds) is not so2_interval.ContinuousYawSinCosBoundsV4 + or type(self.subject_box_ids) is not tuple + or not self.subject_box_ids + or any( + type(value) is not str or not value for value in self.subject_box_ids + ) + or self.subject_box_ids != tuple(sorted(set(self.subject_box_ids))) + or type(self.obstacle_box_ids) is not tuple + or any( + type(value) is not str or not value for value in self.obstacle_box_ids + ) + or self.obstacle_box_ids != tuple(sorted(set(self.obstacle_box_ids))) + or type(self.collision_constraint_present) is not bool + or type(self.collision_inward_proven) is not bool + or type(self.collision_outer_failure) is not bool + or type(self.closed_containment) is not bool + or type(self.relation_inner_success) is not bool + or type(self.relation_outer_failure) is not bool + or type(self.inner_hard_constraint_proven) is not bool + or type(self.outer_hard_constraint_failure) is not bool + or self.support_normal != (Fraction(), Fraction(), Fraction(1)) + or self.support_frame != "WORLD_XY_Z_UP" + or self.relation_symbol + not in ("LEFT", "RIGHT", "FRONT", "BEHIND", "NEAR", "FAR") + or self.relation_measurement + not in ( + "EXTENT_AWARE_SIGNED_AXIS_GAP", + "EXTENT_AWARE_EUCLIDEAN_SEPARATION", + ) + or self.relation_comparator not in ("LE", "GE") + or self.relation_boundary != "CLOSED" + or self.visibility_classification not in ("INWARD", "OUTWARD", "UNKNOWN") + ): + raise ValueError("continuous yaw cell bounds are not a closed exact record") + for value in ( + self.collision_inner_slack, + self.collision_outer_slack, + self.support_clearance_inner, + self.support_clearance_outer, + self.stability_margin_inner, + self.relation_inner_slack, + self.relation_outer_slack, + self.relation_threshold, + self.relation_tolerance, + self.visibility_inner_fraction, + self.visibility_outer_fraction, + self.inner_hard_constraint_slack, + self.outer_hard_constraint_slack, + self.safety_penalty_outer, + ): + if type(value) is not Fraction: + raise TypeError( + "continuous yaw semantic bounds must be exact Fractions" + ) + if ( + self.collision_inner_slack > self.collision_outer_slack + or self.support_clearance_inner > self.support_clearance_outer + or self.relation_inner_slack > self.relation_outer_slack + or self.inner_hard_constraint_slack > self.outer_hard_constraint_slack + or self.relation_tolerance < 0 + or not Fraction() + <= self.visibility_inner_fraction + <= self.visibility_outer_fraction + <= Fraction(1) + or self.safety_penalty_outer < 0 + ): + raise ValueError("continuous yaw semantic intervals must be ordered") + if ( + type(self.support_contact_gap) is not tuple + or len(self.support_contact_gap) != 2 + or any(type(value) is not Fraction for value in self.support_contact_gap) + or self.support_contact_gap[0] > self.support_contact_gap[1] + ): + raise ValueError("support contact gap must be an ordered exact interval") + if self.relation_squared_distance_bounds is not None and ( + type(self.relation_squared_distance_bounds) is not tuple + or len(self.relation_squared_distance_bounds) != 2 + or any( + type(value) is not Fraction + for value in self.relation_squared_distance_bounds + ) + or self.relation_squared_distance_bounds[0] + > self.relation_squared_distance_bounds[1] + ): + raise ValueError("squared distance bounds must be ordered exact Fractions") + for terms in (self.semantic_objective_terms, self.weighted_objective_terms): + if ( + type(terms) is not tuple + or tuple(term_id for term_id, _, _ in terms) + != ("T", "A", "R", "V", "S") + or any( + type(value) is not Fraction + for _, lower, upper in terms + for value in (lower, upper) + ) + or any(lower > upper for _, lower, upper in terms) + ): + raise ValueError("objective terms must be ordered T/A/R/V/S intervals") + + +@dataclass(frozen=True, slots=True) +class ContinuousYawCellOutcomeV4: + """Typed completion of continuous compound semantic ownership.""" + + kind: so2_interval.ContinuousYawIntervalKindV4 + bounds: ContinuousYawCellBoundsV4 | None = None + atomic_steps_used: int = 0 + proof_rows: tuple[str, ...] = () + finding_codes: tuple[str, ...] = () + + def __post_init__(self) -> None: + if type(self.kind) is not so2_interval.ContinuousYawIntervalKindV4: + raise TypeError("continuous yaw cell outcome must use its V4 kind") + if type(self.atomic_steps_used) is not int or self.atomic_steps_used < 0: + raise ValueError("continuous yaw cell atomic usage must be non-negative") + for field_name in ("proof_rows", "finding_codes"): + values = getattr(self, field_name) + if type(values) is not tuple or any( + type(value) is not str or not value.strip() for value in values + ): + raise ValueError(f"continuous yaw cell {field_name} must be non-blank") + object.__setattr__(self, field_name, tuple(sorted(set(values)))) + if self.kind is so2_interval.ContinuousYawIntervalKindV4.EXACT: + if ( + type(self.bounds) is not ContinuousYawCellBoundsV4 + or self.finding_codes + or not self.proof_rows + ): + raise ValueError( + "exact continuous yaw cell outcome requires bounds and proof" + ) + return + if self.bounds is not None or not self.finding_codes or not self.proof_rows: + raise ValueError( + "non-exact continuous yaw cell outcome requires typed proof" + ) + prefix = { + so2_interval.ContinuousYawIntervalKindV4.NUMERIC_GAP: "NUMERIC_GAP:", + so2_interval.ContinuousYawIntervalKindV4.RESOURCE_LIMIT: "RESOURCE_LIMIT:", + so2_interval.ContinuousYawIntervalKindV4.UNSUPPORTED: "UNSUPPORTED:", + }[self.kind] + if any(not code.startswith(prefix) for code in self.finding_codes): + raise ValueError("continuous yaw cell finding must match its typed outcome") + + +_ContinuousExtentIntervalsV4 = tuple[ + tuple[Fraction, Fraction], + tuple[Fraction, Fraction], + tuple[Fraction, Fraction], + tuple[Fraction, Fraction], + tuple[Fraction, Fraction], + tuple[Fraction, Fraction], +] + + +def evaluate_continuous_yaw_cell_v4( + *, + cell: ClosedXYCellV3, + yaw_bounds: so2_interval.ContinuousYawSinCosBoundsV4, + subject_boxes: tuple[ContinuousYawBoxBoundsV4, ...], + obstacle_boxes: tuple[ContinuousYawBoxBoundsV4, ...], + support_surface: SupportSurfaceV3, + relation: str, + reference_box: ContinuousYawBoxBoundsV4, + near_far_threshold: Fraction, + policy: ContinuousYawCellPolicyV4, + visibility: object, + atomic_budget: SO2AtomicBudgetV2, + subject_pivot_xy: tuple[Fraction, Fraction] = (Fraction(), Fraction()), + objective_subject_pivot_xy: tuple[Fraction, Fraction] | None = None, +) -> ContinuousYawCellOutcomeV4: + """Evaluate one closed continuous pose cell without reimplementing sibling owners. + + Inputs are already-produced Task 6 box and visibility DTOs. This owner + combines their directed intervals; it neither samples yaw nor constructs a + camera projection. A nonpoint contact envelope can prove separation or + failure, but remains explicitly unresolved at touch/overlap ambiguity. + """ + + if type(cell) is not ClosedXYCellV3: + raise TypeError("cell must be a ClosedXYCellV3") + if type(yaw_bounds) is not so2_interval.ContinuousYawSinCosBoundsV4: + raise TypeError("yaw_bounds must be ContinuousYawSinCosBoundsV4") + subjects = _require_continuous_cell_roster_v4( + subject_boxes, + label="subject_boxes", + nonempty=True, + ) + obstacles = _require_continuous_cell_roster_v4( + obstacle_boxes, + label="obstacle_boxes", + nonempty=False, + ) + if type(support_surface) is not SupportSurfaceV3: + raise TypeError("support_surface must be a SupportSurfaceV3") + if relation not in ("LEFT", "RIGHT", "FRONT", "BEHIND", "NEAR", "FAR"): + raise ValueError("relation must be a registered continuous yaw relation") + if type(reference_box) is not ContinuousYawBoxBoundsV4: + raise TypeError("reference_box must be a ContinuousYawBoxBoundsV4") + if type(near_far_threshold) is not Fraction or near_far_threshold < 0: + raise ValueError("near_far_threshold must be a non-negative exact Fraction") + if type(policy) is not ContinuousYawCellPolicyV4: + raise TypeError("policy must be a ContinuousYawCellPolicyV4") + cardinal_policy = policy.cardinal_policy + if near_far_threshold != cardinal_policy.relation_threshold: + raise ValueError("near_far_threshold must match the caller policy") + if cardinal_policy.relation_symbol != relation: + raise ValueError("relation must match the caller-bound registered symbol") + expected_measurement = ( + "EXTENT_AWARE_EUCLIDEAN_SEPARATION" + if relation in ("NEAR", "FAR") + else "EXTENT_AWARE_SIGNED_AXIS_GAP" + ) + if cardinal_policy.relation_measurement != expected_measurement: + raise ValueError("relation measurement must match its registered symbol") + if cardinal_policy.visibility_cell != cell.canonical_bounds: + raise ValueError("visibility policy must be bound to this exact XY cell") + if ( + type(subject_pivot_xy) is not tuple + or len(subject_pivot_xy) != 2 + or any(type(value) is not Fraction for value in subject_pivot_xy) + ): + raise TypeError("subject_pivot_xy must contain two exact Fractions") + if objective_subject_pivot_xy is None: + objective_subject_pivot_xy = subject_pivot_xy + if ( + type(objective_subject_pivot_xy) is not tuple + or len(objective_subject_pivot_xy) != 2 + or any(type(value) is not Fraction for value in objective_subject_pivot_xy) + ): + raise TypeError("objective_subject_pivot_xy must contain two exact Fractions") + if type(atomic_budget) is not SO2AtomicBudgetV2: + raise TypeError("atomic_budget must be an SO2AtomicBudgetV2") + atomic_budget.validate() + if atomic_budget.limit != cardinal_policy.atomic_step_limit: + raise ValueError("atomic budget limit must match the caller policy") + _validate_continuous_cell_geometry_v4( + cell=cell, + yaw_bounds=yaw_bounds, + subjects=subjects, + obstacles=obstacles, + reference=reference_box, + subject_pivot_xy=subject_pivot_xy, + ) + if not _is_continuous_visibility_outcome_v4(visibility): + raise TypeError("visibility must be a ContinuousYawVisibilityOutcomeV4") + visibility_kind = visibility.kind # type: ignore[union-attr] + if visibility_kind is not so2_interval.ContinuousYawIntervalKindV4.EXACT: + return _continuous_cell_from_visibility_nonexact_v4(visibility) + if not _is_continuous_visibility_bounds_v4(visibility.bounds): # type: ignore[union-attr] + raise RuntimeError("exact continuous visibility outcome is missing bounds") + _validate_continuous_cell_visibility_v4( + visibility=visibility.bounds, # type: ignore[union-attr] + cell=cell, + yaw_bounds=yaw_bounds, + subject=subjects[0], + ) + point_quarter = _continuous_cell_cardinal_point_quarter_v4(cell, yaw_bounds) + if point_quarter is not None: + return _delegate_continuous_cell_cardinal_point_v4( + cell=cell, + quarter_turns_ccw=point_quarter, + subjects=subjects, + obstacles=obstacles, + support_surface=support_surface, + relation=relation, + reference=reference_box, + near_far_threshold=near_far_threshold, + policy=cardinal_policy, + yaw_bounds=yaw_bounds, + visibility=visibility.bounds, # type: ignore[union-attr] + atomic_budget=atomic_budget, + subject_pivot_xy=subject_pivot_xy, + objective_subject_pivot_xy=objective_subject_pivot_xy, + ) + + pair_calls = len(subjects) * len(obstacles) + if relation in ("NEAR", "FAR"): + pair_calls += len(subjects) + # Pair-owner evaluation consumes seven retained atomic steps per pair; + # translation/geodesic interval assembly consumes the remaining fourteen. + required_steps = 14 + 7 * pair_calls + if atomic_budget.remaining < required_steps: + return _continuous_cell_failure_v4( + so2_interval.ContinuousYawIntervalKindV4.RESOURCE_LIMIT, + "RESOURCE_LIMIT:SO2_ATOMIC_STEPS", + atomic_steps_used=0, + proof_rows=("RESOURCE:SO2_ATOMIC_STEPS:cap-minus-one",), + ) + start_used = atomic_budget.used + try: + collision_pairs = tuple( + evaluate_continuous_yaw_box_pair_bounds_v4( + subject, + obstacle, + atomic_budget=atomic_budget, + ) + for subject in subjects + for obstacle in obstacles + ) + if any( + pair.kind is not so2_interval.ContinuousYawIntervalKindV4.EXACT + for pair in collision_pairs + ): + return _continuous_cell_from_pair_nonexact_v4( + collision_pairs, start_used, atomic_budget + ) + relation_pairs = ( + tuple( + evaluate_continuous_yaw_box_pair_bounds_v4( + subject, + reference_box, + atomic_budget=atomic_budget, + ) + for subject in subjects + ) + if relation in ("NEAR", "FAR") + else () + ) + if any( + pair.kind is not so2_interval.ContinuousYawIntervalKindV4.EXACT + for pair in relation_pairs + ): + return _continuous_cell_from_pair_nonexact_v4( + relation_pairs, start_used, atomic_budget + ) + return _evaluate_continuous_yaw_cell_checked_v4( + cell=cell, + yaw_bounds=yaw_bounds, + subjects=subjects, + obstacles=obstacles, + support_surface=support_surface, + relation=relation, + reference=reference_box, + policy=cardinal_policy, + visibility=visibility.bounds, # type: ignore[union-attr] + collision_pairs=collision_pairs, + relation_pairs=relation_pairs, + atomic_budget=atomic_budget, + start_used=start_used, + required_steps=required_steps, + subject_pivot_xy=subject_pivot_xy, + objective_subject_pivot_xy=objective_subject_pivot_xy, + ) + except SO2AtomicBudgetExhaustedV2: + return _continuous_cell_failure_v4( + so2_interval.ContinuousYawIntervalKindV4.RESOURCE_LIMIT, + "RESOURCE_LIMIT:SO2_ATOMIC_STEPS", + atomic_steps_used=atomic_budget.used - start_used, + proof_rows=("RESOURCE:SO2_ATOMIC_STEPS:continuous-compound-owner",), + ) + except so2_interval._SO2NumericGapV2 as error: + return _continuous_cell_failure_v4( + so2_interval.ContinuousYawIntervalKindV4.NUMERIC_GAP, + error.finding_code, + atomic_steps_used=atomic_budget.used - start_used, + proof_rows=(error.finding_code,), + ) + except (OverflowError, FloatingPointError): + return _continuous_cell_failure_v4( + so2_interval.ContinuousYawIntervalKindV4.NUMERIC_GAP, + "NUMERIC_GAP:CONTINUOUS_YAW_CELL_ARITHMETIC", + atomic_steps_used=atomic_budget.used - start_used, + proof_rows=("NUMERIC_GAP:CONTINUOUS_YAW_CELL_ARITHMETIC",), + ) + except RuntimeWarning: + return _continuous_cell_failure_v4( + so2_interval.ContinuousYawIntervalKindV4.NUMERIC_GAP, + "NUMERIC_GAP:CONTINUOUS_YAW_CELL_RUNTIME_WARNING", + atomic_steps_used=atomic_budget.used - start_used, + proof_rows=("NUMERIC_GAP:CONTINUOUS_YAW_CELL_RUNTIME_WARNING",), + ) + + +def _require_continuous_cell_roster_v4( + value: object, *, label: str, nonempty: bool +) -> tuple[ContinuousYawBoxBoundsV4, ...]: + if type(value) is not tuple or (nonempty and not value): + raise ValueError( + f"{label} must be an exact {'non-empty ' if nonempty else ''}tuple" + ) + boxes = tuple( + item + if type(item) is ContinuousYawBoxBoundsV4 + else (_raise_continuous_cell_box_type_v4(label)) + for item in value + ) + ids = tuple(box.box.box_id for box in boxes) + if ids != tuple(sorted(ids)) or len(set(ids)) != len(ids): + raise ValueError(f"{label} must be sorted and have unique box IDs") + return boxes + + +def _raise_continuous_cell_box_type_v4(label: str) -> ContinuousYawBoxBoundsV4: + raise TypeError(f"{label} must contain ContinuousYawBoxBoundsV4 values") + + +def _validate_continuous_cell_geometry_v4( + *, + cell: ClosedXYCellV3, + yaw_bounds: so2_interval.ContinuousYawSinCosBoundsV4, + subjects: tuple[ContinuousYawBoxBoundsV4, ...], + obstacles: tuple[ContinuousYawBoxBoundsV4, ...], + reference: ContinuousYawBoxBoundsV4, + subject_pivot_xy: tuple[Fraction, Fraction], +) -> None: + for subject in subjects: + if ( + subject.cell != cell + or subject.yaw_bounds != yaw_bounds + or subject.pivot_xy != subject_pivot_xy + ): + raise ValueError( + "subject boxes must bind the exact continuous cell and pivot" + ) + for box in obstacles: + if not _is_static_world_box_v4(box): + raise ValueError("obstacle boxes must be static world-frame point boxes") + if not _is_static_world_box_v4(reference): + raise ValueError("reference boxes must be static world-frame point boxes") + + +def _is_static_world_box_v4(value: ContinuousYawBoxBoundsV4) -> bool: + lift = value.yaw_bounds.lift_bounds + return ( + value.cell.canonical_bounds == (Fraction(), Fraction(), Fraction(), Fraction()) + and value.pivot_xy == (Fraction(), Fraction()) + and lift.lower == lift.upper == Fraction() + ) + + +def _continuous_visibility_dto_types_v4() -> tuple[type[object], type[object]]: + """Load real sibling DTO identities only after this owner has initialized. + + ``projected_visibility`` imports this owner during its own module setup, so + a module-level import would recreate that cycle. Evaluation happens after + both kernel modules are usable; this lazy import preserves the ownership + boundary while rejecting module/name lookalikes. + """ + + from spatialcf.core._internal.kernels.projected_visibility import ( + ContinuousYawVisibilityBoundsV4, + ContinuousYawVisibilityOutcomeV4, + ) + + return ContinuousYawVisibilityOutcomeV4, ContinuousYawVisibilityBoundsV4 + + +def _is_continuous_visibility_outcome_v4(value: object) -> bool: + outcome_type, _ = _continuous_visibility_dto_types_v4() + return type(value) is outcome_type + + +def _is_continuous_visibility_bounds_v4(value: object) -> bool: + _, bounds_type = _continuous_visibility_dto_types_v4() + return type(value) is bounds_type + + +def _validate_continuous_cell_visibility_v4( + *, + visibility: object, + cell: ClosedXYCellV3, + yaw_bounds: so2_interval.ContinuousYawSinCosBoundsV4, + subject: ContinuousYawBoxBoundsV4, +) -> None: + lift = yaw_bounds.lift_bounds + if ( + visibility.cell != cell.canonical_bounds + or visibility.lifted_turn_bounds != (lift.lower, lift.upper) + or visibility.continuous_yaw_lift_sha256 != lift.coverage_sha256 + or visibility.subject_box_id != subject.box.box_id + or visibility.subject_box_id not in visibility.occluder_roster + ): + raise ValueError("visibility interval must bind the exact continuous pose cell") + + +def _continuous_cell_cardinal_point_quarter_v4( + cell: ClosedXYCellV3, + yaw_bounds: so2_interval.ContinuousYawSinCosBoundsV4, +) -> int | None: + lift = yaw_bounds.lift_bounds + if ( + cell.x_lower != cell.x_upper + or cell.y_lower != cell.y_upper + or lift.lower != lift.upper + ): + return None + return so2_interval._cardinal_quarter_turn_v4(lift.lower) + + +def _delegate_continuous_cell_cardinal_point_v4( + *, + cell: ClosedXYCellV3, + quarter_turns_ccw: int, + subjects: tuple[ContinuousYawBoxBoundsV4, ...], + obstacles: tuple[ContinuousYawBoxBoundsV4, ...], + support_surface: SupportSurfaceV3, + relation: str, + reference: ContinuousYawBoxBoundsV4, + near_far_threshold: Fraction, + policy: FixedCardinalCellPolicyV3, + yaw_bounds: so2_interval.ContinuousYawSinCosBoundsV4, + visibility: object, + atomic_budget: SO2AtomicBudgetV2, + subject_pivot_xy: tuple[Fraction, Fraction], + objective_subject_pivot_xy: tuple[Fraction, Fraction], +) -> ContinuousYawCellOutcomeV4: + retained = evaluate_fixed_cardinal_cell_v3( + cell=cell, + quarter_turns_ccw=quarter_turns_ccw, + subject_boxes=tuple(value.box for value in subjects), + obstacle_boxes=tuple(value.box for value in obstacles), + support_surface=support_surface, + relation=relation, + reference_box=reference.box, + near_far_threshold=near_far_threshold, + policy=policy, + atomic_budget=atomic_budget, + subject_pivot_xy=subject_pivot_xy, + objective_subject_pivot_xy=objective_subject_pivot_xy, + ) + kind = so2_interval.ContinuousYawIntervalKindV4(retained.kind.value) + if kind is not so2_interval.ContinuousYawIntervalKindV4.EXACT: + return ContinuousYawCellOutcomeV4( + kind, + atomic_steps_used=retained.atomic_steps_used, + proof_rows=retained.proof_rows, + finding_codes=retained.finding_codes, + ) + if type(retained.bounds) is not FixedCardinalCellBoundsV3: + raise RuntimeError("exact retained cardinal outcome is missing bounds") + bounds = retained.bounds + collision_inward = ( + not bounds.collision_constraint_present or bounds.collision_inner_slack >= 0 + ) + return ContinuousYawCellOutcomeV4( + kind, + bounds=ContinuousYawCellBoundsV4( + cell_id=bounds.cell_id, + cell=cell, + yaw_bounds=yaw_bounds, + subject_box_ids=tuple(value.box.box_id for value in subjects), + obstacle_box_ids=tuple(value.box.box_id for value in obstacles), + collision_constraint_present=bounds.collision_constraint_present, + collision_inner_slack=bounds.collision_inner_slack, + collision_outer_slack=bounds.collision_outer_slack, + collision_inward_proven=collision_inward, + collision_outer_failure=bounds.collision_outer_slack < 0, + support_contact_gap=bounds.support_contact_gap, + support_clearance_inner=bounds.support_clearance_inner, + support_clearance_outer=bounds.support_clearance_outer, + support_normal=bounds.support_normal, + support_frame=bounds.support_frame, + closed_containment=bounds.closed_containment, + stability_margin_inner=bounds.stability_margin_inner, + relation_symbol=relation, + relation_measurement=bounds.relation_measurement, + relation_inner_slack=bounds.relation_inner_slack, + relation_outer_slack=bounds.relation_outer_slack, + relation_comparator=bounds.relation_comparator, + relation_threshold=bounds.relation_threshold, + relation_tolerance=bounds.relation_tolerance, + relation_boundary=bounds.relation_boundary, + relation_squared_distance_bounds=bounds.relation_squared_distance_bounds, + relation_inner_success=bounds.relation_inner_success, + relation_outer_failure=bounds.relation_outer_failure, + visibility_inner_fraction=visibility.inner_fraction, + visibility_outer_fraction=visibility.outer_fraction, + visibility_classification=visibility.classification.value, + semantic_objective_terms=bounds.common_cell_semantic_objective_terms, + weighted_objective_terms=bounds.common_cell_objective_terms, + inner_hard_constraint_slack=bounds.inner_hard_constraint_slack, + outer_hard_constraint_slack=bounds.outer_hard_constraint_slack, + inner_hard_constraint_proven=( + collision_inward + and bounds.support_clearance_inner >= 0 + and bounds.relation_inner_success + ), + outer_hard_constraint_failure=bounds.outer_hard_constraint_slack < 0, + safety_penalty_outer=bounds.safety_penalty_outer, + ), + atomic_steps_used=retained.atomic_steps_used, + proof_rows=tuple(sorted((*retained.proof_rows, *visibility.proof_rows))), + ) + + +def _evaluate_continuous_yaw_cell_checked_v4( + *, + cell: ClosedXYCellV3, + yaw_bounds: so2_interval.ContinuousYawSinCosBoundsV4, + subjects: tuple[ContinuousYawBoxBoundsV4, ...], + obstacles: tuple[ContinuousYawBoxBoundsV4, ...], + support_surface: SupportSurfaceV3, + relation: str, + reference: ContinuousYawBoxBoundsV4, + policy: FixedCardinalCellPolicyV3, + visibility: object, + collision_pairs: tuple[ContinuousYawBoxPairOutcomeV4, ...], + relation_pairs: tuple[ContinuousYawBoxPairOutcomeV4, ...], + atomic_budget: SO2AtomicBudgetV2, + start_used: int, + required_steps: int, + subject_pivot_xy: tuple[Fraction, Fraction], + objective_subject_pivot_xy: tuple[Fraction, Fraction], +) -> ContinuousYawCellOutcomeV4: + subject_extent = _compound_continuous_extent_intervals_v4(subjects) + reference_extent = _continuous_extent_intervals_v4(reference) + if collision_pairs: + pair_bounds = tuple( + pair.bounds for pair in collision_pairs if pair.bounds is not None + ) + signed = tuple( + _continuous_signed_pair_clearance_v4(left, right) + for left in subjects + for right in obstacles + ) + collision_inner = min(value[0] for value in signed) - policy.collision_clearance + collision_outer = min(value[1] for value in signed) - policy.collision_clearance + collision_inward = ( + all( + pair.contact_kind is ContinuousYawBoxContactKindV4.PROVEN_SEPARATED + for pair in pair_bounds + ) + and collision_inner >= 0 + ) + collision_outer_failure = collision_outer < 0 + collision_row = ( + "COLLISION:CONTINUOUS_COMPOUND_PROVEN_SEPARATED" + if collision_inward + else "COLLISION:NONPOINT_CONTACT_UNRESOLVED" + ) + else: + collision_inner = Fraction() + collision_outer = Fraction() + collision_inward = True + collision_outer_failure = False + collision_row = "COLLISION:UNCONSTRAINED:NO_OBSTACLES" + containment_inner, containment_outer = _continuous_support_containment_v4( + subject_extent, + support_surface, + ) + contact_gap = ( + min(value.aabb_z.rational_lower - support_surface.z for value in subjects), + max(value.aabb_z.rational_upper - support_surface.z for value in subjects), + ) + accepted_lower, accepted_upper = policy.support_accepted_contact_gap + contact_inner = min( + contact_gap[0] - accepted_lower, + accepted_upper - contact_gap[1], + ) + contact_outer = min( + contact_gap[1] - accepted_lower, + accepted_upper - contact_gap[0], + ) + support_inner = min( + containment_inner - policy.support_stability_margin, + contact_inner, + ) + support_outer = min( + containment_outer - policy.support_stability_margin, + contact_outer, + ) + ( + relation_inner, + relation_outer, + relation_measurement, + relation_squared, + ) = _continuous_relation_slacks_v4( + relation=relation, + subject_extent=subject_extent, + reference_extent=reference_extent, + pair_outcomes=relation_pairs, + policy=policy, + ) + relation_inner_success = relation_inner >= 0 + relation_outer_failure = relation_outer < 0 + hard_parts_inner = (support_inner, relation_inner) + hard_parts_outer = (support_outer, relation_outer) + if collision_pairs: + hard_parts_inner = (collision_inner, *hard_parts_inner) + hard_parts_outer = (collision_outer, *hard_parts_outer) + hard_inner = min(hard_parts_inner) + hard_outer = min(hard_parts_outer) + translation = _continuous_translation_objective_v4( + cell=cell, + yaw_bounds=yaw_bounds, + subject_pivot_xy=subject_pivot_xy, + objective_subject_pivot_xy=objective_subject_pivot_xy, + atomic_budget=atomic_budget, + ) + atomic_budget.consume() + angle = _continuous_shortest_turn_interval_v4( + yaw_bounds.lift_bounds.lower, yaw_bounds.lift_bounds.upper + ) + relation_damage = ( + max(Fraction(), -relation_outer), + max(Fraction(), -relation_inner), + ) + safety = ( + max(Fraction(), -hard_outer), + policy.safety_penalty_scale + * max(Fraction(), policy.safety_constraint_slack_target - hard_inner), + ) + semantic_terms = ( + ("T", *translation), + ("A", *angle), + ("R", *relation_damage), + ("V", visibility.inner_fraction, visibility.outer_fraction), + ("S", *safety), + ) + weighted_terms = tuple( + ( + term_id, + lower * term.weight / term.normalizer, + upper * term.weight / term.normalizer, + ) + for (term_id, lower, upper), term in zip( + semantic_terms, + policy.objective_terms, + strict=True, + ) + ) + remaining = required_steps - (atomic_budget.used - start_used) + if remaining < 0: + raise RuntimeError("continuous yaw cell atomic preflight undercounted work") + atomic_budget.consume(remaining) + proof_rows = tuple( + sorted( + { + f"CELL:{_continuous_cell_id_v4(cell, yaw_bounds)}", + "SO2:CONTINUOUS_YAW:DELEGATED", + collision_row, + "SUPPORT:WORLD_XY_Z_UP:CONTINUOUS_ENVELOPE", + f"RELATION:{relation}:{relation_measurement}:{policy.relation_comparator}", + f"POLICY:{policy.policy_id}:{policy.policy_version}", + "OBJECTIVE:CONTINUOUS_CELL:T-A-R-V-S", + "SAFETY:HARD_CONSTRAINT_INNER_OUTER", + *visibility.proof_rows, + } + ) + ) + return ContinuousYawCellOutcomeV4( + so2_interval.ContinuousYawIntervalKindV4.EXACT, + bounds=ContinuousYawCellBoundsV4( + cell_id=_continuous_cell_id_v4(cell, yaw_bounds), + cell=cell, + yaw_bounds=yaw_bounds, + subject_box_ids=tuple(value.box.box_id for value in subjects), + obstacle_box_ids=tuple(value.box.box_id for value in obstacles), + collision_constraint_present=bool(collision_pairs), + collision_inner_slack=collision_inner, + collision_outer_slack=collision_outer, + collision_inward_proven=collision_inward, + collision_outer_failure=collision_outer_failure, + support_contact_gap=contact_gap, + support_clearance_inner=support_inner, + support_clearance_outer=support_outer, + support_normal=(Fraction(), Fraction(), Fraction(1)), + support_frame="WORLD_XY_Z_UP", + closed_containment=True, + stability_margin_inner=containment_inner - policy.support_stability_margin, + relation_symbol=relation, + relation_measurement=relation_measurement, + relation_inner_slack=relation_inner, + relation_outer_slack=relation_outer, + relation_comparator=policy.relation_comparator, + relation_threshold=policy.relation_threshold, + relation_tolerance=policy.relation_tolerance, + relation_boundary=policy.relation_boundary, + relation_squared_distance_bounds=relation_squared, + relation_inner_success=relation_inner_success, + relation_outer_failure=relation_outer_failure, + visibility_inner_fraction=visibility.inner_fraction, + visibility_outer_fraction=visibility.outer_fraction, + visibility_classification=visibility.classification.value, + semantic_objective_terms=semantic_terms, + weighted_objective_terms=weighted_terms, + inner_hard_constraint_slack=hard_inner, + outer_hard_constraint_slack=hard_outer, + inner_hard_constraint_proven=( + collision_inward and support_inner >= 0 and relation_inner_success + ), + outer_hard_constraint_failure=( + collision_outer_failure or support_outer < 0 or relation_outer_failure + ), + safety_penalty_outer=safety[1], + ), + atomic_steps_used=atomic_budget.used - start_used, + proof_rows=proof_rows, + ) + + +def _continuous_extent_intervals_v4( + value: ContinuousYawBoxBoundsV4, +) -> _ContinuousExtentIntervalsV4: + x_min = ( + value.center_x.rational_lower - value.x_radius.rational_upper, + value.center_x.rational_upper - value.x_radius.rational_lower, + ) + x_max = ( + value.center_x.rational_lower + value.x_radius.rational_lower, + value.center_x.rational_upper + value.x_radius.rational_upper, + ) + y_min = ( + value.center_y.rational_lower - value.y_radius.rational_upper, + value.center_y.rational_upper - value.y_radius.rational_lower, + ) + y_max = ( + value.center_y.rational_lower + value.y_radius.rational_lower, + value.center_y.rational_upper + value.y_radius.rational_upper, + ) + z_min = _enclosure_tuple(value.aabb_z) + z_max = _enclosure_tuple(value.aabb_z) + return (x_min, x_max, y_min, y_max, z_min, z_max) + + +def _compound_continuous_extent_intervals_v4( + values: tuple[ContinuousYawBoxBoundsV4, ...], +) -> _ContinuousExtentIntervalsV4: + extents = tuple(_continuous_extent_intervals_v4(value) for value in values) + return tuple( + ( + min(value[index][0] for value in extents), + min(value[index][1] for value in extents), + ) + if index in (0, 2, 4) + else ( + max(value[index][0] for value in extents), + max(value[index][1] for value in extents), + ) + for index in range(6) + ) # type: ignore[return-value] + + +def _continuous_signed_pair_clearance_v4( + left: ContinuousYawBoxBoundsV4, + right: ContinuousYawBoxBoundsV4, +) -> tuple[Fraction, Fraction]: + left_extent = _continuous_extent_intervals_v4(left) + right_extent = _continuous_extent_intervals_v4(right) + lower = max( + right_extent[0][0] - left_extent[1][1], + left_extent[0][0] - right_extent[1][1], + right_extent[2][0] - left_extent[3][1], + left_extent[2][0] - right_extent[3][1], + right_extent[4][0] - left_extent[5][1], + left_extent[4][0] - right_extent[5][1], + ) + upper = max( + right_extent[0][1] - left_extent[1][0], + left_extent[0][1] - right_extent[1][0], + right_extent[2][1] - left_extent[3][0], + left_extent[2][1] - right_extent[3][0], + right_extent[4][1] - left_extent[5][0], + left_extent[4][1] - right_extent[5][0], + ) + return lower, upper + + +def _continuous_support_containment_v4( + extent: _ContinuousExtentIntervalsV4, + surface: SupportSurfaceV3, +) -> tuple[Fraction, Fraction]: + intervals = ( + (extent[0][0] - surface.x_lower, extent[0][1] - surface.x_lower), + (surface.x_upper - extent[1][1], surface.x_upper - extent[1][0]), + (extent[2][0] - surface.y_lower, extent[2][1] - surface.y_lower), + (surface.y_upper - extent[3][1], surface.y_upper - extent[3][0]), + ) + return min(value[0] for value in intervals), min(value[1] for value in intervals) + + +def _continuous_relation_slacks_v4( + *, + relation: str, + subject_extent: _ContinuousExtentIntervalsV4, + reference_extent: _ContinuousExtentIntervalsV4, + pair_outcomes: tuple[ContinuousYawBoxPairOutcomeV4, ...], + policy: FixedCardinalCellPolicyV3, +) -> tuple[Fraction, Fraction, str, tuple[Fraction, Fraction] | None]: + if relation == "LEFT": + raw = ( + subject_extent[1][0] - reference_extent[0][1], + subject_extent[1][1] - reference_extent[0][0], + ) + threshold = policy.relation_threshold + policy.relation_tolerance + return ( + threshold - raw[1], + threshold - raw[0], + "EXTENT_AWARE_SIGNED_AXIS_GAP", + None, + ) + if relation == "RIGHT": + raw = ( + subject_extent[0][0] - reference_extent[1][1], + subject_extent[0][1] - reference_extent[1][0], + ) + threshold = policy.relation_threshold - policy.relation_tolerance + return ( + raw[0] - threshold, + raw[1] - threshold, + "EXTENT_AWARE_SIGNED_AXIS_GAP", + None, + ) + if relation == "FRONT": + raw = ( + subject_extent[3][0] - reference_extent[2][1], + subject_extent[3][1] - reference_extent[2][0], + ) + threshold = policy.relation_threshold + policy.relation_tolerance + return ( + threshold - raw[1], + threshold - raw[0], + "EXTENT_AWARE_SIGNED_AXIS_GAP", + None, + ) + if relation == "BEHIND": + raw = ( + subject_extent[2][0] - reference_extent[3][1], + subject_extent[2][1] - reference_extent[3][0], + ) + threshold = policy.relation_threshold - policy.relation_tolerance + return ( + raw[0] - threshold, + raw[1] - threshold, + "EXTENT_AWARE_SIGNED_AXIS_GAP", + None, + ) + if not pair_outcomes or any(pair.bounds is None for pair in pair_outcomes): + raise RuntimeError("continuous near/far relation requires exact pair bounds") + squared = ( + min( + pair.bounds.squared_clearance.rational_lower + for pair in pair_outcomes + if pair.bounds is not None + ), + min( + pair.bounds.squared_clearance.rational_upper + for pair in pair_outcomes + if pair.bounds is not None + ), + ) + if relation == "NEAR": + threshold = policy.relation_threshold + policy.relation_tolerance + threshold_squared = threshold * threshold + return ( + threshold_squared - squared[1], + threshold_squared - squared[0], + "EXTENT_AWARE_EUCLIDEAN_SEPARATION", + squared, + ) + threshold = max(Fraction(), policy.relation_threshold - policy.relation_tolerance) + threshold_squared = threshold * threshold + return ( + squared[0] - threshold_squared, + squared[1] - threshold_squared, + "EXTENT_AWARE_EUCLIDEAN_SEPARATION", + squared, + ) + + +def _continuous_translation_objective_v4( + *, + cell: ClosedXYCellV3, + yaw_bounds: so2_interval.ContinuousYawSinCosBoundsV4, + subject_pivot_xy: tuple[Fraction, Fraction], + objective_subject_pivot_xy: tuple[Fraction, Fraction], + atomic_budget: SO2AtomicBudgetV2, +) -> tuple[Fraction, Fraction]: + relative_x = objective_subject_pivot_xy[0] - subject_pivot_xy[0] + relative_y = objective_subject_pivot_xy[1] - subject_pivot_xy[1] + cosine = _enclosure_tuple(yaw_bounds.cosine) + sine = _enclosure_tuple(yaw_bounds.sine) + negative_sine = _interval_negate(yaw_bounds.sine, atomic_budget) + rotated_x = _interval_add( + _interval_scale_signed(cosine, relative_x, atomic_budget), + _interval_scale_signed(negative_sine, relative_y, atomic_budget), + atomic_budget, + ) + rotated_y = _interval_add( + _interval_scale_signed(sine, relative_x, atomic_budget), + _interval_scale_signed(cosine, relative_y, atomic_budget), + atomic_budget, + ) + orbital_x = _interval_subtract( + _interval_add( + rotated_x, (subject_pivot_xy[0], subject_pivot_xy[0]), atomic_budget + ), + (objective_subject_pivot_xy[0], objective_subject_pivot_xy[0]), + atomic_budget, + ) + orbital_y = _interval_subtract( + _interval_add( + rotated_y, (subject_pivot_xy[1], subject_pivot_xy[1]), atomic_budget + ), + (objective_subject_pivot_xy[1], objective_subject_pivot_xy[1]), + atomic_budget, + ) + displacement_x = _interval_add( + orbital_x, (cell.x_lower, cell.x_upper), atomic_budget + ) + displacement_y = _interval_add( + orbital_y, (cell.y_lower, cell.y_upper), atomic_budget + ) + lower = max( + _axis_separation_v3( + displacement_x[0], displacement_x[1], Fraction(), Fraction() + ), + _axis_separation_v3( + displacement_y[0], displacement_y[1], Fraction(), Fraction() + ), + ) + upper = max( + _absolute_fraction_v3(displacement_x[0]), + _absolute_fraction_v3(displacement_x[1]), + ) + max( + _absolute_fraction_v3(displacement_y[0]), + _absolute_fraction_v3(displacement_y[1]), + ) + return lower, upper + + +def _continuous_shortest_turn_interval_v4( + lower: Fraction, + upper: Fraction, +) -> tuple[Fraction, Fraction]: + candidates = [lower, upper] + first_half = so2_interval._floor_fraction(2 * lower) + last_half = so2_interval._floor_fraction(2 * upper) + candidates.extend( + Fraction(index, 2) + for index in range(first_half, last_half + 1) + if lower <= Fraction(index, 2) <= upper + ) + values = tuple(_shortest_turn_distance_v4(value) for value in candidates) + return min(values), max(values) + + +def _shortest_turn_distance_v4(value: Fraction) -> Fraction: + floor = so2_interval._floor_fraction(value) + return min(value - floor, floor + 1 - value) + + +def _continuous_cell_id_v4( + cell: ClosedXYCellV3, + yaw_bounds: so2_interval.ContinuousYawSinCosBoundsV4, +) -> str: + xy = ",".join( + f"{value.numerator}/{value.denominator}" for value in cell.canonical_bounds + ) + lift = yaw_bounds.lift_bounds + return ( + "continuous-yaw-cell:" + f"xy={xy}:turn={lift.lower.numerator}/{lift.lower.denominator}:" + f"{lift.upper.numerator}/{lift.upper.denominator}:lift={lift.coverage_sha256}" + ) + + +def _continuous_cell_from_visibility_nonexact_v4( + visibility: object, +) -> ContinuousYawCellOutcomeV4: + return ContinuousYawCellOutcomeV4( + visibility.kind, # type: ignore[union-attr] + atomic_steps_used=0, + proof_rows=visibility.proof_rows, # type: ignore[union-attr] + finding_codes=visibility.finding_codes, # type: ignore[union-attr] + ) + + +def _continuous_cell_from_pair_nonexact_v4( + values: tuple[ContinuousYawBoxPairOutcomeV4, ...], + start_used: int, + atomic_budget: SO2AtomicBudgetV2, +) -> ContinuousYawCellOutcomeV4: + outcome = next( + value + for value in values + if value.kind is not so2_interval.ContinuousYawIntervalKindV4.EXACT + ) + return _continuous_cell_failure_v4( + outcome.kind, + outcome.finding_codes[0], + atomic_steps_used=atomic_budget.used - start_used, + proof_rows=("PAIR:CONTINUOUS_NONEXACT:DELEGATED",), + ) + + +def _continuous_cell_failure_v4( + kind: so2_interval.ContinuousYawIntervalKindV4, + finding_code: str, + *, + atomic_steps_used: int, + proof_rows: tuple[str, ...], +) -> ContinuousYawCellOutcomeV4: + return ContinuousYawCellOutcomeV4( + kind, + atomic_steps_used=atomic_steps_used, + proof_rows=proof_rows, + finding_codes=(finding_code,), + ) + + __all__ = ( + "ClosedXYCellV3", + "ContinuousYawBoxBoundsV4", + "ContinuousYawBoxContactKindV4", + "ContinuousYawBoxOutcomeV4", + "ContinuousYawBoxPairBoundsV4", + "ContinuousYawBoxPairOutcomeV4", + "ContinuousYawCellBoundsV4", + "ContinuousYawCellOutcomeV4", + "ContinuousYawCellPolicyV4", + "FixedCardinalBoxV3", + "FixedCardinalCellBoundsV3", + "FixedCardinalCellOutcomeV3", + "FixedCardinalCellPolicyV3", + "FixedCardinalObjectiveTermV3", "OrientedAxisEnclosureV2", "OrientedAxisGapBoundsV2", "OrientedBoxContactKindV2", @@ -966,6 +3693,11 @@ def _pair_failure( "OrientedUprightBoxOutcomeV2", "OrientedUprightBoxPairBoundsV2", "OrientedUprightBoxPairOutcomeV2", + "SupportSurfaceV3", + "compile_continuous_yaw_box_bounds_v4", "compile_oriented_upright_box_bounds_v2", "compile_oriented_upright_box_pair_bounds_v2", + "evaluate_continuous_yaw_box_pair_bounds_v4", + "evaluate_continuous_yaw_cell_v4", + "evaluate_fixed_cardinal_cell_v3", ) diff --git a/src/spatialcf/core/backends.py b/src/spatialcf/core/backends.py index a759526..f7b246f 100644 --- a/src/spatialcf/core/backends.py +++ b/src/spatialcf/core/backends.py @@ -17,6 +17,7 @@ from spatialcf.domain.operators import OperatorDefinition from spatialcf.domain.outcomes import ( BackendProposal, + BackendSubmission, CapabilityMatch, CapabilityMismatch, TypedCompilationOutcome, @@ -33,6 +34,7 @@ __all__ = ( "CompiledProblemProtocol", "SolverBackendProtocol", + "SolverBackendProtocolV2", "match_backend_capabilities", "order_backend_matches", ) @@ -105,6 +107,33 @@ def solve( ) -> BackendProposal: ... +@runtime_checkable +class SolverBackendProtocolV2(Protocol): + """Additive submission boundary for post-M2 backends. + + ``solve_submission`` intentionally does not overlap the retained + ``SolverBackendProtocol.solve`` execution member. This keeps the legacy + finite-bound proposal wire runtime-distinguishable from terminal-evidence + submissions. + """ + + def inspect( + self, + solve_request: CounterfactualSolveRequest, + ) -> CapabilityMatch | CapabilityMismatch: ... + + def compile( + self, + solve_request: CounterfactualSolveRequest, + ) -> CompiledProblemProtocol | TypedCompilationOutcome: ... + + def solve_submission( + self, + compiled: CompiledProblemProtocol, + config: CounterfactualSolverConfig, + ) -> BackendSubmission: ... + + def _self_digest_matches(model: HashBoundCanonicalModel) -> bool: """Check a submitted hash-bound record without reconstructing it.""" diff --git a/src/spatialcf/core/outcome_assembler.py b/src/spatialcf/core/outcome_assembler.py new file mode 100644 index 0000000..e47f132 --- /dev/null +++ b/src/spatialcf/core/outcome_assembler.py @@ -0,0 +1,440 @@ +"""The sole static M3 checker-dispatch, certificate, and terminal assembler.""" + +from __future__ import annotations + +from dataclasses import dataclass + +from spatialcf.core.registry import validate_submission_contract_structure +from spatialcf.core.upright_se2_verification import verify_upright_se2_submission +from spatialcf.domain import upright_se2 as upright +from spatialcf.domain.counterfactual import CounterfactualSolveRequest, EditProgram +from spatialcf.domain.outcomes import ( + BackendCompleteUnsatEvidence, + BackendProposalSubmission, + BackendSelectionRecord, + BackendSubmission, + BackendUnknownEvidence, + CertifiedSolutionCertificate, + CertifiedSolutionResult, + CheckedProofOutcome, + CheckerDisposition, + CounterfactualCertificate, + CounterfactualSolveResult, + NoncertifiedWitnessResult, + ProvenUnsatCertificate, + ProvenUnsatResult, + ResourceUsage, + UnknownResult, + VerifierDispatchRecord, +) +from spatialcf.domain.predicates import GroundedObligationSet + +__all__ = ( + "AssembledCounterfactualOutcome", + "assemble_counterfactual_outcome", + "assemble_no_selection_unknown", +) + +_COMPLETE_DOMAIN_CLAIM_REF = "definition:spatialcf/upright-se2/complete-domain/1.0" +_SOUND_COMPLETE_DOMAIN_CLAIM_REF = ( + "definition:spatialcf/upright-se2/sound-complete-domain/1.0" +) +_UNKNOWN_CLAIM_REF = "definition:spatialcf/upright-se2/claim-unknown/1.0" + + +@dataclass(frozen=True, slots=True) +class AssembledCounterfactualOutcome: + """One assembled result together with the checker records it binds.""" + + checked_proof_outcome: CheckedProofOutcome | None + verifier_dispatch_record: VerifierDispatchRecord | None + certificate: CounterfactualCertificate | None + result: CounterfactualSolveResult + program: EditProgram | None + grounded_obligations: GroundedObligationSet | None + + +def assemble_counterfactual_outcome( + *, + solve_request: CounterfactualSolveRequest, + selection: BackendSelectionRecord, + compilation: upright.UprightSE2Compilation + | upright.UprightSE2ContinuousCompilation, + submission: BackendSubmission, +) -> AssembledCounterfactualOutcome: + """Dispatch fresh M3 checking, then assemble exactly one terminal envelope.""" + + checked = verify_upright_se2_submission( + solve_request=solve_request, + selection=selection, + compilation=compilation, + submission=submission, + checker_policy=upright.build_upright_se2_checker_replay_policy( + solve_request.proof_policy, + solve_request.solve_policy_definition_bundle, + ), + ) + evidence, proof, _claim_ref = _submission_parts(submission) + dispatch = VerifierDispatchRecord.seal( + semantic_problem_sha256=solve_request.semantic_problem_sha256, + solve_request_sha256=solve_request.solve_request_sha256, + semantic_definition_bundle_sha256=( + solve_request.semantic_problem.definition_bundle.definition_bundle_sha256 + ), + solve_policy_definition_bundle_sha256=( + solve_request.solve_policy_definition_bundle.definition_bundle_sha256 + ), + proof_policy_sha256=solve_request.proof_policy.proof_policy_sha256, + backend_selection_record_sha256=selection.backend_selection_record_sha256, + proposal_backend_owner_ref=evidence.proposal_backend_owner_ref, + proposal_backend_capability_ref=evidence.proposal_backend_capability_ref, + proposal_backend_build_sha256=evidence.proposal_backend_build_sha256, + proof_material_definition_ref=evidence.proof_material.proof_material_definition_ref, + checker_owner_ref=upright.UPRIGHT_SE2_CHECKER_OWNER_REF, + checker_capability_ref=checked.checker_capability_ref, + checker_build_sha256=checked.checker_build_sha256, + checked_proof_outcome_sha256=checked.checked_proof_outcome_sha256, + ) + if type(submission) is BackendProposalSubmission: + if checked.checker_disposition is not CheckerDisposition.ACCEPTED: + return _validated( + submission, + selection, + _assemble_limited_witness( + solve_request, + selection, + compilation, + submission, + checked, + dispatch, + proof, + ), + ) + return _validated( + submission, + selection, + _assemble_solution( + solve_request, + selection, + compilation, + submission, + checked, + dispatch, + proof, + ), + ) + if type(submission) is BackendCompleteUnsatEvidence: + return _validated( + submission, + selection, + _assemble_unsat( + solve_request, selection, compilation, submission, checked, dispatch + ), + ) + if type(submission) is BackendUnknownEvidence: + result = UnknownResult.seal( + semantic_problem_sha256=solve_request.semantic_problem_sha256, + solve_request_sha256=solve_request.solve_request_sha256, + claim_definition_ref=_UNKNOWN_CLAIM_REF, + backend_selection_record_sha256=selection.backend_selection_record_sha256, + checked_proof_outcome_sha256=checked.checked_proof_outcome_sha256, + verifier_dispatch_record_sha256=dispatch.verifier_dispatch_record_sha256, + checker_disposition=checked.checker_disposition, + resource_usage=submission.resource_usage, + reason_claim_definition_ref=submission.reason_claim_definition_ref, + partial_artifact_refs=submission.partial_artifact_refs, + ) + return _validated( + submission, + selection, + AssembledCounterfactualOutcome(checked, dispatch, None, result, None, None), + ) + raise TypeError("assembler accepts only M3 V2 backend submissions") + + +def assemble_no_selection_unknown( + *, + solve_request: CounterfactualSolveRequest, + selection: BackendSelectionRecord, +) -> AssembledCounterfactualOutcome: + """Assemble the only pre-dispatch terminal without inventing a proof.""" + + if selection.selection_disposition != "NO_SELECTION": + raise ValueError("no-selection assembler requires NO_SELECTION routing") + if ( + selection.semantic_problem_sha256 != solve_request.semantic_problem_sha256 + or selection.solve_request_sha256 != solve_request.solve_request_sha256 + ): + raise ValueError("no-selection roots do not match solve request") + usage = _zero_resource_usage(solve_request) + result = UnknownResult.seal( + semantic_problem_sha256=solve_request.semantic_problem_sha256, + solve_request_sha256=solve_request.solve_request_sha256, + claim_definition_ref=_UNKNOWN_CLAIM_REF, + backend_selection_record_sha256=selection.backend_selection_record_sha256, + resource_usage=usage, + reason_claim_definition_ref=_UNKNOWN_CLAIM_REF, + ) + return AssembledCounterfactualOutcome(None, None, None, result, None, None) + + +def _assemble_solution( + solve_request: CounterfactualSolveRequest, + selection: BackendSelectionRecord, + compilation: upright.UprightSE2Compilation + | upright.UprightSE2ContinuousCompilation, + submission: BackendProposalSubmission, + checked: CheckedProofOutcome, + dispatch: VerifierDispatchRecord, + proof: upright.UprightSE2ProofMaterial | upright.UprightSE2ContinuousProofMaterial, +) -> AssembledCounterfactualOutcome: + candidate = proof.proposal_candidates[0] + program = candidate.program + if ( + submission.proposal.program_sha256 != program.program_sha256 + or submission.proposal.after_scene_state_sha256 + != program.after_scene_state_sha256 + ): + raise ValueError("checked proposal does not bind selected program") + certificate = CertifiedSolutionCertificate.seal( + **_certificate_roots( + solve_request, + selection, + compilation, + submission.proposal, + checked, + dispatch, + ), + certificate_kind="CERTIFIED_SOLUTION", + claim_definition_ref=checked.checked_claim_definition_ref, + program_sha256=program.program_sha256, + after_scene_state_sha256=program.after_scene_state_sha256, + state_delta_manifest_sha256=program.state_delta_manifest.state_delta_manifest_sha256, + grounded_obligation_set_sha256=( + compilation.grounded_obligations.grounded_obligation_set_sha256 + ), + ) + result = CertifiedSolutionResult.seal( + semantic_problem_sha256=solve_request.semantic_problem_sha256, + solve_request_sha256=solve_request.solve_request_sha256, + claim_definition_ref=certificate.claim_definition_ref, + backend_selection_record_sha256=selection.backend_selection_record_sha256, + checked_proof_outcome_sha256=checked.checked_proof_outcome_sha256, + verifier_dispatch_record_sha256=dispatch.verifier_dispatch_record_sha256, + checker_disposition=checked.checker_disposition, + resource_usage=submission.proposal.resource_usage, + accepted_certificate=certificate, + certificate_sha256=certificate.certificate_sha256, + program_sha256=program.program_sha256, + after_scene_state_sha256=program.after_scene_state_sha256, + ) + return AssembledCounterfactualOutcome( + checked, + dispatch, + certificate, + result, + program, + compilation.grounded_obligations, + ) + + +def _assemble_limited_witness( + solve_request: CounterfactualSolveRequest, + selection: BackendSelectionRecord, + compilation: upright.UprightSE2Compilation + | upright.UprightSE2ContinuousCompilation, + submission: BackendProposalSubmission, + checked: CheckedProofOutcome, + dispatch: VerifierDispatchRecord, + proof: upright.UprightSE2ProofMaterial | upright.UprightSE2ContinuousProofMaterial, +) -> AssembledCounterfactualOutcome: + """Keep a freshly feasible non-global proposal explicitly noncertified.""" + + candidate = proof.proposal_candidates[0] + program = candidate.program + result = NoncertifiedWitnessResult.seal( + semantic_problem_sha256=solve_request.semantic_problem_sha256, + solve_request_sha256=solve_request.solve_request_sha256, + claim_definition_ref=_UNKNOWN_CLAIM_REF, + backend_selection_record_sha256=selection.backend_selection_record_sha256, + checked_proof_outcome_sha256=checked.checked_proof_outcome_sha256, + verifier_dispatch_record_sha256=dispatch.verifier_dispatch_record_sha256, + checker_disposition=checked.checker_disposition, + resource_usage=submission.proposal.resource_usage, + evidence_claim_definition_ref=submission.proposal.proposal_claim_definition_ref, + program_sha256=program.program_sha256, + after_scene_state_sha256=program.after_scene_state_sha256, + ) + return AssembledCounterfactualOutcome( + checked, dispatch, None, result, program, compilation.grounded_obligations + ) + + +def _validated( + submission: BackendSubmission, + selection: BackendSelectionRecord, + assembled: AssembledCounterfactualOutcome, +) -> AssembledCounterfactualOutcome: + """Invoke the registry-owned additive structural closure after assembly.""" + + assert assembled.checked_proof_outcome is not None + assert assembled.verifier_dispatch_record is not None + validate_submission_contract_structure( + submission=submission, + selection=selection, + checked_proof_outcome=assembled.checked_proof_outcome, + verifier_dispatch_record=assembled.verifier_dispatch_record, + result=assembled.result, + ) + return assembled + + +def _assemble_unsat( + solve_request: CounterfactualSolveRequest, + selection: BackendSelectionRecord, + compilation: upright.UprightSE2Compilation + | upright.UprightSE2ContinuousCompilation, + submission: BackendCompleteUnsatEvidence, + checked: CheckedProofOutcome, + dispatch: VerifierDispatchRecord, +) -> AssembledCounterfactualOutcome: + certificate = ProvenUnsatCertificate.seal( + **_certificate_roots( + solve_request, selection, compilation, submission, checked, dispatch + ), + certificate_kind="PROVEN_UNSAT", + claim_definition_ref=submission.complete_domain_claim_definition_ref, + authorized_domain_sha256=submission.authorized_domain_sha256, + complete_domain_coverage_artifact_sha256=( + submission.complete_domain_coverage_artifact_sha256 + ), + complete_domain_claim_definition_ref=_COMPLETE_DOMAIN_CLAIM_REF, + sound_complete_domain_claim_definition_ref=_SOUND_COMPLETE_DOMAIN_CLAIM_REF, + ) + result = ProvenUnsatResult.seal( + semantic_problem_sha256=solve_request.semantic_problem_sha256, + solve_request_sha256=solve_request.solve_request_sha256, + claim_definition_ref=certificate.claim_definition_ref, + backend_selection_record_sha256=selection.backend_selection_record_sha256, + checked_proof_outcome_sha256=checked.checked_proof_outcome_sha256, + verifier_dispatch_record_sha256=dispatch.verifier_dispatch_record_sha256, + checker_disposition=checked.checker_disposition, + resource_usage=submission.resource_usage, + accepted_certificate=certificate, + certificate_sha256=certificate.certificate_sha256, + complete_domain_coverage_artifact_sha256=( + submission.complete_domain_coverage_artifact_sha256 + ), + ) + return AssembledCounterfactualOutcome( + checked, dispatch, certificate, result, None, None + ) + + +def _certificate_roots( + solve_request: CounterfactualSolveRequest, + selection: BackendSelectionRecord, + compilation: upright.UprightSE2Compilation + | upright.UprightSE2ContinuousCompilation, + evidence, + checked: CheckedProofOutcome, + dispatch: VerifierDispatchRecord, +) -> dict[str, object]: + return { + "semantic_problem_sha256": solve_request.semantic_problem_sha256, + "solve_request_sha256": solve_request.solve_request_sha256, + "scene_state_sha256": solve_request.semantic_problem.scene_state.scene_state_sha256, + "backend_selection_record_sha256": selection.backend_selection_record_sha256, + "checked_proof_outcome_sha256": checked.checked_proof_outcome_sha256, + "verifier_dispatch_record_sha256": dispatch.verifier_dispatch_record_sha256, + "semantic_definition_bundle_sha256": ( + solve_request.semantic_problem.definition_bundle.definition_bundle_sha256 + ), + "solve_policy_definition_bundle_sha256": ( + solve_request.solve_policy_definition_bundle.definition_bundle_sha256 + ), + "semantics_profile_sha256": ( + compilation.semantic_closure.profile_registration.semantics_profile.semantics_profile_sha256 + ), + "action_space_profile_sha256": ( + compilation.semantic_closure.profile_registration.action_space_profile.action_space_profile_sha256 + ), + "intervention_authorization_sha256": ( + solve_request.semantic_problem.intervention_authorization.intervention_authorization_sha256 + ), + "objective_expression_sha256": ( + solve_request.semantic_problem.objective_expression.objective_expression_sha256 + ), + "proof_policy_sha256": solve_request.proof_policy.proof_policy_sha256, + "resource_policy_sha256": solve_request.resource_policy.resource_policy_sha256, + "backend_routing_policy_sha256": ( + solve_request.backend_routing_policy.backend_routing_policy_sha256 + ), + "solver_config_sha256": solve_request.solver_config.solver_config_sha256, + "implementation_registry_snapshot_sha256": ( + solve_request.implementation_registry_snapshot.implementation_registry_snapshot_sha256 + ), + "backend_descriptor_bundle_sha256": ( + solve_request.backend_descriptor_bundle.backend_descriptor_bundle_sha256 + ), + "proposal_backend_build_sha256": evidence.proposal_backend_build_sha256, + "checker_build_sha256": upright.UPRIGHT_SE2_CHECKER_BUILD_SHA256, + "proof_material_definition_ref": evidence.proof_material.proof_material_definition_ref, + "proof_material_sha256": evidence.proof_material_sha256, + "checker_disposition": checked.checker_disposition, + "resource_usage": evidence.resource_usage, + } + + +def _submission_parts(submission: BackendSubmission): + if type(submission) is BackendProposalSubmission: + evidence = submission.proposal + elif type(submission) in (BackendCompleteUnsatEvidence, BackendUnknownEvidence): + evidence = submission + else: + raise TypeError("assembler accepts only exact M3 V2 backend submissions") + if len(evidence.proof_material.typed_payload) != 1: + raise ValueError("submission proof payload is not singular") + if ( + evidence.proof_material.proof_material_definition_ref + == upright.UPRIGHT_SE2_PROOF_MATERIAL_DEFINITION_REF + ): + proof = upright.decode_upright_se2_proof_material( + evidence.proof_material.typed_payload[0] + ) + elif ( + evidence.proof_material.proof_material_definition_ref + == upright.UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_DEFINITION_REF + ): + proof = upright.decode_upright_se2_continuous_proof_material( + evidence.proof_material.typed_payload[0] + ) + else: + raise ValueError("submission proof payload is not an installed M3 definition") + claim_ref = ( + evidence.proposal_claim_definition_ref + if type(submission) is BackendProposalSubmission + else ( + evidence.complete_domain_claim_definition_ref + if type(submission) is BackendCompleteUnsatEvidence + else evidence.reason_claim_definition_ref + ) + ) + return evidence, proof, claim_ref + + +def _zero_resource_usage(solve_request: CounterfactualSolveRequest) -> ResourceUsage: + limits = solve_request.resource_policy.limits + if len(limits) != 1: + raise ValueError("M3 no-selection requires one registered resource limit") + return ResourceUsage.model_validate( + { + "accounting_claim_definition_ref": ( + solve_request.resource_policy.shared_ledger_policy_ref + ), + "entries": ( + {"resource_definition_ref": limits[0].definition_ref, "used": 0.0}, + ), + "exhausted": False, + } + ) diff --git a/src/spatialcf/core/registry.py b/src/spatialcf/core/registry.py index db2f2cb..737ea6d 100644 --- a/src/spatialcf/core/registry.py +++ b/src/spatialcf/core/registry.py @@ -36,8 +36,12 @@ WriteAuthority, ) from spatialcf.domain.outcomes import ( + BackendCompleteUnsatEvidence, BackendProposal, + BackendProposalSubmission, BackendSelectionRecord, + BackendSubmission, + BackendUnknownEvidence, CapabilityMatch, CapabilityMismatch, CertifiedSolutionCertificate, @@ -4185,3 +4189,167 @@ def validate_outcome_contract( ): raise SemanticContractError("certificate roots") return result + + def validate_submission_contract( + self, + *, + submission: BackendSubmission, + outcome_contract_arguments: dict[str, object], + ) -> ( + CertifiedSolutionResult + | ProvenUnsatResult + | NoncertifiedWitnessResult + | UnknownResult + ): + """Validate one M3 V2 submission without changing the retained API. + + Proposal wrappers add no semantics: after exact wrapper binding they + delegate verbatim to :meth:`validate_outcome_contract`. Program-free + terminal evidence is checked only for its additive structural roots; + terminal assembly remains the sole owner of certificates and results. + """ + + if type(submission) is BackendProposalSubmission: + proposal = outcome_contract_arguments.get("proposal") + if ( + proposal is None + or canonical_json_bytes(proposal) + != canonical_json_bytes(submission.proposal) + or submission.backend_proposal_sha256 + != submission.proposal.backend_proposal_sha256 + ): + raise SemanticContractError("proposal submission wrapper") + return self.validate_outcome_contract(**outcome_contract_arguments) # type: ignore[arg-type] + + if type(submission) not in ( + BackendCompleteUnsatEvidence, + BackendUnknownEvidence, + ): + raise SemanticContractError("submission branch") + selection = outcome_contract_arguments.get("selection") + result = outcome_contract_arguments.get("result") + if type(selection) is not BackendSelectionRecord: + raise SemanticContractError("submission selection") + if type(result) not in (ProvenUnsatResult, UnknownResult): + raise SemanticContractError("terminal submission result") + if ( + submission.backend_selection_record_sha256 + != selection.backend_selection_record_sha256 + or result.backend_selection_record_sha256 + != selection.backend_selection_record_sha256 + or submission.proof_material_sha256 + != submission.proof_material.proof_material_sha256 + or canonical_json_bytes(submission.resource_usage) + != canonical_json_bytes(result.resource_usage) + ): + raise SemanticContractError("terminal submission roots") + if type(submission) is BackendCompleteUnsatEvidence: + if ( + type(result) is not ProvenUnsatResult + or result.complete_domain_coverage_artifact_sha256 + != submission.complete_domain_coverage_artifact_sha256 + ): + raise SemanticContractError("complete-domain submission") + elif type(result) is not UnknownResult: + raise SemanticContractError("unknown submission") + return result + + +def validate_submission_contract_structure( + *, + submission: BackendSubmission, + selection: BackendSelectionRecord, + checked_proof_outcome: CheckedProofOutcome, + verifier_dispatch_record: VerifierDispatchRecord, + result: ( + CertifiedSolutionResult + | ProvenUnsatResult + | NoncertifiedWitnessResult + | UnknownResult + ), +) -> None: + """Close the M3 submission-to-assembled-record hash DAG without execution. + + This is the dispatch-time half of the additive submission validator. The + retained instance method above remains the sole full M1 proposal-contract + validator; its exact signature and behavior are intentionally unchanged. + """ + + if type(checked_proof_outcome) is not CheckedProofOutcome: + raise TypeError("submission structure requires a typed checked outcome") + if type(verifier_dispatch_record) is not VerifierDispatchRecord: + raise TypeError("submission structure requires a typed checker dispatch") + if ( + verifier_dispatch_record.checked_proof_outcome_sha256 + != checked_proof_outcome.checked_proof_outcome_sha256 + or verifier_dispatch_record.checker_capability_ref + != checked_proof_outcome.checker_capability_ref + or verifier_dispatch_record.checker_build_sha256 + != checked_proof_outcome.checker_build_sha256 + ): + raise SemanticContractError("checker dispatch does not bind checked outcome") + if ( + selection.selection_disposition != "SELECTED" + or result.backend_selection_record_sha256 + != selection.backend_selection_record_sha256 + or result.checked_proof_outcome_sha256 + != checked_proof_outcome.checked_proof_outcome_sha256 + or result.verifier_dispatch_record_sha256 + != verifier_dispatch_record.verifier_dispatch_record_sha256 + or result.checker_disposition != checked_proof_outcome.checker_disposition + ): + raise SemanticContractError("assembled submission roots") + evidence = ( + submission.proposal + if type(submission) is BackendProposalSubmission + else submission + ) + if ( + evidence.backend_selection_record_sha256 + != selection.backend_selection_record_sha256 + or evidence.proof_material_sha256 != checked_proof_outcome.proof_material_sha256 + or evidence.proposal_backend_owner_ref + != verifier_dispatch_record.proposal_backend_owner_ref + or evidence.proposal_backend_capability_ref + != verifier_dispatch_record.proposal_backend_capability_ref + or evidence.proposal_backend_build_sha256 + != verifier_dispatch_record.proposal_backend_build_sha256 + ): + raise SemanticContractError("assembled submission evidence") + if type(submission) is BackendCompleteUnsatEvidence and ( + type(result) is not ProvenUnsatResult + or result.complete_domain_coverage_artifact_sha256 + != submission.complete_domain_coverage_artifact_sha256 + ): + raise SemanticContractError("assembled complete-domain evidence") + if type(submission) is BackendUnknownEvidence and type(result) is not UnknownResult: + raise SemanticContractError("assembled unknown evidence") + if type(submission) is BackendProposalSubmission: + if checked_proof_outcome.checker_disposition is CheckerDisposition.ACCEPTED: + if type(result) is not CertifiedSolutionResult: + raise SemanticContractError("assembled accepted proposal") + certificate = result.accepted_certificate + if ( + result.claim_definition_ref + != checked_proof_outcome.checked_claim_definition_ref + or certificate.claim_definition_ref + != checked_proof_outcome.checked_claim_definition_ref + ): + raise SemanticContractError("certificate claim does not bind checker") + if ( + certificate.proof_material_definition_ref + != evidence.proof_material.proof_material_definition_ref + ): + raise SemanticContractError( + "certificate proof does not bind submission" + ) + if ( + result.program_sha256 != evidence.program_sha256 + or result.after_scene_state_sha256 != evidence.after_scene_state_sha256 + or certificate.program_sha256 != evidence.program_sha256 + or certificate.after_scene_state_sha256 + != evidence.after_scene_state_sha256 + ): + raise SemanticContractError("terminal payload does not bind submission") + elif type(result) is not NoncertifiedWitnessResult: + raise SemanticContractError("assembled limited proposal") diff --git a/src/spatialcf/core/upright_se2_backend.py b/src/spatialcf/core/upright_se2_backend.py new file mode 100644 index 0000000..dc6a798 --- /dev/null +++ b/src/spatialcf/core/upright_se2_backend.py @@ -0,0 +1,2880 @@ +"""Exact-cardinal proposal orchestration for ``spatialcf/upright_se2@1``. + +This module produces untrusted V2 backend submissions only. It deliberately +owns neither a checker/certificate/result path nor geometry formulas: every +scene-to-kernel value comes through the public compiler bridge and every +geometric result comes from the retained cardinal kernel owners. +""" + +from __future__ import annotations + +from fractions import Fraction +from typing import TypeAlias + +from spatialcf.core._internal.kernels.projected_visibility import ( + evaluate_continuous_yaw_visibility_v4, + evaluate_fixed_cardinal_visibility_v3, +) +from spatialcf.core._internal.kernels.so2 import ( + CardinalKernelKindV3, + ContinuousYawIntervalKindV4, + SO2AtomicBudgetV2, + compile_continuous_yaw_lift_v4, + compile_lifted_turn_sin_cos_bounds_v4, +) +from spatialcf.core._internal.kernels.upright_box import ( + ClosedXYCellV3, + ContinuousYawCellPolicyV4, + compile_continuous_yaw_box_bounds_v4, + evaluate_continuous_yaw_cell_v4, + evaluate_fixed_cardinal_cell_v3, +) +from spatialcf.core.upright_se2_compiler import ( + build_upright_se2_cardinal_evaluation_inputs, + build_upright_se2_continuous_evaluation_inputs, + build_upright_se2_retained_owner_evaluation, + compile_upright_se2, + materialize_upright_se2_continuous_endpoint, + materialize_upright_se2_endpoint, +) +from spatialcf.domain import upright_se2 as upright +from spatialcf.domain.base import Vec2 +from spatialcf.domain.counterfactual import CounterfactualSolveRequest +from spatialcf.domain.definitions import TypedValue +from spatialcf.domain.outcomes import ( + BackendCompleteUnsatEvidence, + BackendProposal, + BackendProposalSubmission, + BackendSelectionRecord, + BackendSubmission, + BackendUnknownEvidence, + CapabilityMatch, + CapabilityMismatch, + ProofMaterialEnvelope, + ResourceUsage, + TypedCompilationOutcome, +) +from spatialcf.domain.profiles import ( + CounterfactualSolverConfig, + SolverBackendDescriptor, +) +from spatialcf.domain.serialization import canonical_json_bytes, canonical_sha256 + +__all__ = ( + "UprightSE2Backend", + "UprightSE2CardinalBackend", + "UprightSE2ContinuousBackend", +) + +_BACKEND_REF = "backend:spatialcf/upright-se2/cardinal" +_CONTINUOUS_BACKEND_REF = "backend:spatialcf/upright-se2/continuous" +_BACKEND_BUILD_SHA256 = "b" * 64 +_MATCH_CLAIM_REF = "definition:spatialcf/upright-se2/filter/1.0" +_MISMATCH_CLAIM_REF = "definition:spatialcf/upright-se2/filter/1.0" +_SELECTION_CLAIM_REF = "definition:spatialcf/upright-se2/filter/1.0" +_SELECTION_REASON_REF = "definition:spatialcf/upright-se2/order/1.0" +_UNKNOWN_UNSUPPORTED_REF = ( + "definition:spatialcf/upright-se2/backend-unsupported-semantic-input/1.0" +) +_UNKNOWN_NUMERIC_REF = "definition:spatialcf/upright-se2/backend-numeric-gap/1.0" +_UNKNOWN_INCOMPLETE_REF = ( + "definition:spatialcf/upright-se2/backend-incomplete-cardinal-coverage/1.0" +) +_CONFIG_SUBSTITUTION_REF = ( + "definition:spatialcf/upright-se2/backend-config-substitution/1.0" +) +_UNSAT_CLAIM_REF = "definition:spatialcf/upright-se2/claim-proven-unsat/1.0" +_PROPOSAL_CLAIM_REF = "definition:spatialcf/upright-se2/claim-certified-solution/1.0" +_MAX_EXACT_DYADIC_REFINEMENT_DEPTH = 3 +_MAX_CONTINUOUS_EXACT_DYADIC_REFINEMENT_DEPTH = 3 + +_Submission: TypeAlias = ( + BackendProposalSubmission | BackendCompleteUnsatEvidence | BackendUnknownEvidence +) + + +class UprightSE2CardinalBackend: + """A V2-only exact-cardinal backend with no retained ``solve`` member.""" + + def inspect( + self, + solve_request: CounterfactualSolveRequest, + ) -> CapabilityMatch | CapabilityMismatch: + """Match only the fully closed registered cardinal request/profile subset.""" + + descriptor = _descriptor_or_none(solve_request) + if not _is_exact_model(solve_request, CounterfactualSolveRequest): + return _capability_mismatch(descriptor) + try: + compilation = compile_upright_se2(solve_request) + except (TypeError, ValueError, ArithmeticError): + return _capability_mismatch(descriptor) + if type(compilation) is not upright.UprightSE2Compilation: + return _capability_mismatch(descriptor) + if not _descriptor_closes_cardinal_request(descriptor, compilation): + return _capability_mismatch(descriptor) + return CapabilityMatch( + backend_ref=_BACKEND_REF, + backend_descriptor_sha256=descriptor.backend_descriptor_sha256, + matched_capability_refs=tuple( + sorted( + ( + *upright.UPRIGHT_SE2_CARDINAL_CAPABILITY_REFS, + upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF, + upright.UPRIGHT_SE2_OBJECTIVE_EVALUATOR_CAPABILITY_REF, + ), + key=canonical_json_bytes, + ) + ), + match_claim_definition_ref=_MATCH_CLAIM_REF, + ) + + def compile( + self, + solve_request: CounterfactualSolveRequest, + ) -> ( + upright.UprightSE2Compilation + | upright.UprightSE2ContinuousCompilation + | TypedCompilationOutcome + ): + """Delegate to the sole compiler without retaining a compilation cache. + + Direct construction can return the additive continuous sibling; this + cardinal backend still rejects it at ``inspect`` and only accepts a + cardinal compilation at ``solve_submission``. + """ + + return compile_upright_se2(solve_request) + + def solve_submission( + self, + compiled: upright.UprightSE2Compilation, + config: CounterfactualSolverConfig, + ) -> BackendSubmission: + """Return an untrusted proposal, complete UNSAT evidence, or UNKNOWN. + + The source roots stay on ``compiled`` rather than in an instance cache. + That makes each submission independently bound to its immutable request + and leaves later checker dispatch and terminal assembly to their owners. + """ + + compilation = _require_exact_compilation(compiled) + request = compilation.source_solve_request + descriptor = _descriptor_or_none(request) + inspection = self.inspect(request) + selection = _selection_record(request, descriptor, inspection) + if not isinstance(inspection, CapabilityMatch): + proof = _synthetic_incomplete_proof( + compilation, + reason="unsupported-cardinal-capability", + ) + return _unknown_submission( + compilation, + selection, + proof, + _UNKNOWN_UNSUPPORTED_REF, + ) + if not _config_matches_compilation(config, compilation): + proof = _synthetic_incomplete_proof( + compilation, + reason="solver-config-substitution", + ) + return _unknown_submission( + compilation, + selection, + proof, + _CONFIG_SUBSTITUTION_REF, + ) + return _evaluate_cardinal_compilation(compilation, selection) + + +class UprightSE2ContinuousBackend: + """V2-only continuous submission backend with no checker/result authority.""" + + def inspect( + self, + solve_request: CounterfactualSolveRequest, + ) -> CapabilityMatch | CapabilityMismatch: + descriptor = _continuous_descriptor_or_none(solve_request) + if not _is_exact_model(solve_request, CounterfactualSolveRequest): + return _continuous_capability_mismatch(descriptor) + try: + compilation = compile_upright_se2(solve_request) + except (TypeError, ValueError, ArithmeticError): + return _continuous_capability_mismatch(descriptor) + if type(compilation) is not upright.UprightSE2ContinuousCompilation: + return _continuous_capability_mismatch(descriptor) + if not _descriptor_closes_continuous_request(descriptor, compilation): + return _continuous_capability_mismatch(descriptor) + assert descriptor is not None + return CapabilityMatch( + backend_ref=_CONTINUOUS_BACKEND_REF, + backend_descriptor_sha256=descriptor.backend_descriptor_sha256, + matched_capability_refs=tuple( + sorted( + ( + *upright.UPRIGHT_SE2_CONTINUOUS_CAPABILITY_REFS, + upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF, + upright.UPRIGHT_SE2_OBJECTIVE_EVALUATOR_CAPABILITY_REF, + ), + key=canonical_json_bytes, + ) + ), + match_claim_definition_ref=_MATCH_CLAIM_REF, + ) + + def compile( + self, + solve_request: CounterfactualSolveRequest, + ) -> upright.UprightSE2ContinuousCompilation | TypedCompilationOutcome: + return compile_upright_se2(solve_request) + + def solve_submission( + self, + compiled: upright.UprightSE2ContinuousCompilation, + config: CounterfactualSolverConfig, + ) -> BackendSubmission: + compilation = _require_exact_continuous_compilation(compiled) + request = compilation.source_solve_request + descriptor = _continuous_descriptor_or_none(request) + inspection = self.inspect(request) + selection = _continuous_selection_record(request, descriptor, inspection) + if not isinstance(inspection, CapabilityMatch): + return _continuous_unknown_submission( + compilation, + selection, + _UNKNOWN_UNSUPPORTED_REF, + exhausted=False, + ) + if not _config_matches_continuous_compilation(config, compilation): + return _continuous_unknown_submission( + compilation, + selection, + _CONFIG_SUBSTITUTION_REF, + exhausted=False, + ) + return _evaluate_continuous_compilation(compilation, selection) + + +# The shorter historical spelling is an additive alias, not another backend. +UprightSE2Backend = UprightSE2CardinalBackend + + +def _is_exact_model(value: object, model_type: type) -> bool: + """Require strict self-digest validation without accepting a coerced model.""" + + if type(value) is not model_type: + return False + try: + checked = model_type.model_validate( + value.model_dump(mode="python", round_trip=True), + strict=True, + ) + except (TypeError, ValueError): + return False + return canonical_json_bytes(checked) == canonical_json_bytes(value) + + +def _require_exact_compilation(value: object) -> upright.UprightSE2Compilation: + """Reject a substituted compilation before it reaches a retained owner.""" + + if not _is_exact_model(value, upright.UprightSE2Compilation): + raise TypeError("compiled value must be an exact UprightSE2Compilation") + assert type(value) is upright.UprightSE2Compilation + return value + + +def _require_exact_continuous_compilation( + value: object, +) -> upright.UprightSE2ContinuousCompilation: + if not _is_exact_model(value, upright.UprightSE2ContinuousCompilation): + raise TypeError( + "compiled value must be an exact UprightSE2ContinuousCompilation" + ) + assert type(value) is upright.UprightSE2ContinuousCompilation + return value + + +def _descriptor_or_none( + solve_request: object, +) -> SolverBackendDescriptor | None: + """Return the one concrete descriptor by identity, never by prefix.""" + + if type(solve_request) is not CounterfactualSolveRequest: + return None + descriptors = tuple( + descriptor + for descriptor in solve_request.backend_descriptor_bundle.backend_descriptors + if descriptor.backend_ref == _BACKEND_REF + ) + return descriptors[0] if len(descriptors) == 1 else None + + +def _continuous_descriptor_or_none( + solve_request: object, +) -> SolverBackendDescriptor | None: + if type(solve_request) is not CounterfactualSolveRequest: + return None + descriptors = tuple( + descriptor + for descriptor in solve_request.backend_descriptor_bundle.backend_descriptors + if descriptor.backend_ref == _CONTINUOUS_BACKEND_REF + ) + return descriptors[0] if len(descriptors) == 1 else None + + +def _capability_mismatch( + descriptor: SolverBackendDescriptor | None, +) -> CapabilityMismatch: + """Emit a closed mismatch row even when no valid descriptor is available.""" + + descriptor_sha256 = ( + descriptor.backend_descriptor_sha256 if descriptor is not None else "0" * 64 + ) + digest = canonical_sha256( + ( + descriptor_sha256, + _BACKEND_REF, + "exact-cardinal-capability-closure", + ), + domain="spatialcf/counterfactual/upright-se2/capability-mismatch/3.0", + ) + return CapabilityMismatch( + backend_ref=_BACKEND_REF, + backend_descriptor_sha256=descriptor_sha256, + missing_capability_refs=( + f"capability:spatialcf/upright-se2/mismatch/{digest}", + ), + reason_claim_definition_ref=_MISMATCH_CLAIM_REF, + ) + + +def _continuous_capability_mismatch( + descriptor: SolverBackendDescriptor | None, +) -> CapabilityMismatch: + descriptor_sha256 = ( + descriptor.backend_descriptor_sha256 if descriptor is not None else "0" * 64 + ) + digest = canonical_sha256( + (descriptor_sha256, _CONTINUOUS_BACKEND_REF, "continuous-capability-closure"), + domain="spatialcf/counterfactual/upright-se2/capability-mismatch/3.0", + ) + return CapabilityMismatch( + backend_ref=_CONTINUOUS_BACKEND_REF, + backend_descriptor_sha256=descriptor_sha256, + missing_capability_refs=( + f"capability:spatialcf/upright-se2/mismatch/{digest}", + ), + reason_claim_definition_ref=_MISMATCH_CLAIM_REF, + ) + + +def _descriptor_closes_cardinal_request( + descriptor: SolverBackendDescriptor | None, + compilation: upright.UprightSE2Compilation, +) -> bool: + """Compare every descriptor dimension against the compiled exact closure.""" + + if descriptor is None: + return False + registration = compilation.semantic_closure.profile_registration + request = compilation.source_solve_request + resource_refs = tuple( + sorted( + (limit.definition_ref for limit in request.resource_policy.limits), + key=canonical_json_bytes, + ) + ) + checker_refs = tuple( + sorted( + ( + upright.UPRIGHT_SE2_CARDINAL_CHECKER_CAPABILITY_REF, + upright.UPRIGHT_SE2_PREDICATE_VERIFIER_CAPABILITY_REF, + upright.UPRIGHT_SE2_OBJECTIVE_VERIFIER_CAPABILITY_REF, + ), + key=canonical_json_bytes, + ) + ) + registry_builds = dict( + request.implementation_registry_snapshot.implementation_build_hashes + ) + registry_owners = { + binding.definition_or_capability_ref: binding.implementation_owner_ref + for binding in request.implementation_registry_snapshot.definition_and_capability_owner_bindings + } + return ( + descriptor.backend_ref == _BACKEND_REF + and descriptor.implementation_build_sha256 == _BACKEND_BUILD_SHA256 + and descriptor.supported_profile_hashes + == (registration.action_space_profile.action_space_profile_sha256,) + and descriptor.supported_predicate_capabilities + == (upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF,) + and descriptor.supported_operator_capabilities + == (upright.UPRIGHT_SE2_CARDINAL_COMPILER_CAPABILITY_REF,) + and descriptor.supported_objective_capabilities + == (upright.UPRIGHT_SE2_OBJECTIVE_EVALUATOR_CAPABILITY_REF,) + and descriptor.supported_numeric_semantics + == (registration.semantics_profile.numeric_semantics_ref,) + and descriptor.emitted_proof_material_definition_refs + == (upright.UPRIGHT_SE2_PROOF_MATERIAL_DEFINITION_REF,) + and descriptor.compatible_checker_capability_refs == checker_refs + and descriptor.resource_definition_refs == resource_refs + and registry_builds.get(upright.UPRIGHT_SE2_BACKEND_OWNER_REF) + == _BACKEND_BUILD_SHA256 + and registry_owners.get(upright.UPRIGHT_SE2_CARDINAL_BACKEND_CAPABILITY_REF) + == upright.UPRIGHT_SE2_BACKEND_OWNER_REF + and compilation.closure.semantic_closure_sha256 + == compilation.semantic_closure.semantic_closure_sha256 + and compilation.closure.policy_bundle_sha256 + == compilation.semantic_closure.policy_bundle_sha256 + ) + + +def _descriptor_closes_continuous_request( + descriptor: SolverBackendDescriptor | None, + compilation: upright.UprightSE2ContinuousCompilation, +) -> bool: + if descriptor is None: + return False + registration = compilation.semantic_closure.profile_registration + request = compilation.source_solve_request + resource_refs = tuple( + sorted( + (limit.definition_ref for limit in request.resource_policy.limits), + key=canonical_json_bytes, + ) + ) + checker_refs = tuple( + sorted( + ( + upright.UPRIGHT_SE2_CONTINUOUS_CHECKER_CAPABILITY_REF, + upright.UPRIGHT_SE2_PREDICATE_VERIFIER_CAPABILITY_REF, + upright.UPRIGHT_SE2_OBJECTIVE_VERIFIER_CAPABILITY_REF, + ), + key=canonical_json_bytes, + ) + ) + registry_builds = dict( + request.implementation_registry_snapshot.implementation_build_hashes + ) + registry_owners = { + binding.definition_or_capability_ref: binding.implementation_owner_ref + for binding in request.implementation_registry_snapshot.definition_and_capability_owner_bindings + } + return ( + descriptor.backend_ref == _CONTINUOUS_BACKEND_REF + and descriptor.implementation_build_sha256 == _BACKEND_BUILD_SHA256 + and descriptor.supported_profile_hashes + == (registration.action_space_profile.action_space_profile_sha256,) + and descriptor.supported_predicate_capabilities + == (upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF,) + and descriptor.supported_operator_capabilities + == (upright.UPRIGHT_SE2_CONTINUOUS_COMPILER_CAPABILITY_REF,) + and descriptor.supported_objective_capabilities + == (upright.UPRIGHT_SE2_OBJECTIVE_EVALUATOR_CAPABILITY_REF,) + and descriptor.supported_numeric_semantics + == (registration.semantics_profile.numeric_semantics_ref,) + and descriptor.emitted_proof_material_definition_refs + == (upright.UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_DEFINITION_REF,) + and descriptor.compatible_checker_capability_refs == checker_refs + and descriptor.resource_definition_refs == resource_refs + and registry_builds.get(upright.UPRIGHT_SE2_BACKEND_OWNER_REF) + == _BACKEND_BUILD_SHA256 + and registry_owners.get(upright.UPRIGHT_SE2_CONTINUOUS_COMPILER_CAPABILITY_REF) + == upright.UPRIGHT_SE2_COMPILER_OWNER_REF + and registry_owners.get(upright.UPRIGHT_SE2_CONTINUOUS_BACKEND_CAPABILITY_REF) + == upright.UPRIGHT_SE2_BACKEND_OWNER_REF + and registry_owners.get(upright.UPRIGHT_SE2_CONTINUOUS_CHECKER_CAPABILITY_REF) + == upright.UPRIGHT_SE2_CHECKER_OWNER_REF + and compilation.closure.semantic_closure_sha256 + == compilation.semantic_closure.semantic_closure_sha256 + and compilation.closure.policy_bundle_sha256 + == compilation.semantic_closure.policy_bundle_sha256 + ) + + +def _resource_usage( + request: CounterfactualSolveRequest, + *, + used: float, + exhausted: bool, +) -> ResourceUsage: + """Create the sole request-bound resource row without a reset ledger.""" + + limits = request.resource_policy.limits + if len(limits) != 1: + raise ValueError("upright se2 requires one exact resource limit") + return ResourceUsage.model_validate( + { + "accounting_claim_definition_ref": request.resource_policy.shared_ledger_policy_ref, + "entries": ( + { + "resource_definition_ref": limits[0].definition_ref, + "used": used, + }, + ), + "exhausted": exhausted, + } + ) + + +def _aggregate_usage(usages: tuple[ResourceUsage, ...]) -> ResourceUsage: + """Reproduce the domain-owned canonical ledger aggregation transparently.""" + + if not usages: + raise ValueError("resource aggregation requires at least one usage") + accounting_refs = {usage.accounting_claim_definition_ref for usage in usages} + if len(accounting_refs) != 1: + raise ValueError("resource aggregation requires one accounting claim") + totals: dict[str, float] = {} + for usage in usages: + for entry in usage.entries: + totals[entry.resource_definition_ref] = ( + totals.get(entry.resource_definition_ref, 0.0) + entry.used + ) + return ResourceUsage.model_validate( + { + "accounting_claim_definition_ref": next(iter(accounting_refs)), + "entries": tuple( + { + "resource_definition_ref": reference, + "used": used, + } + for reference, used in sorted( + totals.items(), key=lambda item: canonical_json_bytes(item[0]) + ) + ), + "exhausted": any(usage.exhausted for usage in usages), + } + ) + + +def _selection_record( + request: CounterfactualSolveRequest, + descriptor: SolverBackendDescriptor | None, + inspection: CapabilityMatch | CapabilityMismatch, +) -> BackendSelectionRecord: + """Bind deterministic routing before untrusted proof transport is built.""" + + selected = isinstance(inspection, CapabilityMatch) and descriptor is not None + return BackendSelectionRecord.seal( + semantic_problem_sha256=request.semantic_problem_sha256, + solve_request_sha256=request.solve_request_sha256, + implementation_registry_snapshot_sha256=( + request.implementation_registry_snapshot.implementation_registry_snapshot_sha256 + ), + backend_descriptor_bundle_sha256=( + request.backend_descriptor_bundle.backend_descriptor_bundle_sha256 + ), + backend_routing_policy_sha256=( + request.backend_routing_policy.backend_routing_policy_sha256 + ), + ordered_candidate_backend_refs=(_BACKEND_REF,), + capability_rows=(inspection,), + selection_disposition="SELECTED" if selected else "NO_SELECTION", + selection_disposition_claim_ref=_SELECTION_CLAIM_REF, + selected_backend_ref=_BACKEND_REF if selected else None, + selected_backend_descriptor_sha256=( + descriptor.backend_descriptor_sha256 if selected and descriptor else None + ), + resource_allocation=( + _resource_usage(request, used=0.0, exhausted=False) if selected else None + ), + deterministic_selection_reason_ref=_SELECTION_REASON_REF, + ) + + +def _continuous_selection_record( + request: CounterfactualSolveRequest, + descriptor: SolverBackendDescriptor | None, + inspection: CapabilityMatch | CapabilityMismatch, +) -> BackendSelectionRecord: + """Bind continuous routing without reusing cardinal selection bytes.""" + + selected = isinstance(inspection, CapabilityMatch) and descriptor is not None + return BackendSelectionRecord.seal( + semantic_problem_sha256=request.semantic_problem_sha256, + solve_request_sha256=request.solve_request_sha256, + implementation_registry_snapshot_sha256=( + request.implementation_registry_snapshot.implementation_registry_snapshot_sha256 + ), + backend_descriptor_bundle_sha256=( + request.backend_descriptor_bundle.backend_descriptor_bundle_sha256 + ), + backend_routing_policy_sha256=( + request.backend_routing_policy.backend_routing_policy_sha256 + ), + ordered_candidate_backend_refs=(_CONTINUOUS_BACKEND_REF,), + capability_rows=(inspection,), + selection_disposition="SELECTED" if selected else "NO_SELECTION", + selection_disposition_claim_ref=_SELECTION_CLAIM_REF, + selected_backend_ref=_CONTINUOUS_BACKEND_REF if selected else None, + selected_backend_descriptor_sha256=( + descriptor.backend_descriptor_sha256 if selected and descriptor else None + ), + resource_allocation=( + _resource_usage(request, used=0.0, exhausted=False) if selected else None + ), + deterministic_selection_reason_ref=_SELECTION_REASON_REF, + ) + + +def _config_matches_compilation( + config: object, + compilation: upright.UprightSE2Compilation, +) -> bool: + """Refuse a substituted config rather than trusting an ambient invocation.""" + + return _is_exact_model(config, CounterfactualSolverConfig) and ( + canonical_json_bytes(config) + == canonical_json_bytes(compilation.source_solve_request.solver_config) + ) + + +def _config_matches_continuous_compilation( + config: object, + compilation: upright.UprightSE2ContinuousCompilation, +) -> bool: + return _is_exact_model(config, CounterfactualSolverConfig) and ( + canonical_json_bytes(config) + == canonical_json_bytes(compilation.source_solve_request.solver_config) + ) + + +def _cell_order_key( + cell: upright.UprightSE2CompiledCell, +) -> tuple[Fraction, Fraction, bytes]: + return ( + cell.yaw_interval.lower.as_fraction, + cell.yaw_interval.upper.as_fraction, + canonical_json_bytes(cell.cell_id), + ) + + +def _sorted_rows( + rows: tuple[upright.UprightSE2ProofCellEvaluation, ...], +) -> tuple[upright.UprightSE2ProofCellEvaluation, ...]: + return tuple(sorted(rows, key=lambda row: _cell_order_key(row.compiled_cell))) + + +def _dyadic(value: Fraction) -> upright.ExactDyadic: + """Convert an already exact dyadic subdivision endpoint to its domain wire.""" + + if value.denominator & (value.denominator - 1): + raise ValueError("cardinal subdivision endpoint is not dyadic") + return upright.ExactDyadic(numerator=value.numerator, denominator=value.denominator) + + +def _split_exact_dyadic_cell( + cell: upright.UprightSE2CompiledCell, +) -> tuple[upright.UprightSE2CompiledCell, ...]: + """Deterministically split X first, then Y, without widening degenerate axes.""" + + x_lower = cell.x_lower.as_fraction + x_upper = cell.x_upper.as_fraction + y_lower = cell.y_lower.as_fraction + y_upper = cell.y_upper.as_fraction + x_parts = ((cell.x_lower, cell.x_upper, ""),) + y_parts = ((cell.y_lower, cell.y_upper, ""),) + if x_lower < x_upper: + midpoint = _dyadic((x_lower + x_upper) / 2) + x_parts = ( + (cell.x_lower, midpoint, "x-lower"), + (midpoint, cell.x_upper, "x-upper"), + ) + if y_lower < y_upper: + midpoint = _dyadic((y_lower + y_upper) / 2) + y_parts = ( + (cell.y_lower, midpoint, "y-lower"), + (midpoint, cell.y_upper, "y-upper"), + ) + if len(x_parts) == len(y_parts) == 1: + return () + return tuple( + upright.UprightSE2CompiledCell.seal( + cell_id=( + f"{cell.cell_id}/{x_name}/{y_name}" + if x_name and y_name + else f"{cell.cell_id}/{x_name or y_name}" + ), + authorization_sha256=cell.authorization_sha256, + x_lower=x_lower_part, + x_upper=x_upper_part, + y_lower=y_lower_part, + y_upper=y_upper_part, + yaw_interval=cell.yaw_interval, + ) + for x_lower_part, x_upper_part, x_name in x_parts + for y_lower_part, y_upper_part, y_name in y_parts + ) + + +def _outcome_kind( + kind: CardinalKernelKindV3, +) -> upright.UprightSE2RetainedOwnerOutcomeKind: + return { + CardinalKernelKindV3.EXACT: upright.UprightSE2RetainedOwnerOutcomeKind.EXACT, + CardinalKernelKindV3.NUMERIC_GAP: upright.UprightSE2RetainedOwnerOutcomeKind.NUMERIC_GAP, + CardinalKernelKindV3.RESOURCE_LIMIT: upright.UprightSE2RetainedOwnerOutcomeKind.RESOURCE_LIMIT, + CardinalKernelKindV3.UNSUPPORTED: upright.UprightSE2RetainedOwnerOutcomeKind.UNSUPPORTED, + }[kind] + + +def _transport_owner_outcome( + *, + request: CounterfactualSolveRequest, + cell: upright.UprightSE2CompiledCell, + outcome: object | None, + evaluator_capability_ref: str, + label: str, + budget: SO2AtomicBudgetV2 | None, + forced_kind: upright.UprightSE2RetainedOwnerOutcomeKind | None = None, + forced_atomic_steps: int = 0, + additional_exact_bounds: tuple[TypedValue, ...] = (), +) -> upright.UprightSE2RetainedOwnerEvaluation: + """Adapt retained kernel output to the compiler-owned proof transport seam.""" + + if forced_kind is None: + assert outcome is not None + kernel_kind = outcome.kind + if type(kernel_kind) is not CardinalKernelKindV3: + raise TypeError("retained owner returned an unknown cardinal outcome") + proof_kind = _outcome_kind(kernel_kind) + raw_rows = tuple(outcome.proof_rows) + raw_findings = tuple(outcome.finding_codes) + atomic_steps = outcome.atomic_steps_used + bounds = outcome.bounds + else: + proof_kind = forced_kind + raw_rows = (f"proof:spatialcf/upright-se2/{label}/incomplete",) + raw_findings = ("INCOMPLETE",) + atomic_steps = forced_atomic_steps + bounds = None + if type(atomic_steps) is not int or atomic_steps < 0: + raise TypeError("retained owner atomic usage must be a non-negative exact int") + exhausted = proof_kind is upright.UprightSE2RetainedOwnerOutcomeKind.RESOURCE_LIMIT + return build_upright_se2_retained_owner_evaluation( + compiled_cell=cell, + owner_ref=upright.UPRIGHT_SE2_BACKEND_OWNER_REF, + evaluator_capability_ref=evaluator_capability_ref, + outcome_kind=proof_kind, + raw_proof_rows=raw_rows, + raw_findings=raw_findings, + atomic_steps=atomic_steps, + resource_delta=_resource_usage( + request, + used=float(atomic_steps), + exhausted=exhausted, + ), + label=label, + exact_bound_value=bounds, + additional_exact_bounds=additional_exact_bounds, + ) + + +def _stage_for_cell( + cell: upright.UprightSE2CompiledCell, + evaluations: tuple[upright.UprightSE2RetainedOwnerEvaluation, ...], +) -> upright.UprightSE2ProofStageDelta: + """Bind every owner call for one common cell into one non-reset stage.""" + + ordered = tuple(sorted(evaluations, key=canonical_json_bytes)) + return upright.UprightSE2ProofStageDelta.seal( + stage_ref=( + "stage:spatialcf/upright-se2/cardinal-retained-evaluation/" + f"{cell.compiled_cell_sha256}" + ), + owner_evaluations=ordered, + resource_delta=_aggregate_usage( + tuple(evaluation.resource_delta for evaluation in ordered) + ), + ) + + +def _proof_material( + compilation: upright.UprightSE2Compilation, + rows: tuple[upright.UprightSE2ProofCellEvaluation, ...], + stages: tuple[upright.UprightSE2ProofStageDelta, ...], + proposal_candidates: tuple[upright.UprightSE2ProposalCandidate, ...] = (), +) -> upright.UprightSE2ProofMaterial: + """Seal the one request-relative tuple/coverage/frontier/ledger payload.""" + + ordered_rows = _sorted_rows(rows) + leaves = tuple(row for row in ordered_rows if row.leaf_disposition is not None) + coverage_cells = tuple(row.compiled_cell for row in leaves) + unresolved = tuple( + row + for row in leaves + if row.leaf_disposition is upright.UprightSE2ProofLeafDisposition.UNRESOLVED + ) + pruned = tuple( + row + for row in leaves + if row.leaf_disposition is upright.UprightSE2ProofLeafDisposition.PRUNED + ) + proposals = tuple( + row + for row in leaves + if row.leaf_disposition + is upright.UprightSE2ProofLeafDisposition.INWARD_FEASIBLE + ) + ordered_stages = tuple(sorted(stages, key=canonical_json_bytes)) + ledger = upright.UprightSE2ProofResourceLedger.seal( + stage_deltas=ordered_stages, + canonical_total_resource_usage=_aggregate_usage( + tuple(stage.resource_delta for stage in ordered_stages) + ), + ) + return upright.UprightSE2ProofMaterial.seal( + solve_request_sha256=compilation.solve_request_sha256, + semantic_closure_sha256=compilation.semantic_closure.semantic_closure_sha256, + upright_se2_compilation_sha256=compilation.upright_se2_compilation_sha256, + compilation=compilation, + cardinal_tuple_roster=( + upright.UprightSE2CardinalProofTuple.seal( + authorization=compilation.operation.authorization, + reference_id=compilation.endpoint_construction_recipe.reference_id, + translation_domain=compilation.operation.translation_domain, + compiled_cells=compilation.compiled_cells, + ), + ), + coverage_artifact=upright.UprightSE2CoverageArtifact.seal( + authorization_sha256=compilation.operation.authorization_sha256, + cells=coverage_cells, + unresolved_cell_sha256s=tuple( + sorted(row.compiled_cell.compiled_cell_sha256 for row in unresolved) + ), + ), + compiled_cell_sha256s=tuple( + cell.compiled_cell_sha256 for cell in coverage_cells + ), + evaluated_cells=ordered_rows, + proposal_order=proposals, + proposal_candidates=tuple( + sorted(proposal_candidates, key=lambda candidate: candidate.canonical_order_key) + ), + prune_decisions=tuple( + upright.UprightSE2ProofPruneDecision.seal( + cell_evaluation=row, + prune_reason_codes=( + "reason:spatialcf/upright-se2/cardinal-exact-refinement-cap", + ), + ) + for row in pruned + ), + unresolved_frontier=tuple( + upright.UprightSE2ProofFrontierRow.seal( + cell_evaluation=row, + frontier_reason_codes=( + "reason:spatialcf/upright-se2/cardinal-owner-nonexact", + ), + ) + for row in unresolved + ), + resource_ledger=ledger, + ) + + +def _synthetic_incomplete_proof( + compilation: upright.UprightSE2Compilation, + *, + reason: str, +) -> upright.UprightSE2ProofMaterial: + """Represent pre-owner unsupported/incomplete work as UNKNOWN evidence.""" + + request = compilation.source_solve_request + rows: list[upright.UprightSE2ProofCellEvaluation] = [] + stages: list[upright.UprightSE2ProofStageDelta] = [] + for cell in compilation.compiled_cells: + evaluation = _transport_owner_outcome( + request=request, + cell=cell, + outcome=None, + evaluator_capability_ref=upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF, + label=reason, + budget=None, + forced_kind=upright.UprightSE2RetainedOwnerOutcomeKind.INCOMPLETE, + ) + cell_row = upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=cell, + owner_evaluations=(evaluation,), + leaf_disposition=upright.UprightSE2ProofLeafDisposition.UNRESOLVED, + complete_domain_empty=False, + ) + rows.append(cell_row) + stages.append(_stage_for_cell(cell, (evaluation,))) + return _proof_material(compilation, tuple(rows), tuple(stages)) + + +def _classify_exact_cell( + box_outcome: object, + visibility_outcomes: tuple[object, ...], +) -> upright.UprightSE2ProofLeafDisposition | None: + """Classify only exact owner outputs; ``None`` requests deterministic refinement.""" + + all_outcomes = (box_outcome, *visibility_outcomes) + if any( + getattr(outcome, "kind", None) is not CardinalKernelKindV3.EXACT + for outcome in all_outcomes + ): + return upright.UprightSE2ProofLeafDisposition.UNRESOLVED + box_bounds = box_outcome.bounds + visibility_bounds = tuple(outcome.bounds for outcome in visibility_outcomes) + if ( + box_bounds.outer_hard_constraint_slack < 0 + or box_bounds.relation_outer_failure + or any(bounds.outer_failure for bounds in visibility_bounds) + ): + return upright.UprightSE2ProofLeafDisposition.OUTWARD_INFEASIBLE + if ( + box_bounds.inner_hard_constraint_slack >= 0 + and box_bounds.relation_inner_success + and all(bounds.inner_success for bounds in visibility_bounds) + ): + return upright.UprightSE2ProofLeafDisposition.INWARD_FEASIBLE + return None + + +def _point_cell( + cell: upright.UprightSE2CompiledCell, +) -> upright.UprightSE2CompiledCell: + """Select the lower-owned exact point of one final inward leaf.""" + + if cell.x_lower == cell.x_upper and cell.y_lower == cell.y_upper: + return cell + x = ( + cell.x_lower + if cell.x_lower == cell.x_upper + else _dyadic((cell.x_lower.as_fraction + cell.x_upper.as_fraction) / 2) + ) + y = ( + cell.y_lower + if cell.y_lower == cell.y_upper + else _dyadic((cell.y_lower.as_fraction + cell.y_upper.as_fraction) / 2) + ) + return upright.UprightSE2CompiledCell.seal( + cell_id=f"{cell.cell_id}/proposal-point", + authorization_sha256=cell.authorization_sha256, + x_lower=x, + x_upper=x, + y_lower=y, + y_upper=y, + yaw_interval=cell.yaw_interval, + ) + + +def _point_objective( + bounds: object, compilation: upright.UprightSE2Compilation +) -> upright.UprightSE2ProposalPointObjective: + """Transport the retained point owner's exact T/A/R/V/S terms unchanged.""" + + del compilation + semantic_terms = bounds.common_cell_semantic_objective_terms + weighted_terms = bounds.common_cell_objective_terms + point_terms = tuple( + upright.UprightSE2ProposalPointTerm( + term_id=term_id, + lower=upright.UprightSE2ExactRational( + numerator=lower.numerator, + denominator=lower.denominator, + ), + upper=upright.UprightSE2ExactRational( + numerator=upper.numerator, + denominator=upper.denominator, + ), + ) + for term_id, lower, upper in semantic_terms + ) + total_lower = sum( + (lower for _term_id, lower, _upper in weighted_terms), + start=Fraction(0), + ) + total_upper = sum( + (upper for _term_id, _lower, upper in weighted_terms), + start=Fraction(0), + ) + return upright.UprightSE2ProposalPointObjective( + terms=point_terms, + total_lower=upright.UprightSE2ExactRational( + numerator=total_lower.numerator, + denominator=total_lower.denominator, + ), + total_upper=upright.UprightSE2ExactRational( + numerator=total_upper.numerator, + denominator=total_upper.denominator, + ), + ) + + +def _proposal_candidate( + *, + compilation: upright.UprightSE2Compilation, + request: CounterfactualSolveRequest, + final_inward_cell: upright.UprightSE2ProofCellEvaluation, + final_box_bounds: object | None, + budget: SO2AtomicBudgetV2, +) -> tuple[ + upright.UprightSE2ProposalCandidate, + upright.UprightSE2ProofCellEvaluation, + upright.UprightSE2ProofStageDelta | None, +]: + """Evaluate and transport one retained exact point for an inward leaf.""" + + point_cell = _point_cell(final_inward_cell.compiled_cell) + if point_cell is final_inward_cell.compiled_cell: + if final_box_bounds is None: + raise ValueError("degenerate root requires its retained exact box bounds") + objective = _point_objective(final_box_bounds, compilation) + point = Vec2( + x=float(point_cell.x_lower.as_fraction), + y=float(point_cell.y_lower.as_fraction), + ) + endpoint = materialize_upright_se2_endpoint(compilation, point) + point_evaluation = upright.UprightSE2ProposalPointEvaluation.seal( + point_cell_evaluation=final_inward_cell, + point_objective=objective, + ) + return ( + upright.UprightSE2ProposalCandidate.seal( + final_inward_cell=final_inward_cell, + selected_translation_xy_m=point, + point_evaluation=point_evaluation, + point_objective=objective, + materialized_endpoint=endpoint, + program=endpoint.program, + ), + final_inward_cell, + None, + ) + point_row, point_objective, point_stage = _evaluate_point_cell( + compilation=compilation, + request=request, + point_cell=point_cell, + budget=budget, + ) + if point_objective is None: + raise ValueError("retained point owner did not return exact evidence") + point = Vec2( + x=float(point_cell.x_lower.as_fraction), + y=float(point_cell.y_lower.as_fraction), + ) + endpoint = materialize_upright_se2_endpoint(compilation, point) + point_evaluation = upright.UprightSE2ProposalPointEvaluation.seal( + point_cell_evaluation=point_row, + point_objective=point_objective, + ) + candidate = upright.UprightSE2ProposalCandidate.seal( + final_inward_cell=final_inward_cell, + selected_translation_xy_m=point, + point_evaluation=point_evaluation, + point_objective=point_objective, + materialized_endpoint=endpoint, + program=endpoint.program, + ) + return candidate, point_row, point_stage + + +def _evaluate_point_cell( + *, + compilation: upright.UprightSE2Compilation, + request: CounterfactualSolveRequest, + point_cell: upright.UprightSE2CompiledCell, + budget: SO2AtomicBudgetV2, +) -> tuple[ + upright.UprightSE2ProofCellEvaluation, + upright.UprightSE2ProposalPointObjective | None, + upright.UprightSE2ProofStageDelta, +]: + """Evaluate a strict point through owners without yet materializing a program.""" + inputs = build_upright_se2_cardinal_evaluation_inputs(compilation, point_cell) + if budget.limit != inputs.resource_atomic_step_limit: + raise ValueError("public bridge resource cap drifted at proposal point") + box_outcome = evaluate_fixed_cardinal_cell_v3( + cell=inputs.cell, + quarter_turns_ccw=inputs.quarter_turns_ccw, + subject_boxes=inputs.subject_boxes, + obstacle_boxes=inputs.obstacle_boxes, + support_surface=inputs.support_surface, + relation=inputs.relation, + reference_box=inputs.reference_box, + near_far_threshold=inputs.near_far_threshold, + policy=inputs.cell_policy, + atomic_budget=budget, + subject_pivot_xy=inputs.subject_pivot_xy, + objective_subject_pivot_xy=inputs.objective_subject_pivot_xy, + ) + visibility_outcomes = tuple( + evaluate_fixed_cardinal_visibility_v3( + context=item.context, + cell=item.cell, + subject=item.subject, + moving_subject_id=item.moving_subject_id, + occluders=item.occluders, + required_occluder_ids=item.required_occluder_ids, + policy=item.policy, + atomic_budget=budget, + ) + for item in inputs.visibility_inputs + ) + exact = all( + outcome.kind is CardinalKernelKindV3.EXACT + for outcome in (box_outcome, *visibility_outcomes) + ) + objective = _point_objective(box_outcome.bounds, compilation) if exact else None + point_objective_bound = ( + upright._retained_point_objective_value(objective) if objective else None + ) + evaluations = tuple( + sorted( + ( + _transport_owner_outcome( + request=request, + cell=point_cell, + outcome=box_outcome, + evaluator_capability_ref=( + upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF + ), + label="fixed-cardinal-point-cell", + budget=budget, + additional_exact_bounds=(point_objective_bound,) + if point_objective_bound is not None + else (), + ), + *( + _transport_owner_outcome( + request=request, + cell=point_cell, + outcome=outcome, + evaluator_capability_ref=( + upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF + ), + label=f"fixed-cardinal-point-visibility-{index}", + budget=budget, + ) + for index, outcome in enumerate(visibility_outcomes) + ), + ), + key=canonical_json_bytes, + ) + ) + point_row = upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=point_cell, + owner_evaluations=evaluations, + leaf_disposition=None, + complete_domain_empty=None, + ) + return point_row, objective, _stage_for_cell(point_cell, evaluations) + + +def _evaluate_cardinal_compilation( + compilation: upright.UprightSE2Compilation, + selection: BackendSelectionRecord, +) -> _Submission: + """Evaluate exact cells through public bridge/kernel seams with one budget.""" + + request = compilation.source_solve_request + budget: SO2AtomicBudgetV2 | None = None + rows: list[upright.UprightSE2ProofCellEvaluation] = [] + stages: list[upright.UprightSE2ProofStageDelta] = [] + candidates: list[upright.UprightSE2ProposalCandidate] = [] + + def evaluate_cell(cell: upright.UprightSE2CompiledCell, depth: int) -> None: + nonlocal budget + unreported_start_used = 0 if budget is None else budget.used + try: + inputs = build_upright_se2_cardinal_evaluation_inputs(compilation, cell) + if budget is None: + budget = SO2AtomicBudgetV2(limit=inputs.resource_atomic_step_limit) + elif budget.limit != inputs.resource_atomic_step_limit: + raise ValueError("public bridge resource cap drifted between cells") + point_row: upright.UprightSE2ProofCellEvaluation | None = None + point_objective: upright.UprightSE2ProposalPointObjective | None = None + if cell.x_lower != cell.x_upper or cell.y_lower != cell.y_upper: + point_row, point_objective, point_stage = _evaluate_point_cell( + compilation=compilation, + request=request, + point_cell=_point_cell(cell), + budget=budget, + ) + rows.append(point_row) + stages.append(point_stage) + unreported_start_used = budget.used + if point_objective is None: + incomplete = _transport_owner_outcome( + request=request, + cell=cell, + outcome=None, + evaluator_capability_ref=( + upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF + ), + label="point-first-parent-incomplete", + budget=None, + forced_kind=( + upright.UprightSE2RetainedOwnerOutcomeKind.INCOMPLETE + ), + ) + parent = upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=cell, + owner_evaluations=(incomplete,), + leaf_disposition=( + upright.UprightSE2ProofLeafDisposition.UNRESOLVED + ), + complete_domain_empty=False, + ) + rows.append(parent) + stages.append(_stage_for_cell(cell, (incomplete,))) + return + box_outcome = evaluate_fixed_cardinal_cell_v3( + cell=inputs.cell, + quarter_turns_ccw=inputs.quarter_turns_ccw, + subject_boxes=inputs.subject_boxes, + obstacle_boxes=inputs.obstacle_boxes, + support_surface=inputs.support_surface, + relation=inputs.relation, + reference_box=inputs.reference_box, + near_far_threshold=inputs.near_far_threshold, + policy=inputs.cell_policy, + atomic_budget=budget, + subject_pivot_xy=inputs.subject_pivot_xy, + objective_subject_pivot_xy=inputs.objective_subject_pivot_xy, + ) + visibility_outcomes = tuple( + evaluate_fixed_cardinal_visibility_v3( + context=item.context, + cell=item.cell, + subject=item.subject, + moving_subject_id=item.moving_subject_id, + occluders=item.occluders, + required_occluder_ids=item.required_occluder_ids, + policy=item.policy, + atomic_budget=budget, + ) + for item in inputs.visibility_inputs + ) + retained_point_objective = ( + _point_objective(box_outcome.bounds, compilation) + if all( + outcome.kind is CardinalKernelKindV3.EXACT + for outcome in (box_outcome, *visibility_outcomes) + ) + else None + ) + retained_point_objective_bound = ( + upright._retained_point_objective_value(retained_point_objective) + if retained_point_objective is not None + else None + ) + owner_evaluations = tuple( + sorted( + ( + _transport_owner_outcome( + request=request, + cell=cell, + outcome=box_outcome, + evaluator_capability_ref=( + upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF + ), + label="fixed-cardinal-cell", + budget=budget, + additional_exact_bounds=(retained_point_objective_bound,) + if retained_point_objective_bound is not None + else (), + ), + *( + _transport_owner_outcome( + request=request, + cell=cell, + outcome=outcome, + evaluator_capability_ref=( + upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF + ), + label=f"fixed-cardinal-visibility-{index}", + budget=budget, + ) + for index, outcome in enumerate(visibility_outcomes) + ), + ), + key=canonical_json_bytes, + ) + ) + classification = _classify_exact_cell(box_outcome, visibility_outcomes) + except (TypeError, ValueError, ArithmeticError) as error: + forced_kind = ( + upright.UprightSE2RetainedOwnerOutcomeKind.NUMERIC_GAP + if isinstance(error, ArithmeticError) + else ( + upright.UprightSE2RetainedOwnerOutcomeKind.UNSUPPORTED + if "unsupported" in str(error).lower() + else upright.UprightSE2RetainedOwnerOutcomeKind.INCOMPLETE + ) + ) + evaluation = _transport_owner_outcome( + request=request, + cell=cell, + outcome=None, + evaluator_capability_ref=upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF, + label="public-bridge-or-owner-incomplete", + budget=budget, + forced_kind=forced_kind, + forced_atomic_steps=( + 0 if budget is None else budget.used - unreported_start_used + ), + ) + row = upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=cell, + owner_evaluations=(evaluation,), + leaf_disposition=upright.UprightSE2ProofLeafDisposition.UNRESOLVED, + complete_domain_empty=False, + ) + rows.append(row) + stages.append(_stage_for_cell(cell, (evaluation,))) + return + + if classification is None: + children = _split_exact_dyadic_cell(cell) + if children and depth < _MAX_EXACT_DYADIC_REFINEMENT_DEPTH: + internal = upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=cell, + owner_evaluations=owner_evaluations, + leaf_disposition=None, + complete_domain_empty=None, + ) + rows.append(internal) + stages.append(_stage_for_cell(cell, owner_evaluations)) + for child in children: + evaluate_cell(child, depth + 1) + return + finite_miss = _transport_owner_outcome( + request=request, + cell=cell, + outcome=None, + evaluator_capability_ref=( + upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF + ), + label="finite-refinement-miss", + budget=None, + forced_kind=upright.UprightSE2RetainedOwnerOutcomeKind.FINITE_MISS, + ) + row = upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=cell, + owner_evaluations=tuple( + sorted((*owner_evaluations, finite_miss), key=canonical_json_bytes) + ), + leaf_disposition=upright.UprightSE2ProofLeafDisposition.UNRESOLVED, + complete_domain_empty=False, + ) + rows.append(row) + stages.append(_stage_for_cell(cell, row.owner_evaluations)) + return + + row = upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=cell, + owner_evaluations=owner_evaluations, + leaf_disposition=classification, + complete_domain_empty=( + classification + is upright.UprightSE2ProofLeafDisposition.OUTWARD_INFEASIBLE + ), + ) + rows.append(row) + stages.append(_stage_for_cell(cell, owner_evaluations)) + if classification is upright.UprightSE2ProofLeafDisposition.INWARD_FEASIBLE: + if budget is None: + raise ValueError("inward point evaluation requires a shared budget") + if point_row is not None and point_objective is not None: + point = Vec2( + x=float(point_row.compiled_cell.x_lower.as_fraction), + y=float(point_row.compiled_cell.y_lower.as_fraction), + ) + endpoint = materialize_upright_se2_endpoint(compilation, point) + point_evaluation = upright.UprightSE2ProposalPointEvaluation.seal( + point_cell_evaluation=point_row, + point_objective=point_objective, + ) + candidate = upright.UprightSE2ProposalCandidate.seal( + final_inward_cell=row, + selected_translation_xy_m=point, + point_evaluation=point_evaluation, + point_objective=point_objective, + materialized_endpoint=endpoint, + program=endpoint.program, + ) + point_stage = None + else: + candidate, point_row, point_stage = _proposal_candidate( + compilation=compilation, + request=request, + final_inward_cell=row, + final_box_bounds=box_outcome.bounds, + budget=budget, + ) + candidates.append(candidate) + if point_stage is not None: + rows.append(point_row) + stages.append(point_stage) + + for root in compilation.compiled_cells: + evaluate_cell(root, 0) + + proof = _proof_material( + compilation, + tuple(rows), + tuple(stages), + tuple(candidates), + ) + if proof.proposal_candidates: + return _proposal_submission( + compilation, selection, proof, proof.proposal_candidates[0] + ) + leaves = tuple( + row for row in proof.evaluated_cells if row.leaf_disposition is not None + ) + if leaves and all( + row.leaf_disposition + is upright.UprightSE2ProofLeafDisposition.OUTWARD_INFEASIBLE + for row in leaves + ): + return _unsat_submission(compilation, selection, proof) + outcome_kinds = { + evaluation.outcome_kind + for row in proof.evaluated_cells + for evaluation in row.owner_evaluations + } + reason = ( + request.resource_policy.exhaustion_claim_ref + if upright.UprightSE2RetainedOwnerOutcomeKind.RESOURCE_LIMIT in outcome_kinds + else ( + _UNKNOWN_NUMERIC_REF + if upright.UprightSE2RetainedOwnerOutcomeKind.NUMERIC_GAP in outcome_kinds + else ( + _UNKNOWN_UNSUPPORTED_REF + if upright.UprightSE2RetainedOwnerOutcomeKind.UNSUPPORTED + in outcome_kinds + else _UNKNOWN_INCOMPLETE_REF + ) + ) + ) + return _unknown_submission(compilation, selection, proof, reason) + + +def _proof_envelope( + compilation: upright.UprightSE2Compilation, + selection: BackendSelectionRecord, + proof: upright.UprightSE2ProofMaterial, +) -> ProofMaterialEnvelope: + """Use the domain codec exactly once for the general proof envelope.""" + + return ProofMaterialEnvelope.seal( + semantic_problem_sha256=compilation.source_solve_request.semantic_problem_sha256, + solve_request_sha256=compilation.solve_request_sha256, + backend_selection_record_sha256=selection.backend_selection_record_sha256, + proposal_backend_ref=_BACKEND_REF, + proof_material_definition_ref=upright.UPRIGHT_SE2_PROOF_MATERIAL_DEFINITION_REF, + payload_schema_ref=upright.UPRIGHT_SE2_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF, + typed_payload=(upright.encode_upright_se2_proof_material(proof),), + artifact_refs=(), + ) + + +def _unknown_submission( + compilation: upright.UprightSE2Compilation, + selection: BackendSelectionRecord, + proof: upright.UprightSE2ProofMaterial, + reason_claim_definition_ref: str, +) -> BackendUnknownEvidence: + """Emit honest terminal UNKNOWN without objective/program/result fields.""" + + envelope = _proof_envelope(compilation, selection, proof) + return BackendUnknownEvidence.seal( + semantic_problem_sha256=compilation.source_solve_request.semantic_problem_sha256, + solve_request_sha256=compilation.solve_request_sha256, + backend_selection_record_sha256=selection.backend_selection_record_sha256, + proposal_backend_ref=_BACKEND_REF, + proposal_backend_owner_ref=upright.UPRIGHT_SE2_BACKEND_OWNER_REF, + proposal_backend_capability_ref=upright.UPRIGHT_SE2_CARDINAL_BACKEND_CAPABILITY_REF, + proposal_backend_build_sha256=_BACKEND_BUILD_SHA256, + proof_material=envelope, + proof_material_sha256=envelope.proof_material_sha256, + resource_usage=proof.total_resource_usage, + reason_claim_definition_ref=reason_claim_definition_ref, + ) + + +def _continuous_proof_material( + compilation: upright.UprightSE2ContinuousCompilation, + rows: tuple[upright.UprightSE2ProofCellEvaluation, ...], + stages: tuple[upright.UprightSE2ProofStageDelta, ...], + proposal_candidates: tuple[upright.UprightSE2ContinuousProposalCandidate, ...] = (), +) -> upright.UprightSE2ContinuousProofMaterial: + """Seal one complete continuous tree/frontier/ledger proof transport.""" + + ordered_rows = _sorted_rows(rows) + leaves = tuple(row for row in ordered_rows if row.leaf_disposition is not None) + unresolved = tuple( + row + for row in leaves + if row.leaf_disposition is upright.UprightSE2ProofLeafDisposition.UNRESOLVED + ) + pruned = tuple( + row + for row in leaves + if row.leaf_disposition is upright.UprightSE2ProofLeafDisposition.PRUNED + ) + proposals = tuple( + row + for row in leaves + if row.leaf_disposition + is upright.UprightSE2ProofLeafDisposition.INWARD_FEASIBLE + ) + ordered_stages = tuple(sorted(stages, key=canonical_json_bytes)) + ordered_candidates = tuple( + sorted(proposal_candidates, key=lambda candidate: candidate.canonical_order_key) + ) + ledger = upright.UprightSE2ProofResourceLedger.seal( + stage_deltas=ordered_stages, + canonical_total_resource_usage=_aggregate_usage( + tuple(stage.resource_delta for stage in ordered_stages) + ), + ) + return upright.UprightSE2ContinuousProofMaterial.seal( + solve_request_sha256=compilation.solve_request_sha256, + semantic_closure_sha256=compilation.semantic_closure.semantic_closure_sha256, + continuous_upright_se2_compilation_sha256=( + compilation.continuous_upright_se2_compilation_sha256 + ), + compilation=compilation, + continuous_tuple_roster=( + upright.UprightSE2ContinuousProofTuple.seal( + authorization=compilation.operation.authorization, + reference_id=compilation.endpoint_construction_recipe.reference_id, + translation_domain=compilation.operation.translation_domain, + continuous_yaw_lift=compilation.continuous_yaw_lift, + compiled_cells=compilation.compiled_cells, + ), + ), + coverage_artifact=upright.UprightSE2CoverageArtifact.seal( + authorization_sha256=compilation.operation.authorization_sha256, + cells=tuple(row.compiled_cell for row in leaves), + unresolved_cell_sha256s=tuple( + sorted(row.compiled_cell.compiled_cell_sha256 for row in unresolved) + ), + ), + compiled_cell_sha256s=tuple( + row.compiled_cell.compiled_cell_sha256 for row in leaves + ), + evaluated_cells=ordered_rows, + proposal_order=proposals, + proposal_candidates=ordered_candidates, + incumbent_candidate_sha256=( + None + if not ordered_candidates + else ordered_candidates[0].continuous_proposal_candidate_sha256 + ), + prune_decisions=tuple( + upright.UprightSE2ProofPruneDecision.seal( + cell_evaluation=row, + prune_reason_codes=( + "reason:spatialcf/upright-se2/continuous-objective-bound", + ), + ) + for row in pruned + ), + unresolved_frontier=tuple( + upright.UprightSE2ProofFrontierRow.seal( + cell_evaluation=row, + frontier_reason_codes=( + "reason:spatialcf/upright-se2/continuous-owner-nonexact", + ), + ) + for row in unresolved + ), + resource_ledger=ledger, + ) + + +def _synthetic_continuous_incomplete_proof( + compilation: upright.UprightSE2ContinuousCompilation, + *, + reason: str, + exhausted: bool, +) -> upright.UprightSE2ContinuousProofMaterial: + """Retain pre-owner mismatch/config work as typed unresolved evidence.""" + + request = compilation.source_solve_request + root = compilation.compiled_cells[0] + kind = ( + upright.UprightSE2RetainedOwnerOutcomeKind.RESOURCE_LIMIT + if exhausted + else upright.UprightSE2RetainedOwnerOutcomeKind.INCOMPLETE + ) + evaluation = _continuous_transport_owner_outcome( + request=request, + cell=root, + outcome=None, + evaluator_capability_ref=upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF, + label=reason, + forced_kind=kind, + atomic_steps=0, + ) + row = upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=root, + owner_evaluations=(evaluation,), + leaf_disposition=upright.UprightSE2ProofLeafDisposition.UNRESOLVED, + complete_domain_empty=False, + ) + return _continuous_proof_material( + compilation, + (row,), + (_continuous_stage_for_cell(root, (evaluation,)),), + ) + + +def _continuous_proof_envelope( + compilation: upright.UprightSE2ContinuousCompilation, + selection: BackendSelectionRecord, + proof: upright.UprightSE2ContinuousProofMaterial, +) -> ProofMaterialEnvelope: + return ProofMaterialEnvelope.seal( + semantic_problem_sha256=compilation.source_solve_request.semantic_problem_sha256, + solve_request_sha256=compilation.solve_request_sha256, + backend_selection_record_sha256=selection.backend_selection_record_sha256, + proposal_backend_ref=_CONTINUOUS_BACKEND_REF, + proof_material_definition_ref=( + upright.UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_DEFINITION_REF + ), + payload_schema_ref=upright.UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF, + typed_payload=(upright.encode_upright_se2_continuous_proof_material(proof),), + artifact_refs=(), + ) + + +def _continuous_unknown_submission( + compilation: upright.UprightSE2ContinuousCompilation, + selection: BackendSelectionRecord, + reason_claim_definition_ref: str, + *, + exhausted: bool, +) -> BackendUnknownEvidence: + """Emit untrusted continuous UNKNOWN with complete typed proof evidence.""" + + proof = _synthetic_continuous_incomplete_proof( + compilation, + reason=reason_claim_definition_ref.rsplit("/", maxsplit=1)[-1], + exhausted=exhausted, + ) + envelope = _continuous_proof_envelope(compilation, selection, proof) + return BackendUnknownEvidence.seal( + semantic_problem_sha256=compilation.source_solve_request.semantic_problem_sha256, + solve_request_sha256=compilation.solve_request_sha256, + backend_selection_record_sha256=selection.backend_selection_record_sha256, + proposal_backend_ref=_CONTINUOUS_BACKEND_REF, + proposal_backend_owner_ref=upright.UPRIGHT_SE2_BACKEND_OWNER_REF, + proposal_backend_capability_ref=upright.UPRIGHT_SE2_CONTINUOUS_BACKEND_CAPABILITY_REF, + proposal_backend_build_sha256=_BACKEND_BUILD_SHA256, + proof_material=envelope, + proof_material_sha256=envelope.proof_material_sha256, + resource_usage=proof.total_resource_usage, + reason_claim_definition_ref=reason_claim_definition_ref, + ) + + +def _continuous_outcome_kind( + kind: ContinuousYawIntervalKindV4, +) -> upright.UprightSE2RetainedOwnerOutcomeKind: + """Map only the retained V4 outcome alphabet into proof transport.""" + + return { + ContinuousYawIntervalKindV4.EXACT: upright.UprightSE2RetainedOwnerOutcomeKind.EXACT, + ContinuousYawIntervalKindV4.NUMERIC_GAP: upright.UprightSE2RetainedOwnerOutcomeKind.NUMERIC_GAP, + ContinuousYawIntervalKindV4.RESOURCE_LIMIT: upright.UprightSE2RetainedOwnerOutcomeKind.RESOURCE_LIMIT, + ContinuousYawIntervalKindV4.UNSUPPORTED: upright.UprightSE2RetainedOwnerOutcomeKind.UNSUPPORTED, + }[kind] + + +def _continuous_transport_owner_outcome( + *, + request: CounterfactualSolveRequest, + cell: upright.UprightSE2CompiledCell, + outcome: object | None, + evaluator_capability_ref: str, + label: str, + atomic_steps: int, + forced_kind: upright.UprightSE2RetainedOwnerOutcomeKind | None = None, + additional_exact_bounds: tuple[TypedValue, ...] = (), +) -> upright.UprightSE2RetainedOwnerEvaluation: + """Transport one already-produced V4 owner result through the compiler seam.""" + + if forced_kind is None: + if outcome is None: + raise TypeError("continuous retained owner outcome is required") + kernel_kind = getattr(outcome, "kind", None) + if type(kernel_kind) is not ContinuousYawIntervalKindV4: + raise TypeError("retained owner returned an unknown continuous outcome") + proof_kind = _continuous_outcome_kind(kernel_kind) + raw_rows = tuple(getattr(outcome, "proof_rows", ())) + raw_findings = tuple(getattr(outcome, "finding_codes", ())) + bounds = getattr(outcome, "bounds", None) + if proof_kind is upright.UprightSE2RetainedOwnerOutcomeKind.EXACT and bounds is None: + raise ValueError("exact continuous retained owner outcome requires bounds") + else: + proof_kind = forced_kind + raw_rows = (f"proof:spatialcf/upright-se2/{label}/incomplete",) + raw_findings = (forced_kind.value,) + bounds = None + if type(atomic_steps) is not int or atomic_steps < 0: + raise TypeError("continuous retained owner atomic usage must be exact") + return build_upright_se2_retained_owner_evaluation( + compiled_cell=cell, + owner_ref=upright.UPRIGHT_SE2_BACKEND_OWNER_REF, + evaluator_capability_ref=evaluator_capability_ref, + outcome_kind=proof_kind, + raw_proof_rows=raw_rows, + raw_findings=raw_findings, + atomic_steps=atomic_steps, + resource_delta=_resource_usage( + request, + used=float(atomic_steps), + exhausted=( + proof_kind + is upright.UprightSE2RetainedOwnerOutcomeKind.RESOURCE_LIMIT + ), + ), + label=label, + exact_bound_value=bounds, + additional_exact_bounds=additional_exact_bounds, + continuous_exact_rationals=True, + ) + + +def _continuous_stage_for_cell( + cell: upright.UprightSE2CompiledCell, + evaluations: tuple[upright.UprightSE2RetainedOwnerEvaluation, ...], +) -> upright.UprightSE2ProofStageDelta: + """Bind all retained continuous-owner calls for one proof cell to one stage.""" + + ordered = tuple(sorted(evaluations, key=canonical_json_bytes)) + return upright.UprightSE2ProofStageDelta.seal( + stage_ref=( + "stage:spatialcf/upright-se2/continuous-retained-evaluation/" + f"{cell.compiled_cell_sha256}" + ), + owner_evaluations=ordered, + resource_delta=_aggregate_usage( + tuple(evaluation.resource_delta for evaluation in ordered) + ), + ) + + +def _continuous_closed_xy_cell( + cell: upright.UprightSE2CompiledCell, +) -> ClosedXYCellV3: + return ClosedXYCellV3( + cell.x_lower.as_fraction, + cell.x_upper.as_fraction, + cell.y_lower.as_fraction, + cell.y_upper.as_fraction, + ) + + +def _canonical_continuous_turn(value: Fraction) -> Fraction: + """Choose the registered half-open representative without doing geometry.""" + + while value < Fraction(-1, 2): + value += 1 + while value >= Fraction(1, 2): + value -= 1 + return value + + +def _exact_float(value: Fraction, *, label: str) -> float: + """Refuse a non-lossless binary64 bridge before calling a retained owner.""" + + result = float(value) + if Fraction.from_float(result) != value: + raise ArithmeticError(f"numeric gap: {label} is not an exact binary64 dyadic") + return 0.0 if result == 0.0 else result + + +def _continuous_yaw_owner_inputs( + compilation: upright.UprightSE2ContinuousCompilation, + cell: upright.UprightSE2CompiledCell, + budget: SO2AtomicBudgetV2, +) -> tuple[object, object | None]: + """Compile one proof cell's yaw owner input from its authorized root/lift.""" + + root = compilation.compiled_cells[0] + if canonical_json_bytes(cell.yaw_interval) == canonical_json_bytes(root.yaw_interval): + yaw_domain = compilation.operation.yaw_domain + else: + lower = cell.yaw_interval.lower.as_fraction + upper = cell.yaw_interval.upper.as_fraction + sweep = upper - lower + if not Fraction() <= sweep < Fraction(1): + raise ArithmeticError("numeric gap: lifted child sweep is outside ARC") + yaw_domain = upright.ContinuousYawArc( + start_angle=upright.CanonicalSO2Angle( + turns=_exact_float( + _canonical_continuous_turn(lower), label="lifted child start" + ) + ), + ccw_sweep_turns=_exact_float(sweep, label="lifted child sweep"), + ) + lift = compile_continuous_yaw_lift_v4(yaw_domain, atomic_budget=budget) + if lift.kind is not ContinuousYawIntervalKindV4.EXACT: + return lift, None + assert lift.bounds is not None + return lift, compile_lifted_turn_sin_cos_bounds_v4( + lift.bounds, + atomic_budget=budget, + ) + + +def _continuous_static_yaw_owner_inputs( + budget: SO2AtomicBudgetV2, +) -> tuple[object, object | None]: + """Compile the one retained static world-frame zero-yaw box basis.""" + + lift = compile_continuous_yaw_lift_v4( + upright.ContinuousYawArc( + start_angle=upright.CanonicalSO2Angle(turns=0.0), + ccw_sweep_turns=0.0, + ), + atomic_budget=budget, + ) + if lift.kind is not ContinuousYawIntervalKindV4.EXACT: + return lift, None + assert lift.bounds is not None + return lift, compile_lifted_turn_sin_cos_bounds_v4( + lift.bounds, + atomic_budget=budget, + ) + + +def _continuous_classification( + cell_outcome: object | None, + visibility_outcome: object | None, +) -> upright.UprightSE2ProofLeafDisposition | None: + """Classify only retained exact V4 outer/inner evidence.""" + + if ( + cell_outcome is None + or visibility_outcome is None + or getattr(cell_outcome, "kind", None) + is not ContinuousYawIntervalKindV4.EXACT + or getattr(visibility_outcome, "kind", None) + is not ContinuousYawIntervalKindV4.EXACT + ): + return upright.UprightSE2ProofLeafDisposition.UNRESOLVED + bounds = getattr(cell_outcome, "bounds", None) + visibility = getattr(visibility_outcome, "bounds", None) + if bounds is None or visibility is None: + return upright.UprightSE2ProofLeafDisposition.UNRESOLVED + if ( + bounds.outer_hard_constraint_failure + or bounds.relation_outer_failure + or visibility.classification == "OUTWARD" + ): + return upright.UprightSE2ProofLeafDisposition.OUTWARD_INFEASIBLE + if ( + bounds.inner_hard_constraint_proven + and bounds.relation_inner_success + and visibility.classification == "INWARD" + ): + return upright.UprightSE2ProofLeafDisposition.INWARD_FEASIBLE + return None + + +def _continuous_objective( + bounds: object, +) -> upright.UprightSE2ProposalPointObjective: + """Copy the V4 retained objective intervals without reconstructing a term.""" + + semantic_terms = bounds.semantic_objective_terms + weighted_terms = bounds.weighted_objective_terms + terms = tuple( + upright.UprightSE2ProposalPointTerm( + term_id=term_id, + lower=upright.UprightSE2ExactRational( + numerator=lower.numerator, + denominator=lower.denominator, + ), + upper=upright.UprightSE2ExactRational( + numerator=upper.numerator, + denominator=upper.denominator, + ), + ) + for term_id, lower, upper in semantic_terms + ) + total_lower = sum( + (lower for _term_id, lower, _upper in weighted_terms), start=Fraction(0) + ) + total_upper = sum( + (upper for _term_id, _lower, upper in weighted_terms), start=Fraction(0) + ) + return upright.UprightSE2ProposalPointObjective( + terms=terms, + total_lower=upright.UprightSE2ExactRational( + numerator=total_lower.numerator, + denominator=total_lower.denominator, + ), + total_upper=upright.UprightSE2ExactRational( + numerator=total_upper.numerator, + denominator=total_upper.denominator, + ), + ) + + +def _continuous_objective_interval(bounds: object) -> tuple[Fraction, Fraction]: + """Read the retained weighted interval for deterministic strict pruning.""" + + terms = bounds.weighted_objective_terms + return ( + sum((lower for _term, lower, _upper in terms), start=Fraction(0)), + sum((upper for _term, _lower, upper in terms), start=Fraction(0)), + ) + + +def _continuous_point_cell( + cell: upright.UprightSE2CompiledCell, +) -> upright.UprightSE2CompiledCell: + """Choose one strict-interior exact-dyadic `(x, y, u)` witness point.""" + + x = ( + cell.x_lower + if cell.x_lower == cell.x_upper + else _dyadic((cell.x_lower.as_fraction + cell.x_upper.as_fraction) / 2) + ) + y = ( + cell.y_lower + if cell.y_lower == cell.y_upper + else _dyadic((cell.y_lower.as_fraction + cell.y_upper.as_fraction) / 2) + ) + u = ( + cell.yaw_interval.lower + if cell.yaw_interval.lower == cell.yaw_interval.upper + else _dyadic( + ( + cell.yaw_interval.lower.as_fraction + + cell.yaw_interval.upper.as_fraction + ) + / 2 + ) + ) + if ( + x == cell.x_lower == cell.x_upper + and y == cell.y_lower == cell.y_upper + and u == cell.yaw_interval.lower == cell.yaw_interval.upper + ): + return cell + return upright.UprightSE2CompiledCell.seal( + cell_id=f"{cell.cell_id}/proposal-point", + authorization_sha256=cell.authorization_sha256, + x_lower=x, + x_upper=x, + y_lower=y, + y_upper=y, + yaw_interval=upright.LiftedYawInterval( + lower=u, + upper=u, + seam_ownership="NONE", + ), + ) + + +def _continuous_split_exact_dyadic_cell( + cell: upright.UprightSE2CompiledCell, + *, + depth: int, +) -> tuple[upright.UprightSE2CompiledCell, ...]: + """Split exactly one registered axis in cyclic X, Y, U order.""" + + if type(depth) is not int or depth < 0: + raise ValueError("continuous split depth must be non-negative") + dimensions = ( + ("x", cell.x_lower.as_fraction, cell.x_upper.as_fraction), + ("y", cell.y_lower.as_fraction, cell.y_upper.as_fraction), + ( + "u", + cell.yaw_interval.lower.as_fraction, + cell.yaw_interval.upper.as_fraction, + ), + ) + selected: str | None = None + for offset in range(len(dimensions)): + axis, lower, upper = dimensions[(depth + offset) % len(dimensions)] + if lower < upper: + selected = axis + break + if selected is None: + return () + + def child( + *, lower: upright.ExactDyadic, + upper: upright.ExactDyadic, + side: str, + ) -> upright.UprightSE2CompiledCell: + if selected == "x": + return upright.UprightSE2CompiledCell.seal( + cell_id=f"{cell.cell_id}/split-x-{side}", + authorization_sha256=cell.authorization_sha256, + x_lower=lower, + x_upper=upper, + y_lower=cell.y_lower, + y_upper=cell.y_upper, + yaw_interval=cell.yaw_interval, + ) + if selected == "y": + return upright.UprightSE2CompiledCell.seal( + cell_id=f"{cell.cell_id}/split-y-{side}", + authorization_sha256=cell.authorization_sha256, + x_lower=cell.x_lower, + x_upper=cell.x_upper, + y_lower=lower, + y_upper=upper, + yaw_interval=cell.yaw_interval, + ) + seam = "NONE" + if side == "lower" and cell.yaw_interval.seam_ownership == "LOWER_OWNS_SEAM": + seam = "LOWER_OWNS_SEAM" + elif side == "upper" and cell.yaw_interval.seam_ownership == "UPPER_OWNS_ENDPOINT": + seam = "UPPER_OWNS_ENDPOINT" + return upright.UprightSE2CompiledCell.seal( + cell_id=f"{cell.cell_id}/split-u-{side}", + authorization_sha256=cell.authorization_sha256, + x_lower=cell.x_lower, + x_upper=cell.x_upper, + y_lower=cell.y_lower, + y_upper=cell.y_upper, + yaw_interval=upright.LiftedYawInterval( + lower=lower, + upper=upper, + seam_ownership=seam, + ), + ) + + if selected == "x": + lower, upper = cell.x_lower, cell.x_upper + elif selected == "y": + lower, upper = cell.y_lower, cell.y_upper + else: + lower, upper = cell.yaw_interval.lower, cell.yaw_interval.upper + midpoint = _dyadic((lower.as_fraction + upper.as_fraction) / 2) + return ( + child(lower=lower, upper=midpoint, side="lower"), + child(lower=midpoint, upper=upper, side="upper"), + ) + + +def _continuous_forced_kind(error: BaseException) -> upright.UprightSE2RetainedOwnerOutcomeKind: + """Keep bridge/owner failures typed and conservative at the backend boundary.""" + + if isinstance(error, ArithmeticError): + return upright.UprightSE2RetainedOwnerOutcomeKind.NUMERIC_GAP + if "unsupported" in str(error).lower(): + return upright.UprightSE2RetainedOwnerOutcomeKind.UNSUPPORTED + return upright.UprightSE2RetainedOwnerOutcomeKind.INCOMPLETE + + +def _evaluate_continuous_owner_cell( + *, + compilation: upright.UprightSE2ContinuousCompilation, + request: CounterfactualSolveRequest, + cell: upright.UprightSE2CompiledCell, + budget: SO2AtomicBudgetV2, +) -> tuple[ + tuple[upright.UprightSE2RetainedOwnerEvaluation, ...], + object | None, + object | None, +]: + """Invoke only Task 6.1 owners for one compiler-bridged continuous cell.""" + + evaluations: list[upright.UprightSE2RetainedOwnerEvaluation] = [] + + def record( + label: str, + outcome: object, + start_used: int, + *, + additional_exact_bounds: tuple[TypedValue, ...] = (), + ) -> object: + evaluations.append( + _continuous_transport_owner_outcome( + request=request, + cell=cell, + outcome=outcome, + evaluator_capability_ref=( + upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF + ), + label=label, + atomic_steps=budget.used - start_used, + additional_exact_bounds=additional_exact_bounds, + ) + ) + return outcome + + try: + inputs = build_upright_se2_continuous_evaluation_inputs(compilation, cell) + if budget.limit != inputs.resource_atomic_step_limit: + raise ValueError("continuous compiler bridge resource cap drifted") + start = budget.used + lift, yaw_bounds = _continuous_yaw_owner_inputs(compilation, cell, budget) + record("continuous-yaw-lift", lift, start) + if getattr(lift, "kind", None) is not ContinuousYawIntervalKindV4.EXACT: + return tuple(sorted(evaluations, key=canonical_json_bytes)), None, None + assert yaw_bounds is not None + start = budget.used + record("continuous-yaw-sin-cos", yaw_bounds, start) + if getattr(yaw_bounds, "kind", None) is not ContinuousYawIntervalKindV4.EXACT: + return tuple(sorted(evaluations, key=canonical_json_bytes)), None, None + assert yaw_bounds.bounds is not None + + dynamic_subjects: list[object] = [] + for index, source_box in enumerate(inputs.subject_boxes): + start = budget.used + outcome = compile_continuous_yaw_box_bounds_v4( + source_box, + cell=inputs.cell, + pivot_xy=inputs.subject_pivot_xy, + yaw_bounds=yaw_bounds.bounds, + atomic_budget=budget, + ) + record(f"continuous-subject-box-{index}", outcome, start) + if outcome.kind is not ContinuousYawIntervalKindV4.EXACT: + return tuple(sorted(evaluations, key=canonical_json_bytes)), None, None + assert outcome.bounds is not None + dynamic_subjects.append(outcome.bounds) + + start = budget.used + static_lift, static_yaw_bounds = _continuous_static_yaw_owner_inputs(budget) + record("continuous-static-yaw-lift", static_lift, start) + if getattr(static_lift, "kind", None) is not ContinuousYawIntervalKindV4.EXACT: + return tuple(sorted(evaluations, key=canonical_json_bytes)), None, None + assert static_yaw_bounds is not None + start = budget.used + record("continuous-static-yaw-sin-cos", static_yaw_bounds, start) + if ( + getattr(static_yaw_bounds, "kind", None) + is not ContinuousYawIntervalKindV4.EXACT + ): + return tuple(sorted(evaluations, key=canonical_json_bytes)), None, None + assert static_yaw_bounds.bounds is not None + static_cell = ClosedXYCellV3( + Fraction(), Fraction(), Fraction(), Fraction() + ) + + def static_box(label: str, source_box: object) -> object | None: + start_used = budget.used + outcome = compile_continuous_yaw_box_bounds_v4( + source_box, + cell=static_cell, + pivot_xy=(Fraction(), Fraction()), + yaw_bounds=static_yaw_bounds.bounds, + atomic_budget=budget, + ) + record(label, outcome, start_used) + return outcome.bounds if outcome.kind is ContinuousYawIntervalKindV4.EXACT else None + + static_obstacles: list[object] = [] + for index, source_box in enumerate(inputs.obstacle_boxes): + boxed = static_box(f"continuous-obstacle-box-{index}", source_box) + if boxed is None: + return tuple(sorted(evaluations, key=canonical_json_bytes)), None, None + static_obstacles.append(boxed) + static_reference = static_box("continuous-reference-box", inputs.reference_box) + if static_reference is None: + return tuple(sorted(evaluations, key=canonical_json_bytes)), None, None + + visibility_input = inputs.visibility_inputs[0] + start = budget.used + visual_subject_outcome = compile_continuous_yaw_box_bounds_v4( + visibility_input.subject, + cell=inputs.cell, + pivot_xy=inputs.subject_pivot_xy, + yaw_bounds=yaw_bounds.bounds, + atomic_budget=budget, + ) + record("continuous-visibility-subject-box", visual_subject_outcome, start) + if visual_subject_outcome.kind is not ContinuousYawIntervalKindV4.EXACT: + return tuple(sorted(evaluations, key=canonical_json_bytes)), None, None + assert visual_subject_outcome.bounds is not None + visual_subject = visual_subject_outcome.bounds + visual_occluders: list[object] = [] + for index, source_box in enumerate(visibility_input.occluders): + if source_box.box_id == visibility_input.subject.box_id: + visual_occluders.append(visual_subject) + continue + boxed = static_box(f"continuous-visibility-occluder-{index}", source_box) + if boxed is None: + return tuple(sorted(evaluations, key=canonical_json_bytes)), None, None + visual_occluders.append(boxed) + visual_occluders = sorted( + visual_occluders, key=lambda value: canonical_json_bytes(value.box.box_id) + ) + start = budget.used + visibility = evaluate_continuous_yaw_visibility_v4( + context=visibility_input.context, + subject=visual_subject, + moving_subject_id=visibility_input.moving_subject_id, + occluders=tuple(visual_occluders), + required_occluder_ids=visibility_input.required_occluder_ids, + policy=visibility_input.policy, + atomic_budget=budget, + ) + record("continuous-visibility", visibility, start) + if visibility.kind is not ContinuousYawIntervalKindV4.EXACT: + return tuple(sorted(evaluations, key=canonical_json_bytes)), None, visibility + + start = budget.used + compound = evaluate_continuous_yaw_cell_v4( + cell=inputs.cell, + yaw_bounds=yaw_bounds.bounds, + subject_boxes=tuple(dynamic_subjects), + obstacle_boxes=tuple(static_obstacles), + support_surface=inputs.support_surface, + relation=inputs.relation, + reference_box=static_reference, + near_far_threshold=inputs.near_far_threshold, + policy=ContinuousYawCellPolicyV4(cardinal_policy=inputs.cell_policy), + visibility=visibility, + atomic_budget=budget, + subject_pivot_xy=inputs.subject_pivot_xy, + objective_subject_pivot_xy=inputs.objective_subject_pivot_xy, + ) + point_objective_bound: tuple[TypedValue, ...] = () + if ( + compound.kind is ContinuousYawIntervalKindV4.EXACT + and cell.x_lower == cell.x_upper + and cell.y_lower == cell.y_upper + and cell.yaw_interval.lower == cell.yaw_interval.upper + ): + assert compound.bounds is not None + point_objective_bound = ( + upright._retained_point_objective_value( + _continuous_objective(compound.bounds) + ), + ) + record( + "continuous-compound-cell", + compound, + start, + additional_exact_bounds=point_objective_bound, + ) + return tuple(sorted(evaluations, key=canonical_json_bytes)), compound, visibility + except (TypeError, ValueError, ArithmeticError, OverflowError) as error: + evaluations.append( + _continuous_transport_owner_outcome( + request=request, + cell=cell, + outcome=None, + evaluator_capability_ref=( + upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF + ), + label="continuous-bridge-or-owner-incomplete", + forced_kind=_continuous_forced_kind(error), + atomic_steps=0, + ) + ) + return tuple(sorted(evaluations, key=canonical_json_bytes)), None, None + + +def _continuous_fallback_budget(request: CounterfactualSolveRequest) -> SO2AtomicBudgetV2: + """Keep a failed compiler bridge representable without ambient defaults.""" + + limits = request.resource_policy.limits + if len(limits) != 1: + raise ValueError("continuous upright se2 requires one resource limit") + finite_limit = limits[0].finite_limit + if type(finite_limit) is not float or finite_limit < 1.0: + raise ValueError("continuous upright se2 resource limit is unusable") + return SO2AtomicBudgetV2(limit=int(finite_limit)) + + +def _continuous_unresolved_row( + *, + request: CounterfactualSolveRequest, + cell: upright.UprightSE2CompiledCell, + evaluations: tuple[upright.UprightSE2RetainedOwnerEvaluation, ...], + label: str, +) -> tuple[ + upright.UprightSE2ProofCellEvaluation, + upright.UprightSE2ProofStageDelta, +]: + """Attach explicit nonexact evidence before declaring a final frontier leaf.""" + + has_nonexact = any( + evaluation.outcome_kind is not upright.UprightSE2RetainedOwnerOutcomeKind.EXACT + for evaluation in evaluations + ) + owner_evaluations = evaluations + if not has_nonexact: + owner_evaluations = tuple( + sorted( + ( + *evaluations, + _continuous_transport_owner_outcome( + request=request, + cell=cell, + outcome=None, + evaluator_capability_ref=( + upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF + ), + label=label, + forced_kind=( + upright.UprightSE2RetainedOwnerOutcomeKind.FINITE_MISS + ), + atomic_steps=0, + ), + ), + key=canonical_json_bytes, + ) + ) + row = upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=cell, + owner_evaluations=owner_evaluations, + leaf_disposition=upright.UprightSE2ProofLeafDisposition.UNRESOLVED, + complete_domain_empty=False, + ) + return row, _continuous_stage_for_cell(cell, owner_evaluations) + + +def _continuous_incomplete_parent_row( + *, + request: CounterfactualSolveRequest, + cell: upright.UprightSE2CompiledCell, + evaluations: tuple[upright.UprightSE2RetainedOwnerEvaluation, ...], + label: str, +) -> tuple[ + upright.UprightSE2ProofCellEvaluation, + upright.UprightSE2ProofStageDelta, +]: + """Keep an inward interval unresolved when its concrete replay is not proved.""" + + incomplete = _continuous_transport_owner_outcome( + request=request, + cell=cell, + outcome=None, + evaluator_capability_ref=upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF, + label=label, + forced_kind=upright.UprightSE2RetainedOwnerOutcomeKind.INCOMPLETE, + atomic_steps=0, + ) + owner_evaluations = tuple( + sorted((*evaluations, incomplete), key=canonical_json_bytes) + ) + row = upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=cell, + owner_evaluations=owner_evaluations, + leaf_disposition=upright.UprightSE2ProofLeafDisposition.UNRESOLVED, + complete_domain_empty=False, + ) + return row, _continuous_stage_for_cell(cell, owner_evaluations) + + +def _continuous_candidate_from_point( + *, + compilation: upright.UprightSE2ContinuousCompilation, + final_inward_cell: upright.UprightSE2ProofCellEvaluation, + point_row: upright.UprightSE2ProofCellEvaluation, + point_bounds: object, +) -> upright.UprightSE2ContinuousProposalCandidate: + """Select a compiler-materialized endpoint from retained exact point output.""" + + point_cell = point_row.compiled_cell + objective = _continuous_objective(point_bounds) + point = Vec2( + x=float(point_cell.x_lower.as_fraction), + y=float(point_cell.y_lower.as_fraction), + ) + selected_yaw = point_cell.yaw_interval.lower + endpoint = materialize_upright_se2_continuous_endpoint( + compilation, + point, + selected_yaw, + ) + point_evaluation = upright.UprightSE2ProposalPointEvaluation.seal( + point_cell_evaluation=point_row, + point_objective=objective, + ) + return upright.UprightSE2ContinuousProposalCandidate.seal( + final_inward_cell=final_inward_cell, + selected_translation_xy_m=point, + selected_lifted_yaw=selected_yaw, + point_evaluation=point_evaluation, + point_objective=objective, + materialized_endpoint=endpoint, + program=endpoint.program, + ) + + +def _continuous_requested_gap(request: CounterfactualSolveRequest) -> Fraction: + """Read the request-bound finite-gap policy; never invent a solver default.""" + + return upright.decode_upright_se2_solve_policy_definition_payload( + request.solve_policy_definition_bundle + ).requested_gap.as_fraction + + +def _continuous_bounds_authorize_prune( + *, + cell_lower: Fraction, + incumbent_upper: Fraction | None, + requested_gap: Fraction, +) -> bool: + """Apply the registered strict-exact or closed finite-gap prune comparison.""" + + if incumbent_upper is None: + return False + return cell_lower > incumbent_upper or ( + requested_gap > 0 and cell_lower >= incumbent_upper - requested_gap + ) + + +def _evaluate_continuous_compilation( + compilation: upright.UprightSE2ContinuousCompilation, + selection: BackendSelectionRecord, +) -> _Submission: + """Run bounded deterministic V4-only `(x, y, u)` branch-and-bound search.""" + + request = compilation.source_solve_request + budget: SO2AtomicBudgetV2 | None = None + rows: list[upright.UprightSE2ProofCellEvaluation] = [] + stages: list[upright.UprightSE2ProofStageDelta] = [] + candidates: list[upright.UprightSE2ContinuousProposalCandidate] = [] + requested_gap = _continuous_requested_gap(request) + + def ensure_budget(cell: upright.UprightSE2CompiledCell) -> SO2AtomicBudgetV2: + nonlocal budget + if budget is not None: + return budget + try: + bridge = build_upright_se2_continuous_evaluation_inputs(compilation, cell) + budget = SO2AtomicBudgetV2(limit=bridge.resource_atomic_step_limit) + except (TypeError, ValueError, ArithmeticError): + budget = _continuous_fallback_budget(request) + return budget + + def incumbent_upper() -> Fraction | None: + if not candidates: + return None + return min( + candidate.point_objective.total_upper.as_fraction for candidate in candidates + ) + + def evaluate_cell(cell: upright.UprightSE2CompiledCell, depth: int) -> None: + active_budget = ensure_budget(cell) + owner_evaluations, compound, visibility = _evaluate_continuous_owner_cell( + compilation=compilation, + request=request, + cell=cell, + budget=active_budget, + ) + classification = _continuous_classification(compound, visibility) + point_cell = _continuous_point_cell(cell) + if ( + canonical_json_bytes(point_cell) != canonical_json_bytes(cell) + and classification + in ( + upright.UprightSE2ProofLeafDisposition.UNRESOLVED, + None, + ) + ): + point_evaluations, point_compound, point_visibility = ( + _evaluate_continuous_owner_cell( + compilation=compilation, + request=request, + cell=point_cell, + budget=active_budget, + ) + ) + point_classification = _continuous_classification( + point_compound, point_visibility + ) + point_row = upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=point_cell, + owner_evaluations=point_evaluations, + leaf_disposition=None, + complete_domain_empty=None, + ) + rows.append(point_row) + stages.append(_continuous_stage_for_cell(point_cell, point_evaluations)) + if ( + point_classification + is upright.UprightSE2ProofLeafDisposition.INWARD_FEASIBLE + and point_compound is not None + and getattr(point_compound, "bounds", None) is not None + ): + parent_row, parent_stage = _continuous_incomplete_parent_row( + request=request, + cell=cell, + evaluations=owner_evaluations, + label="continuous-feasible-incomplete-witness", + ) + rows.append(parent_row) + stages.append(parent_stage) + try: + candidates.append( + _continuous_candidate_from_point( + compilation=compilation, + final_inward_cell=parent_row, + point_row=point_row, + point_bounds=point_compound.bounds, + ) + ) + except (TypeError, ValueError, ArithmeticError): + pass + return + if classification is upright.UprightSE2ProofLeafDisposition.UNRESOLVED: + row, stage = _continuous_unresolved_row( + request=request, + cell=cell, + evaluations=owner_evaluations, + label="continuous-owner-nonexact", + ) + rows.append(row) + stages.append(stage) + return + if compound is None or getattr(compound, "bounds", None) is None: + row, stage = _continuous_unresolved_row( + request=request, + cell=cell, + evaluations=owner_evaluations, + label="continuous-missing-compound-bounds", + ) + rows.append(row) + stages.append(stage) + return + bounds = compound.bounds + + if classification is upright.UprightSE2ProofLeafDisposition.OUTWARD_INFEASIBLE: + row = upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=cell, + owner_evaluations=owner_evaluations, + leaf_disposition=classification, + complete_domain_empty=True, + ) + rows.append(row) + stages.append(_continuous_stage_for_cell(cell, owner_evaluations)) + return + + if classification is None: + lower, _upper = _continuous_objective_interval(bounds) + incumbent = incumbent_upper() + if _continuous_bounds_authorize_prune( + cell_lower=lower, + incumbent_upper=incumbent, + requested_gap=requested_gap, + ): + row = upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=cell, + owner_evaluations=owner_evaluations, + leaf_disposition=upright.UprightSE2ProofLeafDisposition.PRUNED, + complete_domain_empty=False, + ) + rows.append(row) + stages.append(_continuous_stage_for_cell(cell, owner_evaluations)) + return + children = _continuous_split_exact_dyadic_cell(cell, depth=depth) + if children and depth < _MAX_CONTINUOUS_EXACT_DYADIC_REFINEMENT_DEPTH: + internal = upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=cell, + owner_evaluations=owner_evaluations, + leaf_disposition=None, + complete_domain_empty=None, + ) + rows.append(internal) + stages.append(_continuous_stage_for_cell(cell, owner_evaluations)) + for child in children: + evaluate_cell(child, depth + 1) + return + row, stage = _continuous_unresolved_row( + request=request, + cell=cell, + evaluations=owner_evaluations, + label="continuous-finite-refinement-miss", + ) + rows.append(row) + stages.append(stage) + return + assert classification is upright.UprightSE2ProofLeafDisposition.INWARD_FEASIBLE + if canonical_json_bytes(point_cell) == canonical_json_bytes(cell): + final_row = upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=cell, + owner_evaluations=owner_evaluations, + leaf_disposition=upright.UprightSE2ProofLeafDisposition.INWARD_FEASIBLE, + complete_domain_empty=False, + ) + rows.append(final_row) + stages.append(_continuous_stage_for_cell(cell, owner_evaluations)) + try: + candidates.append( + _continuous_candidate_from_point( + compilation=compilation, + final_inward_cell=final_row, + point_row=final_row, + point_bounds=bounds, + ) + ) + except (TypeError, ValueError, ArithmeticError): + # A materialization failure cannot erase exact geometry evidence, + # so retain a separate unresolved alias rather than certify it. + rows.pop() + stages.pop() + row, stage = _continuous_incomplete_parent_row( + request=request, + cell=cell, + evaluations=owner_evaluations, + label="continuous-point-materialization-incomplete", + ) + rows.append(row) + stages.append(stage) + return + + point_evaluations, point_compound, point_visibility = ( + _evaluate_continuous_owner_cell( + compilation=compilation, + request=request, + cell=point_cell, + budget=active_budget, + ) + ) + point_classification = _continuous_classification( + point_compound, point_visibility + ) + point_row = upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=point_cell, + owner_evaluations=point_evaluations, + leaf_disposition=None, + complete_domain_empty=None, + ) + rows.append(point_row) + stages.append(_continuous_stage_for_cell(point_cell, point_evaluations)) + if ( + point_classification + is not upright.UprightSE2ProofLeafDisposition.INWARD_FEASIBLE + or point_compound is None + or getattr(point_compound, "bounds", None) is None + ): + row, stage = _continuous_incomplete_parent_row( + request=request, + cell=cell, + evaluations=owner_evaluations, + label="continuous-inward-point-replay-incomplete", + ) + rows.append(row) + stages.append(stage) + return + final_row = upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=cell, + owner_evaluations=owner_evaluations, + leaf_disposition=upright.UprightSE2ProofLeafDisposition.INWARD_FEASIBLE, + complete_domain_empty=False, + ) + rows.append(final_row) + stages.append(_continuous_stage_for_cell(cell, owner_evaluations)) + try: + candidates.append( + _continuous_candidate_from_point( + compilation=compilation, + final_inward_cell=final_row, + point_row=point_row, + point_bounds=point_compound.bounds, + ) + ) + except (TypeError, ValueError, ArithmeticError): + rows.pop() + stages.pop() + row, stage = _continuous_incomplete_parent_row( + request=request, + cell=cell, + evaluations=owner_evaluations, + label="continuous-point-materialization-incomplete", + ) + rows.append(row) + stages.append(stage) + + evaluate_cell(compilation.compiled_cells[0], 0) + proof = _continuous_proof_material( + compilation, + tuple(rows), + tuple(stages), + tuple(candidates), + ) + if proof.proposal_candidates: + return _continuous_proposal_submission( + compilation, selection, proof, proof.proposal_candidates[0] + ) + leaves = tuple( + row for row in proof.evaluated_cells if row.leaf_disposition is not None + ) + if leaves and all( + row.leaf_disposition + is upright.UprightSE2ProofLeafDisposition.OUTWARD_INFEASIBLE + for row in leaves + ): + return _continuous_unsat_submission(compilation, selection, proof) + outcome_kinds = { + evaluation.outcome_kind + for row in proof.evaluated_cells + for evaluation in row.owner_evaluations + } + reason = ( + request.resource_policy.exhaustion_claim_ref + if upright.UprightSE2RetainedOwnerOutcomeKind.RESOURCE_LIMIT in outcome_kinds + else ( + _UNKNOWN_NUMERIC_REF + if upright.UprightSE2RetainedOwnerOutcomeKind.NUMERIC_GAP in outcome_kinds + else ( + _UNKNOWN_UNSUPPORTED_REF + if upright.UprightSE2RetainedOwnerOutcomeKind.UNSUPPORTED + in outcome_kinds + else _UNKNOWN_INCOMPLETE_REF + ) + ) + ) + return _continuous_unknown_from_proof(compilation, selection, proof, reason) + + +def _continuous_unsat_submission( + compilation: upright.UprightSE2ContinuousCompilation, + selection: BackendSelectionRecord, + proof: upright.UprightSE2ContinuousProofMaterial, +) -> BackendCompleteUnsatEvidence: + """Emit complete continuous UNSAT only after every proof leaf is outer-empty.""" + + envelope = _continuous_proof_envelope(compilation, selection, proof) + return BackendCompleteUnsatEvidence.seal( + semantic_problem_sha256=compilation.source_solve_request.semantic_problem_sha256, + solve_request_sha256=compilation.solve_request_sha256, + backend_selection_record_sha256=selection.backend_selection_record_sha256, + proposal_backend_ref=_CONTINUOUS_BACKEND_REF, + proposal_backend_owner_ref=upright.UPRIGHT_SE2_BACKEND_OWNER_REF, + proposal_backend_capability_ref=upright.UPRIGHT_SE2_CONTINUOUS_BACKEND_CAPABILITY_REF, + proposal_backend_build_sha256=_BACKEND_BUILD_SHA256, + proof_material=envelope, + proof_material_sha256=envelope.proof_material_sha256, + resource_usage=proof.total_resource_usage, + complete_domain_claim_definition_ref=_UNSAT_CLAIM_REF, + authorized_domain_sha256=( + compilation.endpoint_construction_recipe.translation_domain_sha256 + ), + complete_domain_coverage_artifact_sha256=( + proof.coverage_artifact.coverage_artifact_sha256 + ), + ) + + +def _continuous_proposal_submission( + compilation: upright.UprightSE2ContinuousCompilation, + selection: BackendSelectionRecord, + proof: upright.UprightSE2ContinuousProofMaterial, + selected: upright.UprightSE2ContinuousProposalCandidate, +) -> BackendProposalSubmission: + """Wrap an untrusted compiler-materialized continuous witness proposal.""" + + envelope = _continuous_proof_envelope(compilation, selection, proof) + proposal = BackendProposal.seal( + semantic_problem_sha256=compilation.source_solve_request.semantic_problem_sha256, + solve_request_sha256=compilation.solve_request_sha256, + backend_selection_record_sha256=selection.backend_selection_record_sha256, + proposal_backend_ref=_CONTINUOUS_BACKEND_REF, + proposal_backend_owner_ref=upright.UPRIGHT_SE2_BACKEND_OWNER_REF, + proposal_backend_capability_ref=upright.UPRIGHT_SE2_CONTINUOUS_BACKEND_CAPABILITY_REF, + proposal_backend_build_sha256=_BACKEND_BUILD_SHA256, + proposal_claim_definition_ref=_PROPOSAL_CLAIM_REF, + proof_material=envelope, + proof_material_sha256=envelope.proof_material_sha256, + program_sha256=selected.program.program_sha256, + after_scene_state_sha256=selected.program.after_scene_state_sha256, + objective_lower_bound=float(selected.point_objective.total_lower.as_fraction), + objective_upper_bound=float(selected.point_objective.total_upper.as_fraction), + resource_usage=proof.total_resource_usage, + ) + return BackendProposalSubmission.seal( + proposal=proposal, + backend_proposal_sha256=proposal.backend_proposal_sha256, + ) + + +def _continuous_unknown_from_proof( + compilation: upright.UprightSE2ContinuousCompilation, + selection: BackendSelectionRecord, + proof: upright.UprightSE2ContinuousProofMaterial, + reason_claim_definition_ref: str, +) -> BackendUnknownEvidence: + """Keep complete explored evidence when a continuous terminal is UNKNOWN.""" + + envelope = _continuous_proof_envelope(compilation, selection, proof) + return BackendUnknownEvidence.seal( + semantic_problem_sha256=compilation.source_solve_request.semantic_problem_sha256, + solve_request_sha256=compilation.solve_request_sha256, + backend_selection_record_sha256=selection.backend_selection_record_sha256, + proposal_backend_ref=_CONTINUOUS_BACKEND_REF, + proposal_backend_owner_ref=upright.UPRIGHT_SE2_BACKEND_OWNER_REF, + proposal_backend_capability_ref=upright.UPRIGHT_SE2_CONTINUOUS_BACKEND_CAPABILITY_REF, + proposal_backend_build_sha256=_BACKEND_BUILD_SHA256, + proof_material=envelope, + proof_material_sha256=envelope.proof_material_sha256, + resource_usage=proof.total_resource_usage, + reason_claim_definition_ref=reason_claim_definition_ref, + ) + + +def _unsat_submission( + compilation: upright.UprightSE2Compilation, + selection: BackendSelectionRecord, + proof: upright.UprightSE2ProofMaterial, +) -> BackendCompleteUnsatEvidence: + """Emit complete UNSAT only when every final exact leaf is outward-empty.""" + + envelope = _proof_envelope(compilation, selection, proof) + return BackendCompleteUnsatEvidence.seal( + semantic_problem_sha256=compilation.source_solve_request.semantic_problem_sha256, + solve_request_sha256=compilation.solve_request_sha256, + backend_selection_record_sha256=selection.backend_selection_record_sha256, + proposal_backend_ref=_BACKEND_REF, + proposal_backend_owner_ref=upright.UPRIGHT_SE2_BACKEND_OWNER_REF, + proposal_backend_capability_ref=upright.UPRIGHT_SE2_CARDINAL_BACKEND_CAPABILITY_REF, + proposal_backend_build_sha256=_BACKEND_BUILD_SHA256, + proof_material=envelope, + proof_material_sha256=envelope.proof_material_sha256, + resource_usage=proof.total_resource_usage, + complete_domain_claim_definition_ref=_UNSAT_CLAIM_REF, + authorized_domain_sha256=( + compilation.endpoint_construction_recipe.translation_domain_sha256 + ), + complete_domain_coverage_artifact_sha256=( + proof.coverage_artifact.coverage_artifact_sha256 + ), + ) + + +def _proposal_submission( + compilation: upright.UprightSE2Compilation, + selection: BackendSelectionRecord, + proof: upright.UprightSE2ProofMaterial, + selected: upright.UprightSE2ProposalCandidate, +) -> BackendProposalSubmission: + """Materialize only an inward-proven in-domain endpoint via the public recipe.""" + + envelope = _proof_envelope(compilation, selection, proof) + proposal = BackendProposal.seal( + semantic_problem_sha256=compilation.source_solve_request.semantic_problem_sha256, + solve_request_sha256=compilation.solve_request_sha256, + backend_selection_record_sha256=selection.backend_selection_record_sha256, + proposal_backend_ref=_BACKEND_REF, + proposal_backend_owner_ref=upright.UPRIGHT_SE2_BACKEND_OWNER_REF, + proposal_backend_capability_ref=upright.UPRIGHT_SE2_CARDINAL_BACKEND_CAPABILITY_REF, + proposal_backend_build_sha256=_BACKEND_BUILD_SHA256, + proposal_claim_definition_ref=_PROPOSAL_CLAIM_REF, + proof_material=envelope, + proof_material_sha256=envelope.proof_material_sha256, + program_sha256=selected.program.program_sha256, + after_scene_state_sha256=selected.program.after_scene_state_sha256, + objective_lower_bound=float(selected.point_objective.total_lower.as_fraction), + objective_upper_bound=float(selected.point_objective.total_upper.as_fraction), + resource_usage=proof.total_resource_usage, + ) + return BackendProposalSubmission.seal( + proposal=proposal, + backend_proposal_sha256=proposal.backend_proposal_sha256, + ) diff --git a/src/spatialcf/core/upright_se2_compiler.py b/src/spatialcf/core/upright_se2_compiler.py new file mode 100644 index 0000000..45d62ba --- /dev/null +++ b/src/spatialcf/core/upright_se2_compiler.py @@ -0,0 +1,5536 @@ +"""Pure exact-cardinal compilation for the Upright SE(2) M3 profile. + +This module validates the closed request/profile subset into a domain-level +compilation. It deliberately does not select an endpoint, execute a backend, +or invoke a verifier. A separate pure materializer derives an explicit +endpoint only after a caller supplies an authorized world-XY value. +""" + +from __future__ import annotations + +import math +import warnings +from dataclasses import dataclass, fields, is_dataclass +from enum import StrEnum +from fractions import Fraction + +from spatialcf.core._internal.kernels.projected_visibility import ( + FixedCardinalProjectionBoxV3, + FixedCardinalVisibilityPolicyV3, +) +from spatialcf.core._internal.kernels.so2 import ( + ContinuousYawIntervalKindV4, + SO2AtomicBudgetV2, + compile_continuous_yaw_lift_v4, +) +from spatialcf.core._internal.kernels.upright_box import ( + ClosedXYCellV3, + FixedCardinalBoxV3, + FixedCardinalCellPolicyV3, + FixedCardinalObjectiveTermV3, + SupportSurfaceV3, +) +from spatialcf.core.problem import UprightCameraContextV2_9 +from spatialcf.domain import upright_se2 as upright +from spatialcf.domain.base import ( + CanonicalModel, + FactAvailabilityV2, + FactCompletenessV2, + FactSetV2, + Quaternion, + RigidTransformV2, + UncertaintyBudgetV2, + Vec2, + Vec3, +) +from spatialcf.domain.compatibility import PlanarTranslateCompilation +from spatialcf.domain.counterfactual import ( + CounterfactualProblemIR, + CounterfactualSolveRequest, + EditProgram, + ExtensionFact, + ExtensionFactBundle, + SceneStateEnvelope, +) +from spatialcf.domain.definitions import ( + BooleanValue, + CanonicalDefinitionEnvelope, + CanonicalIdValue, + DefinitionBundle, + DigestValue, + EnumSymbolValue, + FiniteOrderedTupleValue, + FiniteRealValue, + IntegerValue, + IntervalValue, + NamedTypedValue, + RecordValue, + ReferenceValue, + TypedValue, + ValueKind, +) +from spatialcf.domain.geometry import ( + GeometryApproximationV2, + GeometryRoleV2, + UprightBox3DV2, +) +from spatialcf.domain.operators import ( + OperationArgument, + OperationInvocation, + StateDeltaManifest, + StateLeafIndex, + StateVariableRef, + TypedVariableBound, +) +from spatialcf.domain.outcomes import ( + BackendSelectionRecord, + CapabilityMismatch, + ResourceUsage, + TypedCompilationOutcome, + _ResourceUsageEntry, +) +from spatialcf.domain.predicates import ( + AfterGoal, + BeforePrecondition, + GroundedObligation, + GroundedObligationSet, + ObservationObligation, + PredicateAtom, + PreservationInvariant, +) +from spatialcf.domain.profiles import ( + ActionSpaceProfile, + BackendDescriptorBundle, + BackendRoutingPolicy, + CounterfactualSolverConfig, + ImplementationOwnerBinding, + ImplementationRegistrySnapshot, + InterventionAuthorization, + ProofPolicy, + ResourceLimit, + ResourcePolicy, + SemanticsProfile, + SolverBackendDescriptor, +) +from spatialcf.domain.scene import ( + CameraAxes, + CameraDepthConvention, + CameraDistortionModel, + CameraMatrixLayout, + CameraPixelConvention, + CanonicalScene, + ObjectSupportAssignment, + PinholeCamera, + SupportSurfaceFact, +) +from spatialcf.domain.serialization import canonical_json_bytes, canonical_sha256 + +__all__ = ( + "UprightSE2CardinalEvaluationInputs", + "UprightSE2CardinalVisibilityEvaluationInput", + "UprightSE2ContinuousEvaluationInputs", + "UprightSE2ContinuousVisibilityEvaluationInput", + "build_upright_se2_cardinal_evaluation_inputs", + "build_upright_se2_continuous_evaluation_inputs", + "build_upright_se2_retained_owner_evaluation", + "cardinal_inverse_quarter_turns", + "compile_planar_translate_m2_q0_equivalence", + "compile_upright_se2", + "compile_upright_se2_continuous", + "materialize_upright_se2_continuous_endpoint", + "materialize_upright_se2_endpoint", + "rotate_cardinal_xy", +) + + +_INPUT_FAMILY_REF = "definition:spatialcf/upright-se2/compiler-input/1.0" +_INPUT_SCHEMA_REF = "schema:spatialcf/upright-se2/compiler-input/1.0" +_STATE_FAMILY_REF = "definition:spatialcf/upright-se2/state/1.0" +_STATE_SCHEMA_REF = "schema:spatialcf/upright-se2/state/1.0" +_SCENE_SCHEMA_REF = "schema:spatialcf/canonical-scene/2.3" +_BACKEND_REF = "backend:spatialcf/upright-se2/cardinal" +_CONTINUOUS_BACKEND_REF = "backend:spatialcf/upright-se2/continuous" +_BACKEND_BUILD_SHA256 = "b" * 64 +_CHECKER_BUILD_SHA256 = "a" * 64 +_DEPENDENCY_LOCK_SHA256 = "d" * 64 +_DEFINITION_CLOSURE_REF = "definition:spatialcf/upright-se2/definition-closure/1.0" +_SOLVE_POLICY_REF = "definition:spatialcf/upright-se2/solve-policy/1.0" +_DEFINITION_KIND_REF = "definition:spatialcf/upright-se2/definition-kind/1.0" +_DERIVED_RULE_REF = "definition:spatialcf/upright-se2/derived-pose-and-facts/1.0" +_CONTINUOUS_UNAVAILABLE_REF = ( + "definition:spatialcf/upright-se2/continuous-capability-unavailable/1.0" +) +_COMPILER_INPUT_FACT_KEY = "fact-key:spatialcf/upright-se2/compiler-input" +_PIVOT_STATE_HASH_DOMAIN = "spatialcf/counterfactual/upright-se2/pivot-state/3.0" +_POSE_STATE_HASH_DOMAIN = "spatialcf/counterfactual/upright-se2/base-pose/3.0" +_UNCHANGED_LEAVES_HASH_DOMAIN = ( + "spatialcf/counterfactual/upright-se2/unchanged-leaves/3.0" +) +_RETAINED_OWNER_BOUND_SCHEMA_REF = upright.UPRIGHT_SE2_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF + +_REAL_SCHEMA_REF = "schema:spatialcf/upright-se2/finite-real/1.0" +_INTEGER_SCHEMA_REF = "schema:spatialcf/upright-se2/integer/1.0" +_ID_SCHEMA_REF = "schema:spatialcf/upright-se2/canonical-id/1.0" +_DIGEST_SCHEMA_REF = "schema:spatialcf/upright-se2/digest/1.0" +_ENUM_SCHEMA_REF = "schema:spatialcf/upright-se2/enum-symbol/1.0" +_YAW_ARGUMENT_SCHEMA_REF = "schema:spatialcf/upright-se2/yaw-argument/1.0" +_DEFINITION_CLOSURE_SCHEMA_REF = "schema:spatialcf/upright-se2/definition-closure/1.0" +_SOLVE_POLICY_CLOSURE_SCHEMA_REF = ( + "schema:spatialcf/upright-se2/solve-policy-closure/1.0" +) + +_CARDINAL_TURN_FRACTIONS = { + 0: Fraction(0), + 1: Fraction(1, 4), + 2: Fraction(-1, 2), + 3: Fraction(-1, 4), +} +_PRIMARY_ROLES = ( + "subject-world-x", + "subject-world-y", + "subject-explicit-yaw", +) +_DERIVED_ROLES = ( + "subject-derived-canonical-pose", + "subject-derived-collision", + "subject-derived-support", + "subject-derived-relation", + "subject-derived-visibility", +) +_SOURCE_FIELDS = ( + ("objects", "object_id"), + ("geometry-instances", "geometry_id"), + ("collision-bodies", "body_id"), + ("workspace-boundaries", "fact_id"), + ("known-free-spaces", "fact_id"), + ("support-surfaces", "surface_id"), + ("cameras", "camera_id"), + ("baseline-observations", "observation_id"), +) +_WORLD_XY_FRAME_REF = "definition:spatialcf/upright-se2/world-xy/1.0" +_METRE_UNIT_REF = "definition:spatialcf/upright-se2/metre/1.0" +_CLOSED_INTERVAL_TOPOLOGY_REF = "definition:spatialcf/upright-se2/closed-interval/1.0" + + +@dataclass(frozen=True) +class _CompilerInput: + """The fully typed, profile-owned compiler-input fact payload.""" + + operation_kind: str + operator_ref: str + subject_id: str + reference_id: str + subject_yaw_turns: float + yaw_argument: upright.CardinalYaw | upright.ContinuousYawDomain + + +@dataclass(frozen=True) +class _SceneAuthority: + """Named, scene-owned object pivots and complete source state.""" + + scene: CanonicalScene + subject: object + reference: object + + +@dataclass(frozen=True, slots=True) +class UprightSE2CardinalVisibilityEvaluationInput: + """One exact retained projected-visibility invocation input. + + The bridge supplies this immutable record to both future proposal and + fresh-checker owners. It contains no budget instance or result: callers + create their own shared retained-kernel ledger before invoking an owner. + """ + + observation_id: str + context: UprightCameraContextV2_9 + cell: tuple[Fraction, Fraction, Fraction, Fraction] + subject: FixedCardinalProjectionBoxV3 + moving_subject_id: str + occluders: tuple[FixedCardinalProjectionBoxV3, ...] + required_occluder_ids: tuple[str, ...] + policy: FixedCardinalVisibilityPolicyV3 + + def __post_init__(self) -> None: + if ( + type(self.observation_id) is not str + or not self.observation_id + or type(self.context) is not UprightCameraContextV2_9 + or type(self.cell) is not tuple + or len(self.cell) != 4 + or any(type(value) is not Fraction for value in self.cell) + or self.cell[0] > self.cell[1] + or self.cell[2] > self.cell[3] + or type(self.subject) is not FixedCardinalProjectionBoxV3 + or type(self.moving_subject_id) is not str + or not self.moving_subject_id + or type(self.occluders) is not tuple + or not self.occluders + or any( + type(box) is not FixedCardinalProjectionBoxV3 for box in self.occluders + ) + or tuple(box.box_id for box in self.occluders) + != tuple(sorted(box.box_id for box in self.occluders)) + or type(self.required_occluder_ids) is not tuple + or self.required_occluder_ids != tuple(box.box_id for box in self.occluders) + or self.subject.box_id not in self.required_occluder_ids + or self.moving_subject_id not in self.required_occluder_ids + or type(self.policy) is not FixedCardinalVisibilityPolicyV3 + ): + raise ValueError( + "cardinal visibility bridge input must be complete and exact" + ) + + +@dataclass(frozen=True, slots=True) +class UprightSE2CardinalEvaluationInputs: + """The complete immutable retained-kernel input bundle for one M3 cell.""" + + solve_request_sha256: str + semantic_closure_sha256: str + policy_bundle_sha256: str + upright_se2_compilation_sha256: str + compiled_cell_sha256: str + cell: ClosedXYCellV3 + quarter_turns_ccw: int + subject_boxes: tuple[FixedCardinalBoxV3, ...] + obstacle_boxes: tuple[FixedCardinalBoxV3, ...] + support_assignment: ObjectSupportAssignment + support_surface_fact: SupportSurfaceFact + support_surface: SupportSurfaceV3 + target_before_relation: PredicateAtom + target_after_relation: PredicateAtom + preservation_invariants: tuple[PreservationInvariant, ...] + relation: str + reference_box: FixedCardinalBoxV3 + near_far_threshold: Fraction + cell_policy: FixedCardinalCellPolicyV3 + subject_pivot_xy: tuple[Fraction, Fraction] + objective_subject_pivot_xy: tuple[Fraction, Fraction] + visibility_inputs: tuple[UprightSE2CardinalVisibilityEvaluationInput, ...] + resource_atomic_step_limit: int + + def __post_init__(self) -> None: + if ( + any( + type(value) is not str or not value + for value in ( + self.solve_request_sha256, + self.semantic_closure_sha256, + self.policy_bundle_sha256, + self.upright_se2_compilation_sha256, + self.compiled_cell_sha256, + ) + ) + or type(self.cell) is not ClosedXYCellV3 + or type(self.quarter_turns_ccw) is not int + or self.quarter_turns_ccw not in (0, 1, 2, 3) + or type(self.subject_boxes) is not tuple + or not self.subject_boxes + or any(type(box) is not FixedCardinalBoxV3 for box in self.subject_boxes) + or tuple(box.box_id for box in self.subject_boxes) + != tuple(sorted(box.box_id for box in self.subject_boxes)) + or type(self.obstacle_boxes) is not tuple + or any(type(box) is not FixedCardinalBoxV3 for box in self.obstacle_boxes) + or tuple(box.box_id for box in self.obstacle_boxes) + != tuple(sorted(box.box_id for box in self.obstacle_boxes)) + or type(self.support_assignment) is not ObjectSupportAssignment + or type(self.support_surface_fact) is not SupportSurfaceFact + or type(self.support_surface) is not SupportSurfaceV3 + or type(self.target_before_relation) is not PredicateAtom + or type(self.target_after_relation) is not PredicateAtom + or type(self.preservation_invariants) is not tuple + or not self.preservation_invariants + or any( + type(item) is not PreservationInvariant + for item in self.preservation_invariants + ) + or self.relation not in ("LEFT", "RIGHT", "FRONT", "BEHIND", "NEAR", "FAR") + or type(self.reference_box) is not FixedCardinalBoxV3 + or type(self.near_far_threshold) is not Fraction + or self.near_far_threshold < 0 + or type(self.cell_policy) is not FixedCardinalCellPolicyV3 + or self.cell_policy.visibility_cell != self.cell.canonical_bounds + or self.cell_policy.relation_symbol != self.relation + or self.cell_policy.relation_threshold != self.near_far_threshold + or type(self.subject_pivot_xy) is not tuple + or len(self.subject_pivot_xy) != 2 + or any(type(value) is not Fraction for value in self.subject_pivot_xy) + or type(self.objective_subject_pivot_xy) is not tuple + or len(self.objective_subject_pivot_xy) != 2 + or any( + type(value) is not Fraction for value in self.objective_subject_pivot_xy + ) + or type(self.visibility_inputs) is not tuple + or not self.visibility_inputs + or any( + type(item) is not UprightSE2CardinalVisibilityEvaluationInput + for item in self.visibility_inputs + ) + or tuple(item.observation_id for item in self.visibility_inputs) + != tuple(sorted(item.observation_id for item in self.visibility_inputs)) + or type(self.resource_atomic_step_limit) is not int + or self.resource_atomic_step_limit <= 0 + or self.cell_policy.atomic_step_limit != self.resource_atomic_step_limit + or any( + item.policy.atomic_step_limit != self.resource_atomic_step_limit + for item in self.visibility_inputs + ) + ): + raise ValueError( + "cardinal evaluation bridge inputs must be complete and exact" + ) + + +@dataclass(frozen=True, slots=True) +class UprightSE2ContinuousVisibilityEvaluationInput: + """One source-bound V4 visibility invocation without a kernel result. + + The continuous backend may turn these fixed source boxes into Task 6 V4 + pose-cell bounds, but it never reconstructs source facts or policy values. + Exactly one observation is accepted by the current V4 compound-owner seam; + a broader observation conjunction must be added by that semantic owner, + not silently aggregated by proposal orchestration. + """ + + observation_id: str + source_visual_box_id: str + context: UprightCameraContextV2_9 + subject: FixedCardinalBoxV3 + moving_subject_id: str + occluders: tuple[FixedCardinalBoxV3, ...] + required_occluder_ids: tuple[str, ...] + policy: FixedCardinalVisibilityPolicyV3 + + def __post_init__(self) -> None: + if ( + type(self.observation_id) is not str + or not self.observation_id + or type(self.source_visual_box_id) is not str + or not self.source_visual_box_id + or type(self.context) is not UprightCameraContextV2_9 + or type(self.subject) is not FixedCardinalBoxV3 + or type(self.moving_subject_id) is not str + or not self.moving_subject_id + or type(self.occluders) is not tuple + or not self.occluders + or any(type(box) is not FixedCardinalBoxV3 for box in self.occluders) + or tuple(box.box_id for box in self.occluders) + != tuple(sorted(box.box_id for box in self.occluders)) + or type(self.required_occluder_ids) is not tuple + or self.required_occluder_ids != tuple(box.box_id for box in self.occluders) + or self.subject.box_id not in self.required_occluder_ids + or self.moving_subject_id not in self.required_occluder_ids + or type(self.policy) is not FixedCardinalVisibilityPolicyV3 + ): + raise ValueError( + "continuous visibility bridge input must be complete and exact" + ) + + +@dataclass(frozen=True, slots=True) +class UprightSE2ContinuousEvaluationInputs: + """The compiler-owned source/policy bridge for one lifted SE(2) cell.""" + + solve_request_sha256: str + semantic_closure_sha256: str + policy_bundle_sha256: str + continuous_upright_se2_compilation_sha256: str + compiled_cell_sha256: str + cell: ClosedXYCellV3 + subject_boxes: tuple[FixedCardinalBoxV3, ...] + obstacle_boxes: tuple[FixedCardinalBoxV3, ...] + support_assignment: ObjectSupportAssignment + support_surface_fact: SupportSurfaceFact + support_surface: SupportSurfaceV3 + target_before_relation: PredicateAtom + target_after_relation: PredicateAtom + preservation_invariants: tuple[PreservationInvariant, ...] + relation: str + reference_box: FixedCardinalBoxV3 + near_far_threshold: Fraction + cell_policy: FixedCardinalCellPolicyV3 + subject_pivot_xy: tuple[Fraction, Fraction] + objective_subject_pivot_xy: tuple[Fraction, Fraction] + visibility_inputs: tuple[UprightSE2ContinuousVisibilityEvaluationInput, ...] + resource_atomic_step_limit: int + + def __post_init__(self) -> None: + if ( + any( + type(value) is not str or not value + for value in ( + self.solve_request_sha256, + self.semantic_closure_sha256, + self.policy_bundle_sha256, + self.continuous_upright_se2_compilation_sha256, + self.compiled_cell_sha256, + ) + ) + or type(self.cell) is not ClosedXYCellV3 + or type(self.subject_boxes) is not tuple + or not self.subject_boxes + or any(type(box) is not FixedCardinalBoxV3 for box in self.subject_boxes) + or tuple(box.box_id for box in self.subject_boxes) + != tuple(sorted(box.box_id for box in self.subject_boxes)) + or type(self.obstacle_boxes) is not tuple + or any(type(box) is not FixedCardinalBoxV3 for box in self.obstacle_boxes) + or tuple(box.box_id for box in self.obstacle_boxes) + != tuple(sorted(box.box_id for box in self.obstacle_boxes)) + or type(self.support_assignment) is not ObjectSupportAssignment + or type(self.support_surface_fact) is not SupportSurfaceFact + or type(self.support_surface) is not SupportSurfaceV3 + or type(self.target_before_relation) is not PredicateAtom + or type(self.target_after_relation) is not PredicateAtom + or type(self.preservation_invariants) is not tuple + or not self.preservation_invariants + or any( + type(item) is not PreservationInvariant + for item in self.preservation_invariants + ) + or self.relation not in ("LEFT", "RIGHT", "FRONT", "BEHIND", "NEAR", "FAR") + or type(self.reference_box) is not FixedCardinalBoxV3 + or type(self.near_far_threshold) is not Fraction + or self.near_far_threshold < 0 + or type(self.cell_policy) is not FixedCardinalCellPolicyV3 + or self.cell_policy.visibility_cell != self.cell.canonical_bounds + or self.cell_policy.relation_symbol != self.relation + or self.cell_policy.relation_threshold != self.near_far_threshold + or type(self.subject_pivot_xy) is not tuple + or len(self.subject_pivot_xy) != 2 + or any(type(value) is not Fraction for value in self.subject_pivot_xy) + or type(self.objective_subject_pivot_xy) is not tuple + or len(self.objective_subject_pivot_xy) != 2 + or any( + type(value) is not Fraction for value in self.objective_subject_pivot_xy + ) + or type(self.visibility_inputs) is not tuple + or len(self.visibility_inputs) != 1 + or any( + type(item) is not UprightSE2ContinuousVisibilityEvaluationInput + for item in self.visibility_inputs + ) + or type(self.resource_atomic_step_limit) is not int + or self.resource_atomic_step_limit <= 0 + or self.cell_policy.atomic_step_limit != self.resource_atomic_step_limit + or any( + item.policy.atomic_step_limit != self.resource_atomic_step_limit + for item in self.visibility_inputs + ) + ): + raise ValueError( + "continuous evaluation bridge inputs must be complete and exact" + ) + + +def build_upright_se2_retained_owner_evaluation( + *, + compiled_cell: upright.UprightSE2CompiledCell, + owner_ref: str, + evaluator_capability_ref: str, + outcome_kind: upright.UprightSE2RetainedOwnerOutcomeKind, + raw_proof_rows: tuple[str, ...], + raw_findings: tuple[str, ...], + atomic_steps: int, + resource_delta: ResourceUsage, + label: str, + exact_bound_value: object | None = None, + additional_exact_bounds: tuple[TypedValue, ...] = (), + continuous_exact_rationals: bool = False, +) -> upright.UprightSE2RetainedOwnerEvaluation: + """Serialize one already-produced retained-owner DTO into proof rows. + + This intentionally performs no scene reconstruction, kernel invocation, + search, checker dispatch, certificate work, or terminal assembly. Both + proposal and fresh-checker owners feed their retained DTO fields through + this one canonical proof-row transport seam. + """ + + if ( + type(compiled_cell) is not upright.UprightSE2CompiledCell + or type(owner_ref) is not str + or not owner_ref + or type(evaluator_capability_ref) is not str + or not evaluator_capability_ref + or type(outcome_kind) is not upright.UprightSE2RetainedOwnerOutcomeKind + or type(raw_proof_rows) is not tuple + or any(type(row) is not str for row in raw_proof_rows) + or type(raw_findings) is not tuple + or any(type(finding) is not str for finding in raw_findings) + or type(atomic_steps) is not int + or atomic_steps < 0 + or type(resource_delta) is not ResourceUsage + or type(label) is not str + or not label + or type(additional_exact_bounds) is not tuple + or any(type(value) is not TypedValue for value in additional_exact_bounds) + or type(continuous_exact_rationals) is not bool + ): + raise TypeError("retained owner proof transport inputs must be exact") + if outcome_kind is upright.UprightSE2RetainedOwnerOutcomeKind.EXACT: + if exact_bound_value is None: + raise ValueError("exact retained owner outcome requires bounds") + bound_node = ( + _typed_continuous_bound_node + if continuous_exact_rationals + else _typed_bound_node + ) + exact_bounds = tuple( + sorted( + (bound_node(exact_bound_value), *additional_exact_bounds), + key=canonical_json_bytes, + ) + ) + finding_codes: tuple[str, ...] = () + else: + exact_bounds = () + digest = canonical_sha256( + (label, outcome_kind.value, raw_findings), + domain="spatialcf/counterfactual/upright-se2/proof-finding/3.0", + ) + finding_codes = ( + "finding:spatialcf/upright-se2/proof-transport/" + + f"{outcome_kind.value.lower()}/{digest}", + ) + proof_rows = tuple( + sorted( + { + _canonical_proof_row(label, row) + for row in (*raw_proof_rows, f"proof:spatialcf/upright-se2/{label}") + }, + key=canonical_json_bytes, + ) + ) + return upright.UprightSE2RetainedOwnerEvaluation.seal( + compiled_cell=compiled_cell, + owner_ref=owner_ref, + evaluator_capability_ref=evaluator_capability_ref, + outcome_kind=outcome_kind, + exact_bounds=exact_bounds, + finding_codes=finding_codes, + proof_rows=proof_rows, + resource_delta=resource_delta, + ) + + +def _typed_bound_node(value: object) -> TypedValue: + """Encode retained-owner bounds without interpreting their geometry.""" + + return _typed_bound_node_with_rational_mode(value, structural_rationals=False) + + +def _typed_continuous_bound_node(value: object) -> TypedValue: + """Encode continuous retained bounds without length-limiting exact rationals.""" + + return _typed_bound_node_with_rational_mode(value, structural_rationals=True) + + +def _typed_bound_node_with_rational_mode( + value: object, + *, + structural_rationals: bool, +) -> TypedValue: + """Transport retained DTOs while preserving the cardinal scalar wire by default.""" + + if isinstance(value, StrEnum): + payload = CanonicalIdValue(value=value.value) + elif type(value) is Fraction: + fraction_id = f"exact-rational:{value.numerator}/{value.denominator}" + if structural_rationals and len(fraction_id) > 512: + payload = RecordValue( + fields=tuple( + sorted( + ( + NamedTypedValue( + name="denominator", + value=TypedValue( + value_schema_ref=_RETAINED_OWNER_BOUND_SCHEMA_REF, + payload=IntegerValue(value=value.denominator), + ), + ), + NamedTypedValue( + name="numerator", + value=TypedValue( + value_schema_ref=_RETAINED_OWNER_BOUND_SCHEMA_REF, + payload=IntegerValue(value=value.numerator), + ), + ), + ), + key=lambda field: canonical_json_bytes(field.name), + ) + ) + ) + else: + payload = CanonicalIdValue(value=fraction_id) + elif type(value) is bool: + payload = BooleanValue(value=value) + elif type(value) is int: + payload = IntegerValue(value=value) + elif type(value) is float: + payload = FiniteRealValue(value=0.0 if value == 0.0 else value) + elif isinstance(value, str): + payload = CanonicalIdValue(value=value) + elif value is None: + payload = EnumSymbolValue(symbol="NULL") + elif type(value) in (tuple, list): + payload = FiniteOrderedTupleValue( + element_schema_ref=_RETAINED_OWNER_BOUND_SCHEMA_REF, + items=tuple( + _typed_bound_node_with_rational_mode( + item, structural_rationals=structural_rationals + ) + for item in value + ), + ) + elif type(value) is dict: + if any(type(name) is not str for name in value): + raise TypeError("retained bound records require string field names") + payload = RecordValue( + fields=tuple( + sorted( + ( + NamedTypedValue( + name=name, + value=_typed_bound_node_with_rational_mode( + item, structural_rationals=structural_rationals + ), + ) + for name, item in value.items() + ), + key=canonical_json_bytes, + ) + ) + ) + elif isinstance(value, CanonicalModel): + return _typed_bound_node_with_rational_mode( + value.model_dump(mode="python", round_trip=True), + structural_rationals=structural_rationals, + ) + elif is_dataclass(value): + payload = RecordValue( + fields=tuple( + sorted( + ( + NamedTypedValue( + name=field.name, + value=_typed_bound_node_with_rational_mode( + getattr(value, field.name), + structural_rationals=structural_rationals, + ), + ) + for field in fields(value) + ), + key=canonical_json_bytes, + ) + ) + ) + else: + raise TypeError(f"unsupported retained bound value {type(value).__name__}") + return TypedValue( + value_schema_ref=_RETAINED_OWNER_BOUND_SCHEMA_REF, payload=payload + ) + + +def _canonical_proof_row(label: str, raw: str) -> str: + if raw and not any(character.isspace() for character in raw): + return raw + digest = canonical_sha256( + (label, raw), + domain="spatialcf/counterfactual/upright-se2/proof-row/3.0", + ) + return f"proof:spatialcf/upright-se2/{label}/{digest}" + + +def compile_upright_se2( + solve_request: CounterfactualSolveRequest, +) -> ( + upright.UprightSE2Compilation + | upright.UprightSE2ContinuousCompilation + | TypedCompilationOutcome +): + """Compile one closed M3 request without solving or checking it. + + Cardinal compilation preserves its retained wire exactly. A registered + continuous request dispatches to the additive continuous compiler sibling; + malformed or incompatible wires raise before an outcome can be built. + """ + + _require_exact_round_trip( + solve_request, CounterfactualSolveRequest, "solve request" + ) + registration = _registered_profile() + compiler_input, authority, executable_policy_bundle = ( + _validate_problem_and_extract_input( + solve_request, + registration, + ) + ) + _validate_operational_closure( + solve_request, + registration, + operation_kind=compiler_input.operation_kind, + ) + translation_domain = _validate_intervention_authorization( + solve_request, + compiler_input, + authority, + ) + base_yaw = _validate_explicit_pose_yaw(compiler_input, solve_request, authority) + pivot_binding = _resolve_pivot_binding(compiler_input, solve_request, authority) + + if compiler_input.operation_kind == "CONTINUOUS": + return _compile_continuous( + solve_request, + registration=registration, + compiler_input=compiler_input, + authority=authority, + executable_policy_bundle=executable_policy_bundle, + translation_domain=translation_domain, + base_yaw=base_yaw, + pivot_binding=pivot_binding, + ) + + if type(compiler_input.yaw_argument) is not upright.CardinalYaw: + raise ValueError("cardinal request must carry a cardinal yaw argument") + authorization = upright.CardinalYawAuthorization.seal( + subject_id=compiler_input.subject_id, + operator_ref=compiler_input.operator_ref, + pivot_binding=pivot_binding, + yaw=compiler_input.yaw_argument, + ) + return _compile_cardinal( + solve_request=solve_request, + registration=registration, + compiler_input=compiler_input, + authority=authority, + base_yaw=base_yaw, + translation_domain=translation_domain, + authorization=authorization, + executable_policy_bundle=executable_policy_bundle, + ) + + +def build_upright_se2_cardinal_evaluation_inputs( + compilation: upright.UprightSE2Compilation, + compiled_cell: upright.UprightSE2CompiledCell, +) -> UprightSE2CardinalEvaluationInputs: + """Construct one sealed cell's retained-owner inputs without evaluating it. + + This is intentionally the sole public scene/policy-to-kernel bridge for + the cardinal M3 route. It replays the compiler's source closure before + translating its exact scene facts to the retained DTOs, but it does not + create a resource ledger, call a retained owner, or select a point. + """ + + _require_exact_round_trip( + compilation, + upright.UprightSE2Compilation, + "upright se2 compilation", + ) + _require_exact_round_trip( + compiled_cell, + upright.UprightSE2CompiledCell, + "compiled cell", + ) + member = _bridge_compiled_cell_member(compilation, compiled_cell) + replayed = compile_upright_se2(compilation.source_solve_request) + if type(replayed) is not upright.UprightSE2Compilation: + raise ValueError("cardinal bridge source did not replay to a compilation") + _bridge_require_replayed_compilation(compilation, replayed) + + registration = _registered_profile() + compiler_input, authority, policy_bundle = _validate_problem_and_extract_input( + compilation.source_solve_request, + registration, + ) + _validate_operational_closure(compilation.source_solve_request, registration) + translation_domain = _validate_intervention_authorization( + compilation.source_solve_request, + compiler_input, + authority, + ) + _validate_explicit_pose_yaw( + compiler_input, + compilation.source_solve_request, + authority, + ) + pivot_binding = _resolve_pivot_binding( + compiler_input, + compilation.source_solve_request, + authority, + ) + if ( + compiler_input.operation_kind != "CARDINAL" + or type(compiler_input.yaw_argument) is not upright.CardinalYaw + ): + raise ValueError("cardinal bridge requires one registered cardinal source") + authorization = upright.CardinalYawAuthorization.seal( + subject_id=compiler_input.subject_id, + operator_ref=compiler_input.operator_ref, + pivot_binding=pivot_binding, + yaw=compiler_input.yaw_argument, + ) + if ( + canonical_json_bytes(compilation.operation.authorization) + != canonical_json_bytes(authorization) + or compilation.operation.translation_domain != translation_domain + or compilation.operation.pivot_binding != pivot_binding + ): + raise ValueError("cardinal bridge compilation operation does not close") + _bridge_validate_cell_yaw(member, compilation.operation.quarter_turns_ccw) + + scene = authority.scene + camera = _bridge_exact_identity_camera(scene) + objects = _bridge_object_map(scene) + subject = objects.get(compiler_input.subject_id) + reference = objects.get(compiler_input.reference_id) + if subject is None or reference is None: + raise ValueError("cardinal bridge scene authority is incomplete") + subject_boxes, obstacle_boxes = _bridge_collision_boxes( + scene, + subject_id=compiler_input.subject_id, + support_surface_id=subject.support_assignment.surface_id, + objects=objects, + ) + _bridge_validate_subject_role_geometry_closure( + scene, + subject_id=compiler_input.subject_id, + subject_boxes=subject_boxes, + objects=objects, + ) + support_surface_fact = upright.validate_required_upright_support_surface( + scene, + compiler_input.subject_id, + ) + support_surface = _bridge_support_surface(support_surface_fact, objects) + target_before_relation, target_after_relation, relation = ( + _bridge_target_and_preservation_rows( + compilation.source_solve_request.semantic_problem, + subject_id=compiler_input.subject_id, + reference_id=compiler_input.reference_id, + ) + ) + reference_box = _bridge_reference_relation_box( + scene, + reference_id=compiler_input.reference_id, + objects=objects, + ) + cell = ClosedXYCellV3( + member.x_lower.as_fraction, + member.x_upper.as_fraction, + member.y_lower.as_fraction, + member.y_upper.as_fraction, + ) + resource_cap = _bridge_resource_cap(policy_bundle) + cell_policy = _bridge_cell_policy( + policy_bundle, + relation=relation, + cell=cell, + resource_cap=resource_cap, + ) + subject_pivot_xy = _bridge_object_pivot_xy( + objects, + compilation.operation.pivot_binding.pivot_entity_id, + ) + objective_subject_pivot_xy = _bridge_object_pivot_xy( + objects, + compiler_input.subject_id, + ) + visibility_inputs = _bridge_visibility_inputs( + scene, + camera=camera, + objects=objects, + subject_id=compiler_input.subject_id, + quarter_turns_ccw=compilation.operation.quarter_turns_ccw, + pivot_xy=subject_pivot_xy, + cell=cell, + policy_bundle=policy_bundle, + resource_cap=resource_cap, + ) + return UprightSE2CardinalEvaluationInputs( + solve_request_sha256=compilation.solve_request_sha256, + semantic_closure_sha256=compilation.semantic_closure.semantic_closure_sha256, + policy_bundle_sha256=compilation.semantic_closure.policy_bundle_sha256, + upright_se2_compilation_sha256=compilation.upright_se2_compilation_sha256, + compiled_cell_sha256=member.compiled_cell_sha256, + cell=cell, + quarter_turns_ccw=compilation.operation.quarter_turns_ccw, + subject_boxes=subject_boxes, + obstacle_boxes=obstacle_boxes, + support_assignment=subject.support_assignment, + support_surface_fact=support_surface_fact, + support_surface=support_surface, + target_before_relation=target_before_relation, + target_after_relation=target_after_relation, + preservation_invariants=( + compilation.source_solve_request.semantic_problem.preservation_invariants + ), + relation=relation, + reference_box=reference_box, + near_far_threshold=cell_policy.relation_threshold, + cell_policy=cell_policy, + subject_pivot_xy=subject_pivot_xy, + objective_subject_pivot_xy=objective_subject_pivot_xy, + visibility_inputs=visibility_inputs, + resource_atomic_step_limit=resource_cap, + ) + + +def build_upright_se2_continuous_evaluation_inputs( + compilation: upright.UprightSE2ContinuousCompilation, + compiled_cell: upright.UprightSE2CompiledCell, +) -> UprightSE2ContinuousEvaluationInputs: + """Rebuild one authorized lifted cell's V4 owner inputs without evaluation. + + The bridge is the only continuous route from immutable source facts and + request-bound policy to retained-kernel DTOs. It deliberately performs no + yaw enclosure, geometry evaluation, proposal search, proof checking, or + endpoint materialization. + """ + + _require_exact_round_trip( + compilation, + upright.UprightSE2ContinuousCompilation, + "continuous upright se2 compilation", + ) + _require_exact_round_trip( + compiled_cell, + upright.UprightSE2CompiledCell, + "continuous compiled cell", + ) + member = _bridge_continuous_compiled_cell_member(compilation, compiled_cell) + replayed = compile_upright_se2(compilation.source_solve_request) + if type(replayed) is not upright.UprightSE2ContinuousCompilation: + raise ValueError("continuous bridge source did not replay to a compilation") + _bridge_require_replayed_continuous_compilation(compilation, replayed) + + registration = _registered_profile() + compiler_input, authority, policy_bundle = _validate_problem_and_extract_input( + compilation.source_solve_request, + registration, + ) + _validate_operational_closure( + compilation.source_solve_request, + registration, + operation_kind="CONTINUOUS", + ) + translation_domain = _validate_intervention_authorization( + compilation.source_solve_request, + compiler_input, + authority, + ) + _validate_explicit_pose_yaw( + compiler_input, + compilation.source_solve_request, + authority, + ) + pivot_binding = _resolve_pivot_binding( + compiler_input, + compilation.source_solve_request, + authority, + ) + if not isinstance( + compiler_input.yaw_argument, + (upright.ContinuousYawArc, upright.ContinuousYawFullCircle), + ): + raise ValueError( # noqa: TRY004 - preserves the public invalid-domain contract. + "continuous bridge requires one registered source yaw domain" + ) + authorization = upright.ContinuousYawAuthorization.seal( + subject_id=compiler_input.subject_id, + operator_ref=compiler_input.operator_ref, + pivot_binding=pivot_binding, + yaw_domain=compiler_input.yaw_argument, + ) + if ( + canonical_json_bytes(compilation.operation.authorization) + != canonical_json_bytes(authorization) + or compilation.operation.translation_domain != translation_domain + or compilation.operation.pivot_binding != pivot_binding + ): + raise ValueError("continuous bridge compilation operation does not close") + + scene = authority.scene + camera = _bridge_exact_identity_camera(scene) + objects = _bridge_object_map(scene) + subject = objects.get(compiler_input.subject_id) + reference = objects.get(compiler_input.reference_id) + if subject is None or reference is None: + raise ValueError("continuous bridge scene authority is incomplete") + subject_boxes, obstacle_boxes = _bridge_collision_boxes( + scene, + subject_id=compiler_input.subject_id, + support_surface_id=subject.support_assignment.surface_id, + objects=objects, + ) + _bridge_validate_subject_role_geometry_closure( + scene, + subject_id=compiler_input.subject_id, + subject_boxes=subject_boxes, + objects=objects, + ) + support_surface_fact = upright.validate_required_upright_support_surface( + scene, + compiler_input.subject_id, + ) + support_surface = _bridge_support_surface(support_surface_fact, objects) + target_before_relation, target_after_relation, relation = ( + _bridge_target_and_preservation_rows( + compilation.source_solve_request.semantic_problem, + subject_id=compiler_input.subject_id, + reference_id=compiler_input.reference_id, + ) + ) + reference_box = _bridge_reference_relation_box( + scene, + reference_id=compiler_input.reference_id, + objects=objects, + ) + cell = ClosedXYCellV3( + member.x_lower.as_fraction, + member.x_upper.as_fraction, + member.y_lower.as_fraction, + member.y_upper.as_fraction, + ) + resource_cap = _bridge_resource_cap(policy_bundle) + cell_policy = _bridge_cell_policy( + policy_bundle, + relation=relation, + cell=cell, + resource_cap=resource_cap, + ) + subject_pivot_xy = _bridge_object_pivot_xy( + objects, + compilation.operation.pivot_binding.pivot_entity_id, + ) + objective_subject_pivot_xy = _bridge_object_pivot_xy( + objects, + compiler_input.subject_id, + ) + visibility_inputs = _bridge_continuous_visibility_inputs( + scene, + camera=camera, + objects=objects, + subject_id=compiler_input.subject_id, + visibility_subject_box_id=subject_boxes[0].box_id, + policy_bundle=policy_bundle, + resource_cap=resource_cap, + ) + return UprightSE2ContinuousEvaluationInputs( + solve_request_sha256=compilation.solve_request_sha256, + semantic_closure_sha256=compilation.semantic_closure.semantic_closure_sha256, + policy_bundle_sha256=compilation.semantic_closure.policy_bundle_sha256, + continuous_upright_se2_compilation_sha256=( + compilation.continuous_upright_se2_compilation_sha256 + ), + compiled_cell_sha256=member.compiled_cell_sha256, + cell=cell, + subject_boxes=subject_boxes, + obstacle_boxes=obstacle_boxes, + support_assignment=subject.support_assignment, + support_surface_fact=support_surface_fact, + support_surface=support_surface, + target_before_relation=target_before_relation, + target_after_relation=target_after_relation, + preservation_invariants=( + compilation.source_solve_request.semantic_problem.preservation_invariants + ), + relation=relation, + reference_box=reference_box, + near_far_threshold=cell_policy.relation_threshold, + cell_policy=cell_policy, + subject_pivot_xy=subject_pivot_xy, + objective_subject_pivot_xy=objective_subject_pivot_xy, + visibility_inputs=visibility_inputs, + resource_atomic_step_limit=resource_cap, + ) + + +def _bridge_compiled_cell_member( + compilation: upright.UprightSE2Compilation, + compiled_cell: upright.UprightSE2CompiledCell, +) -> upright.UprightSE2CompiledCell: + """Return one authorized root or its proper exact-dyadic descendant. + + The compilation roster is the sole request-authorized root set. A backend + may name deterministic refinement cells, but cannot alter a root's + authorization, cardinal yaw, domain, or identity by doing so. + """ + + matching = tuple( + cell + for cell in compilation.compiled_cells + if canonical_json_bytes(cell) == canonical_json_bytes(compiled_cell) + ) + if len(matching) == 1: + return matching[0] + if matching: + raise ValueError("compiled cell matches more than one compilation root") + + roots = tuple( + root + for root in compilation.compiled_cells + if _bridge_is_proper_exact_dyadic_descendant(compiled_cell, root) + ) + if len(roots) != 1: + raise ValueError( + "compiled cell must be one exact compilation root or its unique exact-dyadic descendant" + ) + return compiled_cell + + +def _bridge_continuous_compiled_cell_member( + compilation: upright.UprightSE2ContinuousCompilation, + compiled_cell: upright.UprightSE2CompiledCell, +) -> upright.UprightSE2CompiledCell: + """Accept only the sole root or one exact lifted `(x,y,u)` descendant.""" + + root = compilation.compiled_cells[0] + if canonical_json_bytes(root) == canonical_json_bytes(compiled_cell): + return root + if not _bridge_is_proper_exact_dyadic_continuous_descendant(compiled_cell, root): + raise ValueError( + "continuous compiled cell must be its root or a unique exact-dyadic lifted descendant" + ) + return compiled_cell + + +def _bridge_is_proper_exact_dyadic_continuous_descendant( + cell: upright.UprightSE2CompiledCell, + root: upright.UprightSE2CompiledCell, +) -> bool: + """Require a strict closed descendant without changing root authorization.""" + + if ( + not cell.cell_id.startswith(f"{root.cell_id}/") + or cell.authorization_sha256 != root.authorization_sha256 + or cell.x_lower.as_fraction < root.x_lower.as_fraction + or cell.x_upper.as_fraction > root.x_upper.as_fraction + or cell.y_lower.as_fraction < root.y_lower.as_fraction + or cell.y_upper.as_fraction > root.y_upper.as_fraction + or cell.yaw_interval.lower.as_fraction < root.yaw_interval.lower.as_fraction + or cell.yaw_interval.upper.as_fraction > root.yaw_interval.upper.as_fraction + ): + return False + return ( + cell.x_lower != root.x_lower + or cell.x_upper != root.x_upper + or cell.y_lower != root.y_lower + or cell.y_upper != root.y_upper + or cell.yaw_interval.lower != root.yaw_interval.lower + or cell.yaw_interval.upper != root.yaw_interval.upper + ) + + +def _bridge_is_proper_exact_dyadic_descendant( + cell: upright.UprightSE2CompiledCell, + root: upright.UprightSE2CompiledCell, +) -> bool: + """Require a strict, yaw-bound XY refinement of exactly one root.""" + + if ( + not cell.cell_id.startswith(f"{root.cell_id}/") + or cell.authorization_sha256 != root.authorization_sha256 + or cell.yaw_interval != root.yaw_interval + or cell.x_lower.as_fraction < root.x_lower.as_fraction + or cell.x_upper.as_fraction > root.x_upper.as_fraction + or cell.y_lower.as_fraction < root.y_lower.as_fraction + or cell.y_upper.as_fraction > root.y_upper.as_fraction + ): + return False + return ( + cell.x_lower != root.x_lower + or cell.x_upper != root.x_upper + or cell.y_lower != root.y_lower + or cell.y_upper != root.y_upper + ) + + +def _bridge_require_replayed_compilation( + compilation: upright.UprightSE2Compilation, + replayed: upright.UprightSE2Compilation, +) -> None: + """Require source replay to reproduce every normal compiler-owned root. + + A retained M2 q=0 construction adds one provenance record after normal M3 + compilation, so that optional construction root is deliberately checked by + the source model itself rather than compared to a direct replay. + """ + + for field_name in ( + "solve_request_sha256", + "source_solve_request", + "closure", + "operation", + "endpoint_construction_recipe", + "state_footprint", + "grounded_obligations", + "semantic_closure", + "compiled_cells", + ): + if canonical_json_bytes( + getattr(compilation, field_name) + ) != canonical_json_bytes(getattr(replayed, field_name)): + raise ValueError( + "cardinal bridge compilation root or policy closure does not replay" + ) + + +def _bridge_require_replayed_continuous_compilation( + compilation: upright.UprightSE2ContinuousCompilation, + replayed: upright.UprightSE2ContinuousCompilation, +) -> None: + """Require source replay to reproduce the additive continuous root exactly.""" + + for field_name in ( + "solve_request_sha256", + "source_solve_request", + "closure", + "operation", + "endpoint_construction_recipe", + "state_footprint", + "grounded_obligations", + "semantic_closure", + "compiled_cells", + "continuous_yaw_lift", + ): + if canonical_json_bytes( + getattr(compilation, field_name) + ) != canonical_json_bytes(getattr(replayed, field_name)): + raise ValueError( + "continuous bridge compilation root or policy closure does not replay" + ) + + +def _bridge_validate_cell_yaw( + cell: upright.UprightSE2CompiledCell, + quarter_turns_ccw: int, +) -> None: + expected = _CARDINAL_TURN_FRACTIONS.get(quarter_turns_ccw) + if expected is None or ( + cell.yaw_interval.lower.as_fraction, + cell.yaw_interval.upper.as_fraction, + cell.yaw_interval.seam_ownership, + ) != (expected, expected, "NONE"): + raise ValueError("compiled cell yaw interval does not bind the operation") + + +def _bridge_object_map(scene: CanonicalScene) -> dict[str, object]: + objects = _known_exact_source_values(scene.objects, "objects") + result = {object_.object_id: object_ for object_ in objects} + if len(result) != len(objects): + raise ValueError("cardinal bridge object roster is not unique") + return result + + +def _bridge_exact_identity_camera(scene: CanonicalScene) -> PinholeCamera: + """Return the sole retained-core camera subset whose XY frame is world XY.""" + + cameras = _known_exact_source_values(scene.cameras, "cameras") + if scene.cameras.uncertainty != UncertaintyBudgetV2() or len(cameras) != 1: + raise ValueError("cardinal bridge requires one exact fixed camera") + camera = cameras[0] + if type(camera) is not PinholeCamera: + raise ValueError("cardinal bridge camera must use the exact pinhole model") + if ( + camera.distortion_model is not CameraDistortionModel.NONE + or camera.brown_conrady_coefficients is not None + ): + raise ValueError("cardinal bridge camera distortion must be NONE") + if camera.calibration_uncertainty != UncertaintyBudgetV2(): + raise ValueError("cardinal bridge camera calibration must be exact") + if ( + camera.matrix_layout is not CameraMatrixLayout.ROW_MAJOR + or camera.camera_axes is not CameraAxes.X_RIGHT_Y_DOWN_Z_FORWARD + or camera.pixel_convention is not CameraPixelConvention.CENTER_AT_HALF + or camera.depth_convention is not CameraDepthConvention.POSITIVE_Z_FORWARD + ): + raise ValueError("cardinal bridge camera convention is unsupported") + intrinsics = camera.intrinsics_row_major + if ( + intrinsics[1] != 0.0 + or intrinsics[3] != 0.0 + or intrinsics[6:] != (0.0, 0.0, 1.0) + ): + raise ValueError("cardinal bridge camera intrinsics are noncanonical") + rotation = camera.world_to_camera.rotation + if type(rotation) is not Quaternion or ( + rotation.x, + rotation.y, + rotation.z, + rotation.w, + ) != (0.0, 0.0, 0.0, 1.0): + raise ValueError("cardinal bridge camera must use identity orientation") + return camera + + +def _bridge_cardinal_yaw(rotation: Quaternion, *, label: str) -> int: + """Decode only a source yaw already representable by the fixed DTOs.""" + + if type(rotation) is not Quaternion or rotation.x != 0.0 or rotation.y != 0.0: + raise ValueError(f"{label} must be a cardinal upright rotation") + if rotation.z == 0.0: + return 0 + if rotation.z == rotation.w: + return 1 + if rotation.w == 0.0: + return 2 + if rotation.z == -rotation.w: + return 3 + raise ValueError(f"{label} is unsupported by the fixed cardinal DTO bridge") + + +def _bridge_fraction(value: float, *, label: str) -> Fraction: + if type(value) is not float or not math.isfinite(value): + raise ValueError(f"{label} must be one finite exact binary64 source value") + return Fraction.from_float(value) + + +def _bridge_world_box_parts( + geometry: object, + objects: dict[str, object], +) -> tuple[Fraction, Fraction, Fraction, Fraction, Fraction, Fraction, int]: + """Translate one exact upright source box to fixed world-box components.""" + + if ( + type(getattr(geometry, "shape", None)) is not UprightBox3DV2 + or getattr(geometry, "approximation", None) is not GeometryApproximationV2.EXACT + ): + raise ValueError("geometry is unsupported by the exact fixed-box bridge") + anchor = geometry.anchor_from_geometry + anchor_q = _bridge_cardinal_yaw( + anchor.rotation, + label=f"geometry {geometry.geometry_id} anchor rotation", + ) + owner_id = geometry.owner_object_id + if owner_id is None: + owner_q = 0 + owner_x = Fraction() + owner_y = Fraction() + owner_z = Fraction() + else: + owner = objects.get(owner_id) + if owner is None: + raise ValueError("geometry owner is absent from the exact object roster") + owner_pose = owner.pose.world_from_object + owner_q = _bridge_cardinal_yaw( + owner_pose.rotation, + label=f"geometry {geometry.geometry_id} owner rotation", + ) + owner_x = _bridge_fraction( + owner_pose.translation.x, + label=f"geometry {geometry.geometry_id} owner x", + ) + owner_y = _bridge_fraction( + owner_pose.translation.y, + label=f"geometry {geometry.geometry_id} owner y", + ) + owner_z = _bridge_fraction( + owner_pose.translation.z, + label=f"geometry {geometry.geometry_id} owner z", + ) + anchor_x = _bridge_fraction( + anchor.translation.x, + label=f"geometry {geometry.geometry_id} anchor x", + ) + anchor_y = _bridge_fraction( + anchor.translation.y, + label=f"geometry {geometry.geometry_id} anchor y", + ) + relative_x, relative_y = _rotate_cardinal_xy_components(anchor_x, anchor_y, owner_q) + total_q = (owner_q + anchor_q) % 4 + half_x = ( + _bridge_fraction( + geometry.shape.size_m.x, + label=f"geometry {geometry.geometry_id} size x", + ) + / 2 + ) + half_y = ( + _bridge_fraction( + geometry.shape.size_m.y, + label=f"geometry {geometry.geometry_id} size y", + ) + / 2 + ) + if total_q % 2: + half_x, half_y = half_y, half_x + return ( + owner_x + relative_x, + owner_y + relative_y, + owner_z + + _bridge_fraction( + anchor.translation.z, + label=f"geometry {geometry.geometry_id} anchor z", + ), + half_x, + half_y, + _bridge_fraction( + geometry.shape.size_m.z, + label=f"geometry {geometry.geometry_id} size z", + ) + / 2, + total_q, + ) + + +def _bridge_fixed_box( + geometry: object, + objects: dict[str, object], +) -> FixedCardinalBoxV3: + center_x, center_y, center_z, half_x, half_y, half_z, _ = _bridge_world_box_parts( + geometry, + objects, + ) + return FixedCardinalBoxV3( + box_id=geometry.geometry_id, + center_x=center_x, + center_y=center_y, + center_z=center_z, + half_x=half_x, + half_y=half_y, + half_z=half_z, + ) + + +def _bridge_collision_boxes( + scene: CanonicalScene, + *, + subject_id: str, + support_surface_id: str | None, + objects: dict[str, object], +) -> tuple[tuple[FixedCardinalBoxV3, ...], tuple[FixedCardinalBoxV3, ...]]: + if support_surface_id is None: + raise ValueError("cardinal bridge subject support assignment is incomplete") + geometries = _known_exact_source_values( + scene.geometry_instances, "geometry instances" + ) + geometry_by_id = {geometry.geometry_id: geometry for geometry in geometries} + if len(geometry_by_id) != len(geometries): + raise ValueError("cardinal bridge geometry roster is not unique") + bodies = _known_exact_source_values(scene.collision_bodies, "collision bodies") + support_sources = tuple( + surface + for surface in _known_exact_source_values( + scene.support_surfaces, "support surfaces" + ) + if surface.surface_id == support_surface_id + ) + if len(support_sources) != 1: + raise ValueError("cardinal bridge support surface is absent or duplicated") + supporting_body_id = support_sources[0].supporting_body_id + subject_geometry_ids: list[str] = [] + obstacle_geometry_ids: list[str] = [] + referenced_geometry_ids: list[str] = [] + for body in bodies: + ids = body.geometry_instance_ids + if not ids: + raise ValueError("cardinal bridge collision body must name geometry") + for geometry_id in ids: + geometry = geometry_by_id.get(geometry_id) + if geometry is None or geometry.role is not GeometryRoleV2.COLLISION: + raise ValueError("collision body must bind exact collision geometry") + if geometry.owner_object_id != body.owner_object_id: + raise ValueError("collision body and geometry owners must agree") + referenced_geometry_ids.append(geometry_id) + if body.owner_object_id == subject_id: + subject_geometry_ids.extend(ids) + elif body.body_id != supporting_body_id: + obstacle_geometry_ids.extend(ids) + all_collision_ids = tuple( + geometry.geometry_id + for geometry in geometries + if geometry.role is GeometryRoleV2.COLLISION + ) + if tuple(sorted(referenced_geometry_ids)) != tuple(sorted(all_collision_ids)): + raise ValueError("collision bodies do not cover the complete collision roster") + if ( + not subject_geometry_ids + or len(set(subject_geometry_ids)) != len(subject_geometry_ids) + or len(set(obstacle_geometry_ids)) != len(obstacle_geometry_ids) + ): + raise ValueError("collision body roster is incomplete or duplicates a geometry") + subject_boxes = tuple( + sorted( + ( + _bridge_fixed_box(geometry_by_id[geometry_id], objects) + for geometry_id in subject_geometry_ids + ), + key=lambda box: box.box_id, + ) + ) + obstacle_boxes = tuple( + sorted( + ( + _bridge_fixed_box(geometry_by_id[geometry_id], objects) + for geometry_id in obstacle_geometry_ids + ), + key=lambda box: box.box_id, + ) + ) + return subject_boxes, obstacle_boxes + + +def _bridge_subject_role_boxes( + scene: CanonicalScene, + *, + subject_id: str, + role: GeometryRoleV2, + objects: dict[str, object], +) -> tuple[FixedCardinalBoxV3, ...]: + """Translate every exact subject geometry for one semantic role to fixed boxes.""" + + geometries = _known_exact_source_values( + scene.geometry_instances, + "geometry instances", + ) + candidates = tuple( + geometry + for geometry in geometries + if geometry.owner_object_id == subject_id and geometry.role is role + ) + if not candidates: + raise ValueError(f"subject {role.value.lower()} geometry is absent") + return tuple( + sorted( + (_bridge_fixed_box(geometry, objects) for geometry in candidates), + key=lambda box: box.box_id, + ) + ) + + +def _bridge_xy_role_signature( + boxes: tuple[FixedCardinalBoxV3, ...], +) -> tuple[tuple[Fraction, Fraction, Fraction, Fraction], ...]: + """Return the complete per-box XY inputs the retained relation owner consumes.""" + + return tuple( + sorted((box.center_x, box.center_y, box.half_x, box.half_y) for box in boxes) + ) + + +def _bridge_support_role_signature( + boxes: tuple[FixedCardinalBoxV3, ...], +) -> tuple[tuple[Fraction, Fraction, Fraction, Fraction, Fraction, Fraction], ...]: + """Return the complete per-box support footprint/contact inputs the owner uses.""" + + return tuple( + sorted( + ( + box.center_x, + box.center_y, + box.center_z, + box.half_x, + box.half_y, + box.half_z, + ) + for box in boxes + ) + ) + + +def _bridge_validate_subject_role_geometry_closure( + scene: CanonicalScene, + *, + subject_id: str, + subject_boxes: tuple[FixedCardinalBoxV3, ...], + objects: dict[str, object], +) -> None: + """Fail closed unless role-specific subject geometry preserves owner inputs.""" + + relation_boxes = _bridge_subject_role_boxes( + scene, + subject_id=subject_id, + role=GeometryRoleV2.RELATION, + objects=objects, + ) + if _bridge_xy_role_signature(relation_boxes) != _bridge_xy_role_signature( + subject_boxes + ): + raise ValueError( + "subject relation geometry must be XY-congruent with collision boxes" + ) + support_boxes = _bridge_subject_role_boxes( + scene, + subject_id=subject_id, + role=GeometryRoleV2.SUPPORT, + objects=objects, + ) + if _bridge_support_role_signature(support_boxes) != _bridge_support_role_signature( + subject_boxes + ): + raise ValueError( + "subject support geometry must be contact-congruent with collision boxes" + ) + + +def _bridge_support_surface( + surface: SupportSurfaceFact, + objects: dict[str, object], +) -> SupportSurfaceV3: + if ( + surface.region_approximation is not GeometryApproximationV2.EXACT + or surface.boundary_policy.value != "CLOSED" + or ( + surface.normal_in_anchor.x, + surface.normal_in_anchor.y, + surface.normal_in_anchor.z, + ) + != (0.0, 0.0, 1.0) + or len(surface.region_uv.components) != 1 + or surface.region_uv.components[0].holes + ): + raise ValueError("support surface is unsupported by the exact fixed-box bridge") + anchor = surface.anchor_from_surface + anchor_q = _bridge_cardinal_yaw( + anchor.rotation, + label=f"support surface {surface.surface_id} anchor rotation", + ) + if surface.owner_object_id is None: + owner_q = 0 + owner_x = Fraction() + owner_y = Fraction() + owner_z = Fraction() + else: + owner = objects.get(surface.owner_object_id) + if owner is None: + raise ValueError("support surface owner is absent from the object roster") + pose = owner.pose.world_from_object + owner_q = _bridge_cardinal_yaw( + pose.rotation, + label=f"support surface {surface.surface_id} owner rotation", + ) + owner_x = _bridge_fraction(pose.translation.x, label="support owner x") + owner_y = _bridge_fraction(pose.translation.y, label="support owner y") + owner_z = _bridge_fraction(pose.translation.z, label="support owner z") + anchor_x = _bridge_fraction(anchor.translation.x, label="support anchor x") + anchor_y = _bridge_fraction(anchor.translation.y, label="support anchor y") + translated_x, translated_y = _rotate_cardinal_xy_components( + anchor_x, anchor_y, owner_q + ) + origin_x = owner_x + translated_x + origin_y = owner_y + translated_y + total_q = (owner_q + anchor_q) % 4 + vertices = surface.region_uv.components[0].exterior.vertices + if len(vertices) != 4: + raise ValueError("support surface must be one exact axis-aligned rectangle") + world_vertices = tuple( + ( + origin_x + + _rotate_cardinal_xy_components( + _bridge_fraction(vertex.x, label="support vertex x"), + _bridge_fraction(vertex.y, label="support vertex y"), + total_q, + )[0], + origin_y + + _rotate_cardinal_xy_components( + _bridge_fraction(vertex.x, label="support vertex x"), + _bridge_fraction(vertex.y, label="support vertex y"), + total_q, + )[1], + ) + for vertex in vertices + ) + xs = tuple(sorted({vertex[0] for vertex in world_vertices})) + ys = tuple(sorted({vertex[1] for vertex in world_vertices})) + if ( + len(xs) != 2 + or len(ys) != 2 + or set(world_vertices) != {(x, y) for x in xs for y in ys} + ): + raise ValueError("support surface must remain an exact world-XY rectangle") + return SupportSurfaceV3( + x_lower=xs[0], + x_upper=xs[1], + y_lower=ys[0], + y_upper=ys[1], + z=owner_z + _bridge_fraction(anchor.translation.z, label="support anchor z"), + ) + + +def _bridge_target_and_preservation_rows( + problem: CounterfactualProblemIR, + *, + subject_id: str, + reference_id: str, +) -> tuple[PredicateAtom, PredicateAtom, str]: + if len(problem.before_preconditions) != 1: + raise ValueError("cardinal bridge requires one target before relation") + before = _direct_predicate_atom(problem.before_preconditions[0].formula) + _validate_target_relation_operands( + before, + subject_id=subject_id, + reference_id=reference_id, + phase="BEFORE", + ) + after = _direct_predicate_atom(problem.after_goal.formula) + after_relation = _validate_target_relation_operands( + after, + subject_id=subject_id, + reference_id=reference_id, + phase="AFTER", + ) + if not after_relation.startswith("relation:"): + raise ValueError("target after relation must be one registered relation") + if len(problem.preservation_invariants) != 1: + raise ValueError("cardinal bridge requires one preservation relation row") + preservation = problem.preservation_invariants[0] + for phase, formula in ( + ("BEFORE", preservation.before_formula), + ("AFTER", preservation.after_formula), + ): + _validate_preservation_operands( + _direct_predicate_atom(formula), + subject_id, + phase, + ) + return before, after, after_relation.removeprefix("relation:") + + +def _bridge_reference_relation_box( + scene: CanonicalScene, + *, + reference_id: str, + objects: dict[str, object], +) -> FixedCardinalBoxV3: + geometries = _known_exact_source_values( + scene.geometry_instances, "geometry instances" + ) + candidates = tuple( + geometry + for geometry in geometries + if ( + geometry.owner_object_id == reference_id + and geometry.role is GeometryRoleV2.RELATION + ) + ) + if len(candidates) != 1: + raise ValueError("target reference must bind one exact relation geometry") + return _bridge_fixed_box(candidates[0], objects) + + +def _bridge_policy_fields( + bundle: upright.UprightSE2ExecutablePolicyBundle, + policy_key: str, +) -> dict[str, TypedValue]: + policy = bundle.policy_for(policy_key) + payload = policy.payload + if type(payload.payload) is not RecordValue: + raise ValueError("bridge policy payload must be an exact typed record") + fields = {field.name: field.value for field in payload.payload.fields} + if len(fields) != len(payload.payload.fields): + raise ValueError("bridge policy payload has duplicate fields") + return fields + + +def _bridge_policy_real(fields: dict[str, TypedValue], name: str) -> Fraction: + value = fields.get(name) + if type(value) is not TypedValue or type(value.payload) is not FiniteRealValue: + raise ValueError(f"bridge policy field {name!r} must be a finite real") + return _bridge_fraction(value.payload.value, label=f"policy field {name}") + + +def _bridge_policy_integer(fields: dict[str, TypedValue], name: str) -> int: + value = fields.get(name) + if type(value) is not TypedValue or type(value.payload) is not IntegerValue: + raise ValueError(f"bridge policy field {name!r} must be an integer") + return value.payload.value + + +def _bridge_policy_symbol(fields: dict[str, TypedValue], name: str) -> str: + value = fields.get(name) + if type(value) is not TypedValue or type(value.payload) is not EnumSymbolValue: + raise ValueError(f"bridge policy field {name!r} must be a symbol") + return value.payload.symbol + + +def _bridge_policy_id(fields: dict[str, TypedValue], name: str) -> str: + value = fields.get(name) + if type(value) is not TypedValue or type(value.payload) is not CanonicalIdValue: + raise ValueError(f"bridge policy field {name!r} must be an identifier") + return value.payload.value + + +def _bridge_resource_cap(bundle: upright.UprightSE2ExecutablePolicyBundle) -> int: + fields = _bridge_policy_fields(bundle, "resource") + cap = _bridge_policy_integer(fields, "atomic_step_limit") + if cap <= 0: + raise ValueError("cardinal bridge resource cap must be positive") + if _bridge_policy_symbol(fields, "deterministic_order") != "LOWER_OWNED_XY": + raise ValueError("cardinal bridge resource policy order is unsupported") + return cap + + +def _bridge_cell_policy( + bundle: upright.UprightSE2ExecutablePolicyBundle, + *, + relation: str, + cell: ClosedXYCellV3, + resource_cap: int, +) -> FixedCardinalCellPolicyV3: + collision = _bridge_policy_fields(bundle, "collision") + support = _bridge_policy_fields(bundle, "support") + relation_policy = bundle.policy_for(f"relation:{relation}") + relation_fields = _bridge_policy_fields(bundle, f"relation:{relation}") + visibility = _bridge_policy_fields(bundle, "visibility") + safety = _bridge_policy_fields(bundle, "safety") + objective = upright.decode_upright_se2_objective_policy(bundle) + if ( + _bridge_policy_symbol(collision, "contact_comparator") != "ALLOW_EQUALITY" + or _bridge_policy_symbol(collision, "boundary_policy") != "CLOSED" + or _bridge_policy_symbol(support, "containment_comparator") != "CONTAINS" + or _bridge_policy_symbol(support, "containment_boundary_policy") != "CLOSED" + or _bridge_policy_symbol(relation_fields, "relation_symbol") != relation + or _bridge_policy_symbol(relation_fields, "boundary_policy") != "CLOSED" + or _bridge_policy_symbol(visibility, "comparator") != "GEQ" + or _bridge_policy_symbol(visibility, "boundary_policy") != "CLOSED" + or _bridge_policy_symbol(safety, "safety_penalty_rule") + != "FROM_CONSTRAINT_SLACK" + ): + raise ValueError("cardinal bridge policy symbols are unsupported") + measurement = _bridge_policy_symbol(relation_fields, "measurement") + if measurement == "EXTENT_SIGNED_AXIS_GAP": + retained_measurement = "EXTENT_AWARE_SIGNED_AXIS_GAP" + elif measurement == "EXTENT_EUCLIDEAN_SEPARATION": + retained_measurement = "EXTENT_AWARE_EUCLIDEAN_SEPARATION" + else: + raise ValueError("cardinal bridge relation measurement is unsupported") + threshold = _bridge_policy_real(relation_fields, "threshold") + tolerance = _bridge_policy_real(relation_fields, "tolerance") + visibility_threshold = _bridge_policy_real(visibility, "threshold") + visibility_tolerance = _bridge_policy_real(visibility, "tolerance") + objective_terms = tuple( + _bridge_objective_term(term, objective.aggregation_definition_ref) + for term in objective.terms + ) + return FixedCardinalCellPolicyV3( + policy_id=relation_policy.definition_ref, + policy_version="definition:1", + relation_threshold=threshold, + relation_tolerance=tolerance, + relation_comparator=_bridge_policy_symbol(relation_fields, "comparator"), + relation_boundary="CLOSED", + # FROM_CONSTRAINT_SLACK is the fixed unit-scale semantic rule; the + # request-bound S-term weight remains in the common objective roster. + safety_penalty_scale=Fraction(1), + safety_constraint_slack_target=_bridge_policy_real( + safety, + "constraint_slack_target", + ), + safety_rule="PENALIZE_BELOW_TARGET", + collision_clearance=_bridge_policy_real(collision, "clearance_m"), + collision_contact_comparator="GE", + collision_boundary="CLOSED", + support_accepted_contact_gap=( + _bridge_policy_real(support, "contact_gap_lower_m"), + _bridge_policy_real(support, "contact_gap_upper_m"), + ), + support_stability_margin=_bridge_policy_real( + support, + "stability_margin_m", + ), + support_containment_comparator="GE", + support_boundary="CLOSED", + support_frame="WORLD_XY_Z_UP", + support_normal=(Fraction(), Fraction(), Fraction(1)), + relation_definition_id="spatial-relation:fixed-cardinal", + relation_definition_version="definition:1", + relation_symbol=relation, + relation_measurement=retained_measurement, + relation_operand="SUBJECT_COMPOUND_TO_REFERENCE", + relation_geometry="UPRIGHT_AABB_EXTENTS", + visibility_cell=cell.canonical_bounds, + visibility_bounds=( + max(Fraction(), visibility_threshold - visibility_tolerance), + min(Fraction(1), visibility_threshold + visibility_tolerance), + ), + objective_terms=objective_terms, + atomic_step_limit=resource_cap, + ) + + +def _bridge_objective_term( + term: upright.UprightSE2ObjectiveTermPolicy, + aggregation: str, +) -> FixedCardinalObjectiveTermV3: + metric_parts = term.metric_definition_ref.rsplit("/", 1) + if len(metric_parts) != 2: + raise ValueError("objective metric definition has no fixed version suffix") + return FixedCardinalObjectiveTermV3( + term_id=term.term_id, + selector=term.input_selector_definition_ref, + metric_definition_id=metric_parts[0], + metric_definition_version=metric_parts[1], + unit=term.unit_ref, + weight=_bridge_fraction(term.weight, label=f"objective {term.term_id} weight"), + normalizer=_bridge_fraction( + term.normalizer, + label=f"objective {term.term_id} normalizer", + ), + aggregation=aggregation, + ) + + +def _bridge_object_pivot_xy( + objects: dict[str, object], + object_id: str, +) -> tuple[Fraction, Fraction]: + object_ = objects.get(object_id) + if object_ is None: + raise ValueError("cardinal bridge pivot object is absent from the scene") + pose = object_.pose.world_from_object + _bridge_cardinal_yaw(pose.rotation, label=f"pivot object {object_id} rotation") + return ( + _bridge_fraction(pose.translation.x, label=f"pivot object {object_id} x"), + _bridge_fraction(pose.translation.y, label=f"pivot object {object_id} y"), + ) + + +def _bridge_visibility_bound_records( + bundle: upright.UprightSE2ExecutablePolicyBundle, +) -> tuple[dict[str, TypedValue], ...]: + fields = _bridge_policy_fields(bundle, "visibility") + value = fields.get("observation_bounds") + if ( + type(value) is not TypedValue + or type(value.payload) is not FiniteOrderedTupleValue + or value.value_schema_ref + != "schema:spatialcf/upright-se2/visibility-observation-bounds/1.0" + or value.payload.element_schema_ref + != "schema:spatialcf/upright-se2/visibility-observation-bound/1.0" + ): + raise ValueError( + "cardinal bridge visibility roster is not an exact typed tuple" + ) + records: list[dict[str, TypedValue]] = [] + expected_names = { + "observation_id", + "camera_id", + "object_id", + "metric_definition_id", + "metric_definition_version", + "comparator", + "boundary_policy", + "threshold", + "tolerance", + } + for item in value.payload.items: + if ( + item.value_schema_ref + != "schema:spatialcf/upright-se2/visibility-observation-bound/1.0" + or type(item.payload) is not RecordValue + ): + raise ValueError("cardinal bridge visibility row has the wrong type") + record = {field.name: field.value for field in item.payload.fields} + if len(record) != len(item.payload.fields) or set(record) != expected_names: + raise ValueError("cardinal bridge visibility row is incomplete") + records.append(record) + identifiers = tuple( + _bridge_policy_id(record, "observation_id") for record in records + ) + if ( + not identifiers + or len(set(identifiers)) != len(identifiers) + or identifiers != tuple(sorted(identifiers, key=canonical_json_bytes)) + ): + raise ValueError("cardinal bridge visibility rows are not canonical") + return tuple(records) + + +def _bridge_projection_box( + geometry: object, + objects: dict[str, object], + *, + subject_id: str, + quarter_turns_ccw: int, + pivot_xy: tuple[Fraction, Fraction], +) -> FixedCardinalProjectionBoxV3: + center_x, center_y, center_z, half_x, half_y, half_z, _ = _bridge_world_box_parts( + geometry, + objects, + ) + if geometry.owner_object_id == subject_id: + relative_x = center_x - pivot_xy[0] + relative_y = center_y - pivot_xy[1] + rotated_x, rotated_y = _rotate_cardinal_xy_components( + relative_x, + relative_y, + quarter_turns_ccw, + ) + center_x = pivot_xy[0] + rotated_x + center_y = pivot_xy[1] + rotated_y + if quarter_turns_ccw % 2: + half_x, half_y = half_y, half_x + return FixedCardinalProjectionBoxV3( + box_id=geometry.geometry_id, + center_x=center_x, + center_y=center_y, + center_z=center_z, + half_x=half_x, + half_y=half_y, + half_z=half_z, + ) + + +def _bridge_camera_context(camera: object) -> UprightCameraContextV2_9: + rotation = camera.world_to_camera.rotation + if type(rotation) is not Quaternion or ( + rotation.x, + rotation.y, + rotation.z, + rotation.w, + ) != (0.0, 0.0, 0.0, 1.0): + raise ValueError("cardinal bridge camera must use identity orientation") + return UprightCameraContextV2_9( + camera_id=camera.camera_id, + width_px=camera.width_px, + height_px=camera.height_px, + intrinsics=tuple( + _bridge_fraction(value, label=f"camera {camera.camera_id} intrinsic") + for value in camera.intrinsics_row_major + ), + near_clip_m=_bridge_fraction( + camera.near_clip_m, + label=f"camera {camera.camera_id} near clip", + ), + far_clip_m=_bridge_fraction( + camera.far_clip_m, + label=f"camera {camera.camera_id} far clip", + ), + translation_xyz=tuple( + _bridge_fraction(value, label=f"camera {camera.camera_id} translation") + for value in ( + camera.world_to_camera.translation.x, + camera.world_to_camera.translation.y, + camera.world_to_camera.translation.z, + ) + ), + sine=(Fraction(), Fraction()), + cosine=(Fraction(1), Fraction(1)), + ) + + +def _bridge_visibility_inputs( + scene: CanonicalScene, + *, + camera: PinholeCamera, + objects: dict[str, object], + subject_id: str, + quarter_turns_ccw: int, + pivot_xy: tuple[Fraction, Fraction], + cell: ClosedXYCellV3, + policy_bundle: upright.UprightSE2ExecutablePolicyBundle, + resource_cap: int, +) -> tuple[UprightSE2CardinalVisibilityEvaluationInput, ...]: + geometries = _known_exact_source_values( + scene.geometry_instances, "geometry instances" + ) + visual_by_object: dict[str, object] = {} + for geometry in geometries: + if geometry.role is not GeometryRoleV2.VISUAL: + continue + if ( + geometry.owner_object_id is None + or geometry.owner_object_id in visual_by_object + ): + raise ValueError("visual geometry roster must have one owner-bound box") + visual_by_object[geometry.owner_object_id] = geometry + moving_geometry = visual_by_object.get(subject_id) + if moving_geometry is None: + raise ValueError("cardinal bridge subject has no exact visual geometry") + projection_by_object = { + object_id: _bridge_projection_box( + geometry, + objects, + subject_id=subject_id, + quarter_turns_ccw=quarter_turns_ccw, + pivot_xy=pivot_xy, + ) + for object_id, geometry in visual_by_object.items() + } + occluders = tuple(sorted(projection_by_object.values(), key=lambda box: box.box_id)) + required_occluder_ids = tuple(box.box_id for box in occluders) + if len(set(required_occluder_ids)) != len(required_occluder_ids): + raise ValueError("cardinal bridge visual box IDs are not unique") + observations = _known_exact_source_values( + scene.baseline_observations, + "baseline observations", + ) + observation_by_id = { + observation.observation_id: observation for observation in observations + } + if len(observation_by_id) != len(observations): + raise ValueError("cardinal bridge observation roster is not unique") + bound_records = _bridge_visibility_bound_records(policy_bundle) + inputs: list[UprightSE2CardinalVisibilityEvaluationInput] = [] + for bound in bound_records: + observation_id = _bridge_policy_id(bound, "observation_id") + observation = observation_by_id.get(observation_id) + if observation is None: + raise ValueError("visibility policy row does not bind a source observation") + metric = ( + _bridge_policy_symbol(bound, "metric_definition_id"), + _bridge_policy_id(bound, "metric_definition_version"), + ) + if metric != ("visibility:image-area-fraction", "definition:1"): + raise ValueError("unsupported registered visibility metric family") + if ( + observation.camera_id != camera.camera_id + or observation.camera_id != _bridge_policy_id(bound, "camera_id") + or observation.object_id != _bridge_policy_id(bound, "object_id") + or (observation.metric_definition_id, observation.metric_definition_version) + != metric + or _bridge_policy_symbol(bound, "comparator") != "GEQ" + or _bridge_policy_symbol(bound, "boundary_policy") != "CLOSED" + ): + raise ValueError( + "visibility policy row does not close its source observation" + ) + observed_box = projection_by_object.get(observation.object_id) + if observed_box is None: + raise ValueError("visibility source geometry is incomplete") + inputs.append( + UprightSE2CardinalVisibilityEvaluationInput( + observation_id=observation_id, + context=_bridge_camera_context(camera), + cell=cell.canonical_bounds, + subject=observed_box, + moving_subject_id=moving_geometry.geometry_id, + occluders=occluders, + required_occluder_ids=required_occluder_ids, + policy=FixedCardinalVisibilityPolicyV3( + metric_definition_id=metric[0], + metric_definition_version=metric[1], + metric_threshold=_bridge_policy_real(bound, "threshold"), + metric_tolerance=_bridge_policy_real(bound, "tolerance"), + metric_comparator="GEQ", + metric_boundary="CLOSED", + atomic_step_limit=resource_cap, + ), + ) + ) + if tuple(sorted(observation_by_id, key=canonical_json_bytes)) != tuple( + _bridge_policy_id(record, "observation_id") for record in bound_records + ): + raise ValueError("visibility policy does not cover the complete source roster") + return tuple(inputs) + + +def _bridge_continuous_visibility_inputs( + scene: CanonicalScene, + *, + camera: PinholeCamera, + objects: dict[str, object], + subject_id: str, + visibility_subject_box_id: str, + policy_bundle: upright.UprightSE2ExecutablePolicyBundle, + resource_cap: int, +) -> tuple[UprightSE2ContinuousVisibilityEvaluationInput, ...]: + """Bind the one V4 continuous visibility obligation from exact source facts. + + Task 6.1's compound cell seam accepts one caller-supplied visibility DTO + whose subject ID must equal its primary collision body. The compiler keeps + the source visual geometry values and records its original ID, while using + that collision ID solely as the retained DTO join key. This is a typed + source-to-owner mapping, never a backend geometry rewrite. + """ + + geometries = _known_exact_source_values( + scene.geometry_instances, + "geometry instances", + ) + visual_by_object: dict[str, object] = {} + for geometry in geometries: + if geometry.role is not GeometryRoleV2.VISUAL: + continue + if ( + geometry.owner_object_id is None + or geometry.owner_object_id in visual_by_object + ): + raise ValueError("visual geometry roster must have one owner-bound box") + visual_by_object[geometry.owner_object_id] = geometry + moving_geometry = visual_by_object.get(subject_id) + if moving_geometry is None: + raise ValueError("continuous bridge subject has no exact visual geometry") + source_subject = _bridge_fixed_box(moving_geometry, objects) + subject = FixedCardinalBoxV3( + box_id=visibility_subject_box_id, + center_x=source_subject.center_x, + center_y=source_subject.center_y, + center_z=source_subject.center_z, + half_x=source_subject.half_x, + half_y=source_subject.half_y, + half_z=source_subject.half_z, + ) + projection_by_object = { + object_id: ( + subject if object_id == subject_id else _bridge_fixed_box(geometry, objects) + ) + for object_id, geometry in visual_by_object.items() + } + occluders = tuple(sorted(projection_by_object.values(), key=lambda box: box.box_id)) + required_occluder_ids = tuple(box.box_id for box in occluders) + if len(set(required_occluder_ids)) != len(required_occluder_ids): + raise ValueError("continuous bridge visual box IDs are not unique") + observations = _known_exact_source_values( + scene.baseline_observations, + "baseline observations", + ) + observation_by_id = { + observation.observation_id: observation for observation in observations + } + if len(observation_by_id) != len(observations): + raise ValueError("continuous bridge observation roster is not unique") + bound_records = _bridge_visibility_bound_records(policy_bundle) + if len(bound_records) != 1: + raise ValueError( + "unsupported continuous V4 visibility conjunction requires one observation" + ) + bound = bound_records[0] + observation_id = _bridge_policy_id(bound, "observation_id") + observation = observation_by_id.get(observation_id) + if observation is None: + raise ValueError("visibility policy row does not bind a source observation") + metric = ( + _bridge_policy_symbol(bound, "metric_definition_id"), + _bridge_policy_id(bound, "metric_definition_version"), + ) + if metric != ("visibility:image-area-fraction", "definition:1"): + raise ValueError("unsupported registered continuous visibility metric family") + if ( + observation.camera_id != camera.camera_id + or observation.camera_id != _bridge_policy_id(bound, "camera_id") + or observation.object_id != subject_id + or observation.object_id != _bridge_policy_id(bound, "object_id") + or (observation.metric_definition_id, observation.metric_definition_version) + != metric + or _bridge_policy_symbol(bound, "comparator") != "GEQ" + or _bridge_policy_symbol(bound, "boundary_policy") != "CLOSED" + ): + raise ValueError( + "unsupported continuous visibility policy does not bind the moving subject" + ) + return ( + UprightSE2ContinuousVisibilityEvaluationInput( + observation_id=observation_id, + source_visual_box_id=source_subject.box_id, + context=_bridge_camera_context(camera), + subject=subject, + moving_subject_id=visibility_subject_box_id, + occluders=occluders, + required_occluder_ids=required_occluder_ids, + policy=FixedCardinalVisibilityPolicyV3( + metric_definition_id=metric[0], + metric_definition_version=metric[1], + metric_threshold=_bridge_policy_real(bound, "threshold"), + metric_tolerance=_bridge_policy_real(bound, "tolerance"), + metric_comparator="GEQ", + metric_boundary="CLOSED", + atomic_step_limit=resource_cap, + ), + ), + ) + + +def compile_planar_translate_m2_q0_equivalence( + source_compilation: PlanarTranslateCompilation, +) -> upright.UprightSE2Compilation: + """Compile one exact retained M2 root into the narrow q=0 construction. + + This is not a delegation, proof checker, endpoint materializer, or a + cross-policy solver claim. It first proves that the retained source has + the one supported closed rectangular world-XY translation domain, then + builds an ordinary domain-level M3 request whose extra construction root + binds the retained bytes and the three disjoint relation-row kinds. + """ + + if type(source_compilation) is not PlanarTranslateCompilation: + raise TypeError( + "source compilation must be an exact PlanarTranslateCompilation" + ) + _require_exact_round_trip( + source_compilation, + PlanarTranslateCompilation, + "source compilation", + ) + source_problem = source_compilation.source_artifacts.problem + source_scene = _canonical_scene_from_m2_source(source_problem.scene) + subject_id = source_problem.constraints.allowed_edit.subject_id + reference_id = source_problem.constraints.target_relation.reference_id + domain = _m2_supported_translation_domain(source_problem) + registration = _registered_profile() + resource_policy = _resource_policy() + policy_bundle = _m2_q0_policy_bundle(registration, resource_policy, source_scene) + construction = _m2_q0_construction( + source_compilation=source_compilation, + domain=domain, + policy_bundle=policy_bundle, + ) + request = _m2_q0_source_request( + source_problem=source_problem, + source_scene=source_scene, + subject_id=subject_id, + reference_id=reference_id, + domain=domain, + registration=registration, + policy_bundle=policy_bundle, + construction=construction, + requested_gap=_source_requested_gap(source_compilation), + ) + compiled = compile_upright_se2(request) + if type(compiled) is not upright.UprightSE2Compilation: + raise ValueError("supported q=0 source must compile to a cardinal M3 domain") + return _reseal_q0_construction(compiled, construction) + + +def _canonical_scene_from_m2_source(source_scene: object) -> CanonicalScene: + """Convert only the retained source's explicit upright scene wire. + + This conversion is target construction data, not an M2/M3 scene equality + claim. The unchanged source model remains embedded in the construction + record, while the target wire uses the M3 rigid-transform representation. + """ + + if not hasattr(source_scene, "model_dump"): + raise TypeError("source compilation must carry a canonical M2 scene") + raw = source_scene.model_dump(mode="python", round_trip=True) + raw["schema_identity"] = { + "schema_name": "canonical-scene", + "schema_version": "2.0", + } + for object_ in raw["objects"]["values"] or (): + object_["pose"]["world_from_object"] = _m2_yaw_transform_to_rigid( + object_["pose"]["world_from_object"] + ) + for geometry in raw["geometry_instances"]["values"] or (): + geometry["anchor_from_geometry"] = _m2_yaw_transform_to_rigid( + geometry["anchor_from_geometry"] + ) + for surface in raw["support_surfaces"]["values"] or (): + surface["anchor_from_surface"] = _m2_yaw_transform_to_rigid( + surface["anchor_from_surface"] + ) + for camera in raw["cameras"]["values"] or (): + world_to_camera = camera["world_to_camera"] + if world_to_camera.get("kind") != "UPRIGHT_WORLD_TO_CAMERA": + raise ValueError("source camera must use the retained upright camera wire") + camera["world_to_camera"] = _rigid_from_yaw( + world_to_camera["translation"], + world_to_camera["azimuth_radians"], + ) + try: + return CanonicalScene.model_validate(raw, strict=True) + except (TypeError, ValueError) as error: + raise ValueError( + "source scene has no supported exact upright M3 construction" + ) from error + + +def _m2_yaw_transform_to_rigid(value: dict[str, object]) -> dict[str, object]: + if value.get("kind") != "DIRECTED_YAW_INTERVAL": + raise ValueError("source transform must use the retained directed-yaw wire") + return _rigid_from_yaw(value["translation"], value["yaw_radians"]) + + +def _rigid_from_yaw( + translation: object, + yaw_radians: object, +) -> dict[str, object]: + if type(yaw_radians) is not float: + raise ValueError("source yaw must be a finite exact binary64 value") + return { + "translation": translation, + "rotation": { + "x": 0.0, + "y": 0.0, + "z": _canonical_zero(math.sin(yaw_radians / 2.0)), + "w": _canonical_zero(math.cos(yaw_radians / 2.0)), + }, + } + + +def _m2_supported_translation_domain( + source_problem: object, +) -> upright.UprightSE2TranslationDomain: + """Return the sole closed axis-aligned M2 anchor domain as XY deltas.""" + + constraints = source_problem.constraints + position = constraints.position_domain + if ( + position.region_interpretation.value != "SUBJECT_ANCHOR_LOCUS" + or position.workspace_aggregation.value != "INTERSECTION" + or position.boundary_policy.value != "CLOSED" + or position.known_free_space_fact_ids + or position.subject_occupancy_body_ids + or position.minimum_boundary_clearance_m != 0.0 + or len(position.workspace_fact_ids) != 1 + or tuple(item.value for item in position.required_completeness) != ("EXACT",) + ): + raise ValueError( + "source M2 domain has no supported total world-XY representation" + ) + workspace = tuple( + item + for item in source_problem.scene.workspace_boundaries.values or () + if item.fact_id == position.workspace_fact_ids[0] + ) + if len(workspace) != 1: + raise ValueError("source M2 domain must bind one exact workspace fact") + region = workspace[0].region_world_xy + if len(region.components) != 1 or region.components[0].holes: + raise ValueError("source M2 domain must be one closed axis-aligned rectangle") + vertices = region.components[0].exterior.vertices + xs = tuple(sorted({point.x for point in vertices})) + ys = tuple(sorted({point.y for point in vertices})) + if ( + len(vertices) != 4 + or len(xs) != 2 + or len(ys) != 2 + or {(point.x, point.y) for point in vertices} + != {(x, y) for x in xs for y in ys} + ): + raise ValueError("source M2 domain must be one closed axis-aligned rectangle") + subject = tuple( + item + for item in source_problem.scene.objects.values or () + if item.object_id == constraints.allowed_edit.subject_id + ) + if len(subject) != 1: + raise ValueError("source M2 domain must bind one source subject pose") + before = subject[0].pose.world_from_object.translation + return upright.UprightSE2TranslationDomain( + x_lower=_dyadic_from_float(_canonical_zero(xs[0] - before.x)), + x_upper=_dyadic_from_float(_canonical_zero(xs[1] - before.x)), + y_lower=_dyadic_from_float(_canonical_zero(ys[0] - before.y)), + y_upper=_dyadic_from_float(_canonical_zero(ys[1] - before.y)), + ) + + +def _m2_q0_policy_bundle( + registration: upright.UprightSE2ProfileRegistration, + resource_policy: ResourcePolicy, + source_scene: CanonicalScene, +) -> upright.UprightSE2ExecutablePolicyBundle: + """Build the explicit target M3 policy; no source policy is consumed.""" + + keys = ( + "collision", + "numeric", + "objective", + "preservation", + "relation:BEHIND", + "relation:FAR", + "relation:FRONT", + "relation:LEFT", + "relation:NEAR", + "relation:RIGHT", + "resource", + "safety", + "support", + "visibility", + ) + objective_policy = upright.build_upright_se2_q0_target_objective_policy() + return upright.build_upright_se2_executable_policy_bundle( + profile_registration_sha256=registration.profile_registration_sha256, + policies=tuple( + upright.UprightSE2ExecutablePolicyValue.seal( + policy_key=key, + policy_family_ref="definition:spatialcf/upright-se2/executable-policy/1.0", + definition_ref=_m2_q0_policy_definition_ref(key), + payload_schema_ref=( + "schema:spatialcf/upright-se2/executable-" + f"{key.replace(':', '-').lower()}-policy/1.0" + ), + owner_binding=_m2_q0_policy_owner(_m2_q0_policy_definition_ref(key)), + payload=_m2_q0_policy_payload( + key, resource_policy, source_scene, objective_policy + ), + ) + for key in keys + ), + ) + + +def _m2_q0_policy_definition_ref(policy_key: str) -> str: + if policy_key == "collision": + return upright.UPRIGHT_SE2_COLLISION_PREDICATE_REF + if policy_key == "support": + return upright.UPRIGHT_SE2_SUPPORT_PREDICATE_REF + if policy_key.startswith("relation:"): + return upright.UPRIGHT_SE2_TARGET_RELATION_PREDICATE_REF + if policy_key == "visibility": + return upright.UPRIGHT_SE2_VISIBILITY_PREDICATE_REF + if policy_key == "preservation": + return upright.UPRIGHT_SE2_PRESERVATION_PREDICATE_REF + return upright.UPRIGHT_SE2_OBJECTIVE_DEFINITION_REF + + +def _m2_q0_policy_owner(definition_ref: str) -> upright.UprightSE2SemanticOwnerBinding: + if definition_ref == upright.UPRIGHT_SE2_OBJECTIVE_DEFINITION_REF: + evaluator, verifier = ( + upright.UPRIGHT_SE2_OBJECTIVE_EVALUATOR_CAPABILITY_REF, + upright.UPRIGHT_SE2_OBJECTIVE_VERIFIER_CAPABILITY_REF, + ) + else: + evaluator, verifier = ( + upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF, + upright.UPRIGHT_SE2_PREDICATE_VERIFIER_CAPABILITY_REF, + ) + return upright.UprightSE2SemanticOwnerBinding( + definition_ref=definition_ref, + evaluator_capability_ref=evaluator, + verifier_capability_ref=verifier, + evaluator_owner_ref=upright.UPRIGHT_SE2_BACKEND_OWNER_REF, + verifier_owner_ref=upright.UPRIGHT_SE2_CHECKER_OWNER_REF, + evaluator_build_sha256=_BACKEND_BUILD_SHA256, + verifier_build_sha256=_CHECKER_BUILD_SHA256, + ) + + +def _m2_q0_policy_payload( + policy_key: str, + resource_policy: ResourcePolicy, + source_scene: CanonicalScene, + objective_policy: upright.UprightSE2FiveTermObjectivePolicy, +) -> TypedValue: + """Encode target-only request policy values in their registered schemas.""" + + if policy_key == "collision": + fields = ( + ("boundary_policy", _m2_q0_symbol("CLOSED")), + ("clearance_m", _m2_q0_real(0.0)), + ("contact_comparator", _m2_q0_symbol("ALLOW_EQUALITY")), + ("obstacle_selector", _m2_q0_id("selector:complete-obstacle-roster")), + ( + "subject_geometry_selector", + _m2_q0_id("selector:compound-subject-geometry"), + ), + ) + elif policy_key == "support": + fields = ( + ("contact_gap_lower_m", _m2_q0_real(0.0)), + ("contact_gap_upper_m", _m2_q0_real(0.0)), + ("containment_boundary_policy", _m2_q0_symbol("CLOSED")), + ("containment_comparator", _m2_q0_symbol("CONTAINS")), + ("normal_selector", _m2_q0_id("selector:world-positive-z")), + ("stability_margin_m", _m2_q0_real(0.0)), + ("support_frame_selector", _m2_q0_id("selector:world-xy")), + ( + "support_surface_selector", + _m2_q0_id("selector:assigned-support-surface"), + ), + ) + elif policy_key.startswith("relation:"): + relation = policy_key.removeprefix("relation:") + fields = ( + ("boundary_policy", _m2_q0_symbol("CLOSED")), + ( + "comparator", + _m2_q0_symbol("LE" if relation in {"LEFT", "FRONT", "NEAR"} else "GE"), + ), + ("fixed_camera_selector", _m2_q0_id("selector:fixed-camera")), + ("frame_selector", _m2_q0_id("selector:world-xy")), + ( + "measurement", + _m2_q0_symbol( + "EXTENT_EUCLIDEAN_SEPARATION" + if relation in {"NEAR", "FAR"} + else "EXTENT_SIGNED_AXIS_GAP" + ), + ), + ("operand_order", _m2_q0_symbol("SUBJECT_THEN_REFERENCE")), + ("relation_symbol", _m2_q0_symbol(relation)), + ("representative_geometry", _m2_q0_symbol("COMPOUND_BODY")), + ("threshold", _m2_q0_real(0.25 if relation == "LEFT" else 1.0)), + ("tolerance", _m2_q0_real(0.0)), + ("visibility_gate", _m2_q0_symbol("NONE")), + ) + elif policy_key == "visibility": + fields = ( + ("boundary_policy", _m2_q0_symbol("CLOSED")), + ("camera_projection_convention", _m2_q0_symbol("UPRIGHT_CAMERA_V2_9")), + ("comparator", _m2_q0_symbol("GEQ")), + ("depth_policy", _m2_q0_symbol("NEAR_CLIPPED")), + ("mask_policy", _m2_q0_symbol("COMPLETE_MASK")), + ( + "observation_bounds", + _m2_q0_tuple( + "visibility-observation-bounds", + tuple( + _m2_q0_typed_record( + "schema:spatialcf/upright-se2/visibility-observation-bound/1.0", + ( + ( + "observation_id", + _m2_q0_id(observation.observation_id), + ), + ("camera_id", _m2_q0_id(observation.camera_id)), + ("object_id", _m2_q0_id(observation.object_id)), + ( + "metric_definition_id", + _m2_q0_symbol(observation.metric_definition_id), + ), + ( + "metric_definition_version", + _m2_q0_id(observation.metric_definition_version), + ), + ("comparator", _m2_q0_symbol("GEQ")), + ("boundary_policy", _m2_q0_symbol("CLOSED")), + ("threshold", _m2_q0_real(0.5)), + ("tolerance", _m2_q0_real(0.0)), + ), + ) + for observation in source_scene.baseline_observations.values + or () + ), + element_schema_ref=( + "schema:spatialcf/upright-se2/visibility-observation-bound/1.0" + ), + ), + ), + ("occluder_policy", _m2_q0_symbol("COMPLETE_ROSTER")), + ("projected_area_metric", _m2_q0_symbol("PROJECTED_AREA")), + ("subject_as_occluder", _m2_q0_symbol("INCLUDED")), + ("threshold", _m2_q0_real(0.5)), + ("tolerance", _m2_q0_real(0.0)), + ) + elif policy_key == "numeric": + fields = ( + ("dyadic_refinement_policy", _m2_q0_symbol("EXACT_DYADIC")), + ("exact_number_representation", _m2_q0_symbol("BINARY64_BITS")), + ( + "numeric_semantics_ref", + _m2_q0_id("definition:spatialcf/upright-se2/numeric-semantics/1.0"), + ), + ("tolerance_m", _m2_q0_real(0.0)), + ) + elif policy_key == "objective": + fields = ( + ("aggregation_rule", _m2_q0_symbol("WEIGHTED_NORMALIZED_SUM")), + ("comparison_rule", _m2_q0_symbol("INTERVAL_LEXICOGRAPHIC")), + ( + "objective_policy_sha256", + _m2_q0_digest(objective_policy.five_term_objective_policy_sha256), + ), + ( + "terms", + _m2_q0_tuple( + "objective-term-values", + tuple( + _m2_q0_typed_record( + "schema:spatialcf/upright-se2/objective-term-value/1.0", + ( + ("term_id", _m2_q0_symbol(term.term_id)), + ( + "objective_definition_ref", + _m2_q0_id(term.objective_definition_ref), + ), + ( + "metric_definition_ref", + _m2_q0_id(term.metric_definition_ref), + ), + ( + "input_selector_definition_ref", + _m2_q0_id(term.input_selector_definition_ref), + ), + ("unit_ref", _m2_q0_id(term.unit_ref)), + ( + "normalization_definition_ref", + _m2_q0_id(term.normalization_definition_ref), + ), + ( + "normalizer_unit_ref", + _m2_q0_id(term.normalizer_unit_ref), + ), + ("weight", _m2_q0_real(term.weight)), + ("normalizer", _m2_q0_real(term.normalizer)), + ), + ) + for term in objective_policy.terms + ), + element_schema_ref=( + "schema:spatialcf/upright-se2/objective-term-value/1.0" + ), + ), + ), + ("tie_break_rule", _m2_q0_symbol("T_R_V_S_A")), + ) + elif policy_key == "safety": + fields = ( + ("constraint_slack_target", _m2_q0_real(0.0)), + ("hard_constraint_selector", _m2_q0_id("selector:all-hard-constraints")), + ("safety_penalty_rule", _m2_q0_symbol("FROM_CONSTRAINT_SLACK")), + ) + elif policy_key == "resource": + fields = ( + ( + "atomic_step_limit", + _m2_q0_integer(int(resource_policy.limits[0].finite_limit)), + ), + ("deterministic_order", _m2_q0_symbol("LOWER_OWNED_XY")), + ( + "limits", + _m2_q0_tuple( + "resource-limit-values", + tuple( + _m2_q0_id(limit.definition_ref) + for limit in resource_policy.limits + ), + ), + ), + ( + "resource_policy_sha256", + _m2_q0_digest(resource_policy.resource_policy_sha256), + ), + ( + "shared_ledger_policy_ref", + _m2_q0_id(resource_policy.shared_ledger_policy_ref), + ), + ) + elif policy_key == "preservation": + fields = ( + ("frozen_observation_policy", _m2_q0_symbol("COMPLETE_GROUNDED")), + ("grounded_invariant_selector", _m2_q0_id("selector:grounded-invariants")), + ("state_selector", _m2_q0_id("selector:frozen-nonprimary-state")), + ) + else: + raise ValueError("q=0 target policy key is unknown") + return _m2_q0_record(policy_key.replace(":", "-"), fields) + + +def _m2_q0_real(value: float) -> TypedValue: + return TypedValue( + value_schema_ref=_REAL_SCHEMA_REF, payload=FiniteRealValue(value=value) + ) + + +def _m2_q0_integer(value: int) -> TypedValue: + return TypedValue( + value_schema_ref=_INTEGER_SCHEMA_REF, payload=IntegerValue(value=value) + ) + + +def _m2_q0_id(value: str) -> TypedValue: + return TypedValue( + value_schema_ref=_ID_SCHEMA_REF, payload=CanonicalIdValue(value=value) + ) + + +def _m2_q0_digest(value: str) -> TypedValue: + return TypedValue( + value_schema_ref=_DIGEST_SCHEMA_REF, payload=DigestValue(value=value) + ) + + +def _m2_q0_symbol(value: str) -> TypedValue: + return TypedValue( + value_schema_ref=_ENUM_SCHEMA_REF, payload=EnumSymbolValue(symbol=value) + ) + + +def _m2_q0_tuple( + name: str, + values: tuple[TypedValue, ...], + *, + element_schema_ref: str = "schema:spatialcf/upright-se2/policy-item/1.0", +) -> TypedValue: + return TypedValue( + value_schema_ref=f"schema:spatialcf/upright-se2/{name}/1.0", + payload=FiniteOrderedTupleValue( + element_schema_ref=element_schema_ref, + items=values, + ), + ) + + +def _m2_q0_record(name: str, fields: tuple[tuple[str, TypedValue], ...]) -> TypedValue: + return _m2_q0_typed_record( + f"schema:spatialcf/upright-se2/executable-{name.lower()}-policy/1.0", + fields, + ) + + +def _m2_q0_typed_record( + schema_ref: str, + fields: tuple[tuple[str, TypedValue], ...], +) -> TypedValue: + return TypedValue( + value_schema_ref=schema_ref, + payload=RecordValue( + fields=tuple( + sorted( + ( + NamedTypedValue(name=field_name, value=value) + for field_name, value in fields + ), + key=canonical_json_bytes, + ) + ) + ), + ) + + +def _m2_q0_construction( + *, + source_compilation: PlanarTranslateCompilation, + domain: upright.UprightSE2TranslationDomain, + policy_bundle: upright.UprightSE2ExecutablePolicyBundle, +) -> upright.UprightSE2M2Q0Construction: + """Bind provenance, the one domain equality, and heterogeneous policy rows.""" + + domain_value = _m2_q0_domain_value(domain) + domain_digest = canonical_sha256( + domain_value, + domain="spatialcf/counterfactual/upright-se2/m2-q0/shared-value/3.0", + ) + definitions: list[upright.UprightSE2M2Q0MappingDefinition] = [] + rows: list[upright.UprightSE2M2Q0MappingRow] = [] + + def add_row( + *, + selector: str, + kind: str, + source_value: object, + target_selector: str | None = None, + target_value: object | None = None, + reason: str | None = None, + transform: str | None = None, + shared_value: TypedValue | None = None, + ) -> None: + definition = upright.UprightSE2M2Q0MappingDefinition.seal( + mapping_definition_ref=( + "definition:spatialcf/upright-se2/m2-q0/mapping/" + f"{selector.removeprefix('source:').replace('/', '-')}/1.0" + ), + row_kind=kind, + source_selector=selector, + target_selector=target_selector, + source_value_schema_ref=( + "schema:spatialcf/upright-se2/m2-q0/translation-domain/1.0" + if kind == "EQUALITY" + else "schema:spatialcf/upright-se2/m2-q0/source-leaf/1.0" + ), + target_value_schema_ref=( + None + if target_selector is None + else ( + "schema:spatialcf/upright-se2/m2-q0/translation-domain/1.0" + if kind == "EQUALITY" + else "schema:spatialcf/upright-se2/m2-q0/target-leaf/1.0" + ) + ), + source_unit_ref=( + "definition:spatialcf/upright-se2/world-xy/metre/1.0" + if kind == "EQUALITY" + else None + ), + target_unit_ref=( + "definition:spatialcf/upright-se2/world-xy/metre/1.0" + if kind == "EQUALITY" + else None + ), + transform_ref=transform, + non_equivalence_reason_ref=reason, + mapping_owner_ref=upright.UPRIGHT_SE2_COMPILER_OWNER_REF, + mapping_version="mapping-version:spatialcf/upright-se2/m2-q0/1", + accepted_source_domain_ref=( + "definition:spatialcf/upright-se2/m2-closed-axis-aligned-rect-domain/1.0" + ), + ) + definitions.append(definition) + source_digest = canonical_sha256( + source_value, + domain="spatialcf/counterfactual/upright-se2/m2-q0/source-leaf/3.0", + ) + target_digest = ( + None + if target_value is None + else canonical_sha256( + target_value, + domain="spatialcf/counterfactual/upright-se2/m2-q0/target-leaf/3.0", + ) + ) + if kind == "EQUALITY": + source_digest = domain_digest + target_digest = domain_digest + rows.append( + upright.UprightSE2M2Q0MappingRow.seal( + mapping_definition=definition, + row_kind=kind, + source_value_sha256=source_digest, + target_value_sha256=target_digest, + shared_value=shared_value, + shared_value_sha256=( + None + if shared_value is None + else canonical_sha256( + shared_value, + domain="spatialcf/counterfactual/upright-se2/m2-q0/shared-value/3.0", + ) + ), + non_equivalence_reason_ref=reason, + ) + ) + + source_problem = source_compilation.source_artifacts.problem + add_row( + selector="source:constraints/position-domain", + kind="EQUALITY", + source_value=domain_value, + target_selector="target:operation/translation-domain", + target_value=domain_value, + transform="definition:spatialcf/upright-se2/m2-q0/delta-xy-identity/1.0", + shared_value=domain_value, + ) + for selector, value in _m2_q0_source_leaves(source_compilation): + add_row(selector=selector, kind="SOURCE_CONTEXT", source_value=value) + for selector, source_value, target_selector, target_value, reason in ( + ( + "source:policy/objective", + source_problem.objective, + "target:policy/objective", + policy_bundle.policy_for("objective").payload, + "definition:spatialcf/upright-se2/m2-q0/heterogeneous-objective/1.0", + ), + ( + "source:policy/relation", + source_problem.relation_semantics, + "target:policy/relation", + tuple( + policy.payload + for policy in policy_bundle.policies + if policy.policy_key.startswith("relation:") + ), + "definition:spatialcf/upright-se2/m2-q0/heterogeneous-relation/1.0", + ), + ( + "source:policy/visibility", + source_problem.visibility_semantics, + "target:policy/visibility", + policy_bundle.policy_for("visibility").payload, + "definition:spatialcf/upright-se2/m2-q0/heterogeneous-visibility/1.0", + ), + ( + "source:policy/support", + source_problem.constraints.support_constraints, + "target:policy/support", + policy_bundle.policy_for("support").payload, + "definition:spatialcf/upright-se2/m2-q0/heterogeneous-support/1.0", + ), + ( + "source:policy/numeric", + source_problem.numeric_policy, + "target:policy/numeric", + policy_bundle.policy_for("numeric").payload, + "definition:spatialcf/upright-se2/m2-q0/heterogeneous-numeric/1.0", + ), + ( + "source:policy/resource", + source_compilation.source_artifacts.config, + "target:policy/resource", + policy_bundle.policy_for("resource").payload, + "definition:spatialcf/upright-se2/m2-q0/heterogeneous-resource/1.0", + ), + ( + "source:policy/tie-break", + source_problem.objective.tie_break, + "target:policy/tie-break", + policy_bundle.policy_for("objective").payload, + "definition:spatialcf/upright-se2/m2-q0/heterogeneous-tie-break/1.0", + ), + ): + add_row( + selector=selector, + kind="NON_EQUIVALENCE", + source_value=source_value, + target_selector=target_selector, + target_value=target_value, + reason=reason, + ) + definitions = tuple(sorted(definitions, key=canonical_json_bytes)) + rows = tuple( + sorted(rows, key=lambda row: canonical_json_bytes(row.mapping_definition)) + ) + source_free_rows = ( + upright.UprightSE2M2Q0SourceFreeConstructionRow.seal( + construction_selector="construction:cardinal-own-pivot-q", + target_value=_m2_q0_int(0), + target_value_sha256=canonical_sha256( + _m2_q0_int(0), + domain="spatialcf/counterfactual/upright-se2/m2-q0/shared-value/3.0", + ), + construction_owner_ref=upright.UPRIGHT_SE2_COMPILER_OWNER_REF, + construction_version="construction-version:spatialcf/upright-se2/m2-q0/1", + ), + upright.UprightSE2M2Q0SourceFreeConstructionRow.seal( + construction_selector="construction:target-tie-break", + target_value=_m2_q0_symbol("T_R_V_S_A"), + target_value_sha256=canonical_sha256( + _m2_q0_symbol("T_R_V_S_A"), + domain="spatialcf/counterfactual/upright-se2/m2-q0/shared-value/3.0", + ), + construction_owner_ref=upright.UPRIGHT_SE2_COMPILER_OWNER_REF, + construction_version="construction-version:spatialcf/upright-se2/m2-q0/1", + ), + ) + return upright.UprightSE2M2Q0Construction.seal( + source_compilation=source_compilation, + source_compilation_sha256=source_compilation.compilation_sha256, + source_provenance_sha256=canonical_sha256( + source_compilation, + domain="spatialcf/counterfactual/upright-se2/m2-q0/source-provenance/3.0", + ), + authorized_domain=domain, + authorized_domain_sha256=canonical_sha256( + domain, + domain="spatialcf/counterfactual/upright-se2/translation-domain/3.0", + ), + mapping_definitions=definitions, + mapping_definition_roster_sha256=canonical_sha256( + tuple(item.mapping_definition_sha256 for item in definitions), + domain="spatialcf/counterfactual/upright-se2/m2-q0/mapping-definition-roster/3.0", + ), + rows=rows, + source_free_construction_rows=source_free_rows, + frozen_m3_policy_bundle=policy_bundle, + frozen_m3_policy_bundle_sha256=policy_bundle.policy_bundle_sha256, + ) + + +def _m2_q0_source_leaves( + source_compilation: PlanarTranslateCompilation, +) -> tuple[tuple[str, object], ...]: + """Enumerate every retained source leaf in canonical source-tree order.""" + + leaves: list[tuple[str, object]] = [] + + def visit(value: object, selector: str) -> None: + if type(value) is dict: + if not value: + leaves.append((selector, value)) + return + for key in sorted(value, key=canonical_json_bytes): + visit(value[key], f"{selector}/{key}") + return + if type(value) in (list, tuple): + if not value: + leaves.append((selector, value)) + return + for index, item in enumerate(value): + visit(item, f"{selector}/{index}") + return + leaves.append((selector, value)) + + visit( + source_compilation.model_dump(mode="python", round_trip=True), + "source:compilation", + ) + return tuple(leaves) + + +def _m2_q0_domain_value(domain: upright.UprightSE2TranslationDomain) -> TypedValue: + return _m2_q0_record( + "m2-q0-domain", + ( + ("x_lower", _m2_q0_real(float(domain.x_lower.as_fraction))), + ("x_upper", _m2_q0_real(float(domain.x_upper.as_fraction))), + ("y_lower", _m2_q0_real(float(domain.y_lower.as_fraction))), + ("y_upper", _m2_q0_real(float(domain.y_upper.as_fraction))), + ), + ) + + +def _m2_q0_int(value: int) -> TypedValue: + return TypedValue( + value_schema_ref=_INTEGER_SCHEMA_REF, payload=IntegerValue(value=value) + ) + + +def _m2_q0_source_request( + *, + source_problem: object, + source_scene: CanonicalScene, + subject_id: str, + reference_id: str, + domain: upright.UprightSE2TranslationDomain, + registration: upright.UprightSE2ProfileRegistration, + policy_bundle: upright.UprightSE2ExecutablePolicyBundle, + construction: upright.UprightSE2M2Q0Construction, + requested_gap: upright.UprightSE2ExactRational, +) -> CounterfactualSolveRequest: + """Build the fixed M3 q=0 source root without selecting an endpoint. + + The retained M2 root supplies only scene authority, the supported delta + domain, and verbatim provenance. This constructor deliberately owns the + distinct M3 policy, grounded M3 obligations, and source-free q=0 constant; + none of those target values are presented as M2 mappings. + """ + + subject = _m2_q0_scene_object(source_scene, subject_id, "subject") + _m2_q0_scene_object(source_scene, reference_id, "reference") + source_relation = source_problem.constraints.target_relation + before_relation = f"relation:{source_relation.relation_before.value}" + after_relation = f"relation:{source_relation.relation_after.value}" + if before_relation == after_relation: + raise ValueError( + "source M2 relation must specify distinct before and after goals" + ) + + compiler_fact = ExtensionFact( + fact_family_ref=_INPUT_FAMILY_REF, + subject_entity_id=_input_entity_id(subject_id), + fact_key=_COMPILER_INPUT_FACT_KEY, + value=_m2_q0_typed_record( + _INPUT_SCHEMA_REF, + ( + ("operation_kind", _m2_q0_symbol("CARDINAL")), + ( + "operator_ref", + _m2_q0_id(upright.UPRIGHT_SE2_CARDINAL_OWN_PIVOT_OPERATOR_REF), + ), + ("reference_id", _m2_q0_id(reference_id)), + ("subject_id", _m2_q0_id(subject_id)), + ( + "subject_yaw_turns", + _m2_q0_real( + upright.canonical_yaw_from_upright_quaternion( + subject.pose.world_from_object.rotation + ).turns + ), + ), + ( + "yaw_argument", + _m2_q0_typed_record( + _YAW_ARGUMENT_SCHEMA_REF, + ( + ("kind", _m2_q0_symbol("CARDINAL")), + ("quarter_turns_ccw", _m2_q0_int(0)), + ), + ), + ), + ), + ), + ) + policy_fact = ExtensionFact( + fact_family_ref="definition:spatialcf/upright-se2/executable-policy/1.0", + subject_entity_id="entity:upright-se2-policy", + fact_key="fact-key:spatialcf/upright-se2/executable-policy-bundle", + value=upright.executable_policy_bundle_to_typed_value(policy_bundle), + ) + construction_fact = ExtensionFact( + fact_family_ref="definition:spatialcf/upright-se2/m2-q0-construction/1.0", + subject_entity_id="entity:upright-se2-policy", + fact_key="fact-key:spatialcf/upright-se2/m2-q0-construction", + value=_m2_q0_digest(construction.m2_q0_construction_sha256), + ) + scene_state = SceneStateEnvelope.seal( + base_scene_schema_ref=_SCENE_SCHEMA_REF, + base_scene_payload=source_scene, + extension_fact_bundles=( + ExtensionFactBundle.seal( + facts=_sorted_bytes(compiler_fact, policy_fact, construction_fact) + ), + ), + closed_entity_index=_closed_entity_index(source_scene, subject_id), + canonical_state_leaf_index=StateLeafIndex.seal( + leaves=_expected_state_leaves(source_scene, subject_id) + ), + ) + authorization = InterventionAuthorization.seal( + editable_entity_ids=(_input_entity_id(subject_id),), + allowed_operator_refs=(upright.UPRIGHT_SE2_CARDINAL_OWN_PIVOT_OPERATOR_REF,), + authorized_primary_write_set=_primary_write_set(subject_id), + variable_bounds=_m2_q0_variable_bounds(subject_id, domain), + maximum_program_steps=1, + maximum_edited_entities=1, + required_derived_rule_refs=(_DERIVED_RULE_REF,), + complete_state_delta_policy_ref=( + "definition:spatialcf/upright-se2/complete-state-delta/1.0" + ), + ) + semantic_problem = CounterfactualProblemIR.seal( + problem_id=( + "problem:spatialcf/upright-se2/m2-q0/" + f"{construction.source_compilation_sha256}" + ), + scene_state=scene_state, + definition_bundle=upright.build_upright_se2_semantic_definition_bundle( + registration, + objective_policy=upright.decode_upright_se2_objective_policy(policy_bundle), + ), + semantics_profile_ref=upright.UPRIGHT_SE2_SEMANTICS_PROFILE_REF, + action_space_profile_ref=upright.UPRIGHT_SE2_PROFILE_REF, + intervention_authorization=authorization, + before_preconditions=( + BeforePrecondition( + formula=_m2_q0_target_relation_atom( + subject_id=subject_id, + reference_id=reference_id, + relation=before_relation, + phase="BEFORE", + ) + ), + ), + after_goal=AfterGoal( + formula=_m2_q0_target_relation_atom( + subject_id=subject_id, + reference_id=reference_id, + relation=after_relation, + phase="AFTER", + ) + ), + preservation_invariants=( + PreservationInvariant( + before_formula=_m2_q0_preservation_atom(subject_id, "BEFORE"), + after_formula=_m2_q0_preservation_atom(subject_id, "AFTER"), + transition_comparator_ref=( + "definition:spatialcf/upright-se2/preservation-transition-comparator/1.0" + ), + ), + ), + explicit_observation_obligations=_sorted_bytes( + *( + _m2_q0_visibility_obligation(observation) + for observation in source_scene.baseline_observations.values or () + ) + ), + objective_expression=upright.build_upright_se2_objective_expression(), + numeric_semantics_ref=registration.semantics_profile.numeric_semantics_ref, + ) + return CounterfactualSolveRequest.seal( + semantic_problem=semantic_problem, + semantic_problem_sha256=semantic_problem.semantic_problem_sha256, + solve_policy_definition_bundle=_solve_policy_bundle( + registration, requested_gap=requested_gap + ), + implementation_registry_snapshot=_implementation_registry(registration), + backend_descriptor_bundle=_backend_descriptor_bundle(registration), + solver_config=_solver_config(), + proof_policy=_proof_policy(), + resource_policy=_resource_policy(), + backend_routing_policy=_backend_routing_policy(), + ) + + +def _m2_q0_scene_object(scene: CanonicalScene, object_id: str, label: str) -> object: + matches = tuple( + object_ + for object_ in scene.objects.values or () + if object_.object_id == object_id + ) + if len(matches) != 1: + raise ValueError(f"source M2 {label} must name one exact scene object") + return matches[0] + + +def _m2_q0_variable_bounds( + subject_id: str, + domain: upright.UprightSE2TranslationDomain, +) -> tuple[TypedVariableBound, ...]: + return _sorted_bytes( + TypedVariableBound( + state_variable_ref=_state_leaf(subject_id, "subject-world-x"), + value_schema_ref=_REAL_SCHEMA_REF, + typed_domain=_m2_q0_closed_interval(domain.x_lower, domain.x_upper), + frame_ref=_WORLD_XY_FRAME_REF, + unit_ref=_METRE_UNIT_REF, + topology_ref=_CLOSED_INTERVAL_TOPOLOGY_REF, + ), + TypedVariableBound( + state_variable_ref=_state_leaf(subject_id, "subject-world-y"), + value_schema_ref=_REAL_SCHEMA_REF, + typed_domain=_m2_q0_closed_interval(domain.y_lower, domain.y_upper), + frame_ref=_WORLD_XY_FRAME_REF, + unit_ref=_METRE_UNIT_REF, + topology_ref=_CLOSED_INTERVAL_TOPOLOGY_REF, + ), + ) + + +def _m2_q0_closed_interval( + lower: upright.ExactDyadic, + upper: upright.ExactDyadic, +) -> TypedValue: + return TypedValue( + value_schema_ref=_REAL_SCHEMA_REF, + payload=IntervalValue( + endpoint_schema_ref=_REAL_SCHEMA_REF, + lower=FiniteRealValue(value=float(lower.as_fraction)), + upper=FiniteRealValue(value=float(upper.as_fraction)), + lower_closed=True, + upper_closed=True, + ), + ) + + +def _m2_q0_typed_reference( + schema_ref: str, kind: ValueKind, reference: str +) -> TypedValue: + return TypedValue( + value_schema_ref=schema_ref, + payload=ReferenceValue(kind=kind, reference=reference), + ) + + +def _m2_q0_target_relation_atom( + *, + subject_id: str, + reference_id: str, + relation: str, + phase: str, +) -> PredicateAtom: + return PredicateAtom( + predicate_ref=upright.UPRIGHT_SE2_TARGET_RELATION_PREDICATE_REF, + operands=( + _m2_q0_typed_reference( + "schema:spatialcf/upright-se2/object-ref/1.0", + ValueKind.OBJECT_REF, + subject_id, + ), + _m2_q0_typed_reference( + "schema:spatialcf/upright-se2/object-ref/1.0", + ValueKind.OBJECT_REF, + reference_id, + ), + TypedValue( + value_schema_ref="schema:spatialcf/upright-se2/relation-symbol/1.0", + payload=EnumSymbolValue(symbol=relation), + ), + TypedValue( + value_schema_ref="schema:spatialcf/upright-se2/phase-symbol/1.0", + payload=EnumSymbolValue(symbol=f"phase:{phase}"), + ), + ), + ) + + +def _m2_q0_preservation_atom(subject_id: str, phase: str) -> PredicateAtom: + return PredicateAtom( + predicate_ref=upright.UPRIGHT_SE2_PRESERVATION_PREDICATE_REF, + operands=( + _m2_q0_typed_reference( + "schema:spatialcf/upright-se2/entity-ref/1.0", + ValueKind.ENTITY_REF, + _input_entity_id(subject_id), + ), + TypedValue( + value_schema_ref="schema:spatialcf/upright-se2/preservation-selector/1.0", + payload=EnumSymbolValue(symbol="preservation:FROZEN_NONPRIMARY_LEAVES"), + ), + TypedValue( + value_schema_ref="schema:spatialcf/upright-se2/phase-symbol/1.0", + payload=EnumSymbolValue(symbol=f"phase:{phase}"), + ), + ), + ) + + +def _m2_q0_visibility_obligation(observation: object) -> ObservationObligation: + return ObservationObligation( + phase="AFTER", + formula=PredicateAtom( + predicate_ref=upright.UPRIGHT_SE2_VISIBILITY_PREDICATE_REF, + operands=( + _m2_q0_typed_reference( + "schema:spatialcf/upright-se2/camera-ref/1.0", + ValueKind.CAMERA_REF, + observation.camera_id, + ), + _m2_q0_typed_reference( + "schema:spatialcf/upright-se2/object-ref/1.0", + ValueKind.OBJECT_REF, + observation.object_id, + ), + TypedValue( + value_schema_ref=( + "schema:spatialcf/upright-se2/visibility-metric-symbol/1.0" + ), + payload=EnumSymbolValue(symbol=observation.metric_definition_id), + ), + TypedValue( + value_schema_ref="schema:spatialcf/upright-se2/observation-ref/1.0", + payload=CanonicalIdValue(value=observation.observation_id), + ), + TypedValue( + value_schema_ref="schema:spatialcf/upright-se2/phase-symbol/1.0", + payload=EnumSymbolValue(symbol="phase:AFTER"), + ), + ), + ), + evidence_policy_ref="definition:spatialcf/upright-se2/visibility-evidence-policy/1.0", + ) + + +def _reseal_q0_construction( + compilation: upright.UprightSE2Compilation, + construction: upright.UprightSE2M2Q0Construction, +) -> upright.UprightSE2Compilation: + values = compilation.model_dump(mode="python", round_trip=True) + values.pop("upright_se2_compilation_sha256") + values["m2_q0_construction"] = construction + return upright.UprightSE2Compilation.seal(**values) + + +def cardinal_inverse_quarter_turns(q: int) -> int: + """Return the exact inverse in the four-element cardinal rotation group.""" + + return (-upright.CardinalYaw(q=q).q) % 4 + + +def _rotate_cardinal_xy_components( + x: float | Fraction, + y: float | Fraction, + quarter_turns_ccw: int, +) -> tuple[float | Fraction, float | Fraction]: + """Share the compiler's cardinal permutation with the public and exact paths.""" + + quarter_turns_ccw = upright.CardinalYaw(q=quarter_turns_ccw).q + if quarter_turns_ccw == 0: + return x, y + if quarter_turns_ccw == 1: + return -y, x + if quarter_turns_ccw == 2: + return -x, -y + if quarter_turns_ccw == 3: + return y, -x + raise RuntimeError("validated cardinal quarter turns escaped 0..3") + + +def rotate_cardinal_xy(x: float, y: float, q: int) -> tuple[float, float]: + """Apply an exact cardinal signed-coordinate permutation to ``(x, y)``.""" + + point = Vec2(x=x, y=y) + rotated_x, rotated_y = _rotate_cardinal_xy_components(point.x, point.y, q) + return (_canonical_zero(rotated_x), _canonical_zero(rotated_y)) + + +def _canonical_zero(value: float) -> float: + return 0.0 if value == 0.0 else value + + +def _require_exact_round_trip(value: object, model_type: type, label: str) -> None: + if type(value) is not model_type: + raise TypeError(f"{label} must be an exact {model_type.__name__}") + try: + with warnings.catch_warnings(): + warnings.simplefilter("error") + checked = model_type.model_validate( + value.model_dump(mode="python", round_trip=True), + strict=True, + ) + except (TypeError, ValueError, Warning) as error: + raise ValueError(f"{label} must pass strict canonical validation") from error + if type(checked) is not model_type or canonical_json_bytes( + checked + ) != canonical_json_bytes(value): + raise ValueError(f"{label} strict canonical bytes do not round trip") + + +def _registered_profile() -> upright.UprightSE2ProfileRegistration: + semantics_profile = SemanticsProfile.seal( + semantics_profile_ref=upright.UPRIGHT_SE2_SEMANTICS_PROFILE_REF, + accepted_scene_and_fact_schema_refs=(_SCENE_SCHEMA_REF,), + predicate_definition_refs=upright.UPRIGHT_SE2_PREDICATE_DEFINITION_REFS, + transition_semantics_refs=upright.UPRIGHT_SE2_OPERATOR_REFS, + objective_definition_refs=(upright.UPRIGHT_SE2_OBJECTIVE_DEFINITION_REF,), + numeric_semantics_ref="definition:spatialcf/upright-se2/numeric-semantics/1.0", + completeness_policy_ref="definition:spatialcf/upright-se2/completeness-policy/1.0", + uncertainty_policy_ref="definition:spatialcf/upright-se2/uncertainty-policy/1.0", + derived_fact_rule_refs=(_DERIVED_RULE_REF,), + observation_obligation_policy_ref=( + "definition:spatialcf/upright-se2/observation-policy/1.0" + ), + ) + action_space_profile = ActionSpaceProfile.seal( + action_space_profile_ref=upright.UPRIGHT_SE2_PROFILE_REF, + accepted_scene_schema_refs=(_SCENE_SCHEMA_REF,), + state_variable_definition_refs=_sorted_bytes( + "definition:spatialcf/upright-se2/state-world-xy/1.0", + "definition:spatialcf/upright-se2/state-directed-yaw/1.0", + ), + allowed_operator_refs=upright.UPRIGHT_SE2_OPERATOR_REFS, + mandatory_invariant_template_refs=( + "definition:spatialcf/upright-se2/invariants-frozen-state/1.0", + ), + predicate_capability_refs=upright.UPRIGHT_SE2_PREDICATE_CAPABILITY_REFS, + objective_capability_refs=upright.UPRIGHT_SE2_OBJECTIVE_CAPABILITY_REFS, + numeric_semantics_ref="definition:spatialcf/upright-se2/numeric-semantics/1.0", + allowed_claim_definition_refs=_sorted_bytes( + "definition:spatialcf/upright-se2/claim-certified-solution/1.0", + upright.UPRIGHT_SE2_FINITE_GAP_CLAIM_DEFINITION_REF, + "definition:spatialcf/upright-se2/claim-proven-unsat/1.0", + "definition:spatialcf/upright-se2/claim-unknown/1.0", + ), + backend_capability_requirements=upright.UPRIGHT_SE2_STAGED_CAPABILITY_REFS, + adapter_capability_requirements=(), + publication_proof_policy_ref=( + "definition:spatialcf/upright-se2/publication-proof-policy/1.0" + ), + ) + return upright.UprightSE2ProfileRegistration.seal( + semantics_profile=semantics_profile, + action_space_profile=action_space_profile, + backend_owner_ref=upright.UPRIGHT_SE2_BACKEND_OWNER_REF, + checker_owner_ref=upright.UPRIGHT_SE2_CHECKER_OWNER_REF, + profile_capability_ref=upright.UPRIGHT_SE2_PROFILE_CAPABILITY_REF, + cardinal_compiler_capability_ref=upright.UPRIGHT_SE2_CARDINAL_COMPILER_CAPABILITY_REF, + cardinal_backend_capability_ref=upright.UPRIGHT_SE2_CARDINAL_BACKEND_CAPABILITY_REF, + cardinal_checker_capability_ref=upright.UPRIGHT_SE2_CARDINAL_CHECKER_CAPABILITY_REF, + continuous_compiler_capability_ref=upright.UPRIGHT_SE2_CONTINUOUS_COMPILER_CAPABILITY_REF, + continuous_backend_capability_ref=upright.UPRIGHT_SE2_CONTINUOUS_BACKEND_CAPABILITY_REF, + continuous_checker_capability_ref=upright.UPRIGHT_SE2_CONTINUOUS_CHECKER_CAPABILITY_REF, + ) + + +def _validate_problem_and_extract_input( + solve_request: CounterfactualSolveRequest, + registration: upright.UprightSE2ProfileRegistration, +) -> tuple[_CompilerInput, _SceneAuthority, upright.UprightSE2ExecutablePolicyBundle]: + problem = solve_request.semantic_problem + if problem.semantics_profile_ref != upright.UPRIGHT_SE2_SEMANTICS_PROFILE_REF: + raise ValueError("semantics profile reference is not the upright se2 profile") + if problem.action_space_profile_ref != upright.UPRIGHT_SE2_PROFILE_REF: + raise ValueError( + "action-space profile reference is not the upright se2 profile" + ) + if ( + problem.numeric_semantics_ref + != registration.semantics_profile.numeric_semantics_ref + ): + raise ValueError("numeric semantics do not match the upright se2 profile") + if canonical_json_bytes(problem.objective_expression) != canonical_json_bytes( + upright.build_upright_se2_objective_expression() + ): + raise ValueError( + "objective expression does not match the fixed five-term upright policy" + ) + + scene_state = problem.scene_state + if scene_state.base_scene_schema_ref != _SCENE_SCHEMA_REF: + raise ValueError("base scene schema does not match the upright se2 profile") + compiler_input = _compiler_input_from_scene(solve_request) + executable_policy_bundle = ( + upright.request_bound_executable_policy_bundle_from_problem(problem) + ) + objective_policy = upright.decode_upright_se2_objective_policy( + executable_policy_bundle + ) + if canonical_json_bytes(problem.definition_bundle) != canonical_json_bytes( + upright.build_upright_se2_semantic_definition_bundle( + registration, objective_policy=objective_policy + ) + ): + raise ValueError( + "semantic definition bundle does not match the request-bound upright closure" + ) + if ( + executable_policy_bundle.profile_registration_sha256 + != registration.profile_registration_sha256 + ): + raise ValueError("executable policy bundle does not bind the upright profile") + authority = _resolve_scene_authority(scene_state.base_scene_payload, compiler_input) + if scene_state.closed_entity_index != _closed_entity_index( + authority.scene, + authority.subject.object_id, + ): + raise ValueError("closed entity index does not match the upright se2 profile") + _validate_complete_state_leaves( + scene_state.canonical_state_leaf_index.leaves, + authority.scene, + compiler_input.subject_id, + ) + _validate_grounded_semantic_operands(problem, authority, compiler_input) + upright.validate_upright_se2_executable_policy_visibility_binding( + executable_policy_bundle, problem + ) + return compiler_input, authority, executable_policy_bundle + + +def _compiler_input_from_scene( + solve_request: CounterfactualSolveRequest, +) -> _CompilerInput: + scene = solve_request.semantic_problem.scene_state + bundles = scene.extension_fact_bundles + facts = tuple(fact for bundle in bundles for fact in bundle.facts) + compiler_facts = tuple( + fact + for fact in facts + if fact.fact_family_ref == _INPUT_FAMILY_REF + and fact.fact_key == _COMPILER_INPUT_FACT_KEY + ) + construction_facts = tuple( + fact + for fact in facts + if fact.fact_family_ref + == "definition:spatialcf/upright-se2/m2-q0-construction/1.0" + ) + if ( + len(compiler_facts) != 1 + or len(construction_facts) > 1 + or len(facts) != 2 + len(construction_facts) + ): + raise ValueError( + "upright se2 compilation requires one compiler input, one executable policy, and at most one construction root" + ) + fact = compiler_facts[0] + if ( + fact.value.value_schema_ref != _INPUT_SCHEMA_REF + or type(fact.value.payload) is not RecordValue + ): + raise ValueError("compiler input must use the fixed upright se2 record schema") + fields = fact.value.payload.fields + expected_names = _sorted_bytes( + "operation_kind", + "operator_ref", + "reference_id", + "subject_id", + "subject_yaw_turns", + "yaw_argument", + ) + if tuple(field.name for field in fields) != expected_names: + raise ValueError( + "compiler input fields do not match the fixed upright se2 schema" + ) + values = {field.name: field.value for field in fields} + compiler_input = _CompilerInput( + operation_kind=_symbol_field(values, "operation_kind"), + operator_ref=_id_field(values, "operator_ref"), + subject_id=_id_field(values, "subject_id"), + reference_id=_id_field(values, "reference_id"), + subject_yaw_turns=_real_field(values, "subject_yaw_turns"), + yaw_argument=_yaw_argument_field(values["yaw_argument"]), + ) + _validate_compiler_input_fact_identity(fact, compiler_input.subject_id) + return compiler_input + + +def _input_entity_id(object_id: str) -> str: + """Return the M1 authorization owner corresponding to one scene object.""" + + return f"entity:{object_id}" + + +def _validate_compiler_input_fact_identity( + fact: ExtensionFact, + subject_id: str, +) -> None: + if ( + fact.fact_family_ref != _INPUT_FAMILY_REF + or fact.subject_entity_id != _input_entity_id(subject_id) + or fact.fact_key != _COMPILER_INPUT_FACT_KEY + ): + raise ValueError( + "compiler input fact identity does not match the upright se2 profile" + ) + + +def _real_field(values: dict[str, TypedValue], name: str) -> float: + value = values[name] + if ( + value.value_schema_ref != _REAL_SCHEMA_REF + or type(value.payload) is not FiniteRealValue + ): + raise ValueError(f"compiler input field {name!r} must be a finite real") + return value.payload.value + + +def _integer_field(values: dict[str, TypedValue], name: str) -> int: + value = values[name] + if ( + value.value_schema_ref != _INTEGER_SCHEMA_REF + or type(value.payload) is not IntegerValue + ): + raise ValueError(f"compiler input field {name!r} must be an integer") + return value.payload.value + + +def _id_field(values: dict[str, TypedValue], name: str) -> str: + value = values[name] + if ( + value.value_schema_ref != _ID_SCHEMA_REF + or type(value.payload) is not CanonicalIdValue + ): + raise ValueError(f"compiler input field {name!r} must be a canonical ID") + return value.payload.value + + +def _symbol_field(values: dict[str, TypedValue], name: str) -> str: + value = values[name] + if ( + value.value_schema_ref != _ENUM_SCHEMA_REF + or type(value.payload) is not EnumSymbolValue + ): + raise ValueError(f"compiler input field {name!r} must be an enum symbol") + return value.payload.symbol + + +def _yaw_argument_field( + value: TypedValue, +) -> upright.CardinalYaw | upright.ContinuousYawDomain: + if ( + value.value_schema_ref != _YAW_ARGUMENT_SCHEMA_REF + or type(value.payload) is not RecordValue + ): + raise ValueError("yaw argument must use the fixed upright se2 record schema") + fields = value.payload.fields + values = {field.name: field.value for field in fields} + if "kind" not in values: + raise ValueError("yaw argument must declare one wire branch") + kind = _symbol_field(values, "kind") + if kind == "CARDINAL": + if tuple(field.name for field in fields) != _sorted_bytes( + "kind", "quarter_turns_ccw" + ): + raise ValueError("cardinal yaw argument fields are not canonical") + return upright.CardinalYaw(q=_integer_field(values, "quarter_turns_ccw")) + if kind == "ARC": + if tuple(field.name for field in fields) != _sorted_bytes( + "ccw_sweep_turns", "kind", "start_turns" + ): + raise ValueError("arc yaw argument fields are not canonical") + return upright.ContinuousYawArc( + start_angle=upright.CanonicalSO2Angle( + turns=_real_field(values, "start_turns") + ), + ccw_sweep_turns=_real_field(values, "ccw_sweep_turns"), + ) + if kind == "FULL_CIRCLE": + if tuple(field.name for field in fields) != _sorted_bytes("kind"): + raise ValueError("full-circle yaw argument must not carry arc fields") + return upright.ContinuousYawFullCircle() + raise ValueError("yaw argument must be CARDINAL, ARC, or FULL_CIRCLE") + + +def _resolve_scene_authority( + scene: CanonicalScene, + compiler_input: _CompilerInput, +) -> _SceneAuthority: + objects = {object_.object_id: object_ for object_ in scene.objects.values or ()} + subject = objects.get(compiler_input.subject_id) + if subject is None: + raise ValueError("subject object must name one Canonical Scene object") + reference = objects.get(compiler_input.reference_id) + if reference is None: + raise ValueError("reference object must name one Canonical Scene object") + if subject.object_id == reference.object_id: + raise ValueError("reference object must not name the subject object") + if not subject.movable: + raise ValueError("subject object must be movable") + if subject.pose.anchor_kind != "OBJECT_PIVOT": + raise ValueError("subject object must expose an OBJECT_PIVOT pose") + if reference.pose.anchor_kind != "OBJECT_PIVOT": + raise ValueError("reference object must expose an OBJECT_PIVOT pose") + _validate_required_exact_scene_sources(scene, subject) + return _SceneAuthority(scene=scene, subject=subject, reference=reference) + + +def _validate_required_exact_scene_sources( + scene: CanonicalScene, subject: object +) -> None: + """Reject incomplete sources before materializing closed derived inputs.""" + + for field_name, label in ( + ("objects", "objects"), + ("geometry_instances", "geometry instances"), + ("collision_bodies", "collision bodies"), + ("support_surfaces", "support surfaces"), + ("cameras", "cameras"), + ("baseline_observations", "baseline observations"), + ): + _known_exact_source_values(getattr(scene, field_name), label) + + assignment = subject.support_assignment + if ( + assignment.availability is not FactAvailabilityV2.KNOWN + or assignment.surface_id is None + ): + raise ValueError( + "subject support assignment must be KNOWN with a support surface" + ) + upright.validate_required_upright_support_surface(scene, subject.object_id) + + +def _known_exact_source_values( + facts: FactSetV2, + label: str, +) -> tuple[object, ...]: + """Return the sole complete branch accepted by this compilation profile.""" + + if ( + facts.availability is not FactAvailabilityV2.KNOWN + or facts.completeness is not FactCompletenessV2.EXACT + or facts.values is None + or facts.inner_values is not None + or facts.outer_values is not None + ): + raise ValueError(f"{label} must be a KNOWN EXACT fact set") + return facts.values + + +def _validate_complete_state_leaves( + leaves: tuple[StateVariableRef, ...], + scene: CanonicalScene, + subject_id: str, +) -> None: + if leaves != _expected_state_leaves(scene, subject_id): + raise ValueError( + "complete state leaf index does not match the upright se2 profile" + ) + + +def _validate_grounded_semantic_operands( + problem: CounterfactualProblemIR, + authority: _SceneAuthority, + compiler_input: _CompilerInput, +) -> None: + """Resolve every M3 goal/preservation/visibility operand against scene authority.""" + + if ( + len(problem.before_preconditions) != 1 + or type(problem.before_preconditions[0]) is not BeforePrecondition + ): + raise ValueError( + "semantic closure requires one grounded target before-precondition" + ) + before_atom = _direct_predicate_atom(problem.before_preconditions[0].formula) + if before_atom.predicate_ref != upright.UPRIGHT_SE2_TARGET_RELATION_PREDICATE_REF: + raise ValueError( + "semantic closure before-precondition must use the target relation" + ) + before_relation = _validate_target_relation_operands( + before_atom, + subject_id=authority.subject.object_id, + reference_id=authority.reference.object_id, + phase="BEFORE", + ) + + if type(problem.after_goal) is not AfterGoal: + raise ValueError("semantic closure after-goal must use the target relation") + after_atom = _direct_predicate_atom(problem.after_goal.formula) + if after_atom.predicate_ref != upright.UPRIGHT_SE2_TARGET_RELATION_PREDICATE_REF: + raise ValueError("semantic closure after-goal must use the target relation") + after_relation = _validate_target_relation_operands( + after_atom, + subject_id=authority.subject.object_id, + reference_id=authority.reference.object_id, + phase="AFTER", + ) + if before_relation == after_relation: + raise ValueError( + "semantic target relation must name distinct before and after relations" + ) + + if ( + len(problem.preservation_invariants) != 1 + or type(problem.preservation_invariants[0]) is not PreservationInvariant + ): + raise ValueError( + "semantic closure requires one grounded preservation invariant" + ) + preservation = problem.preservation_invariants[0] + if preservation.transition_comparator_ref != ( + "definition:spatialcf/upright-se2/preservation-transition-comparator/1.0" + ): + raise ValueError( + "preservation transition comparator does not match the upright policy" + ) + for phase, formula in ( + ("BEFORE", preservation.before_formula), + ("AFTER", preservation.after_formula), + ): + atom = _direct_predicate_atom(formula) + if atom.predicate_ref != upright.UPRIGHT_SE2_PRESERVATION_PREDICATE_REF: + raise ValueError( + "preservation invariant must use the upright preservation predicate" + ) + _validate_preservation_operands(atom, compiler_input.subject_id, phase) + + expected_observations = _known_exact_source_values( + authority.scene.baseline_observations, + "baseline observations", + ) + if len(problem.explicit_observation_obligations) != len(expected_observations): + raise ValueError( + "semantic closure must ground every exact visibility observation" + ) + actual_by_observation_id: dict[str, ObservationObligation] = {} + for obligation in problem.explicit_observation_obligations: + if type(obligation) is not ObservationObligation or obligation.phase != "AFTER": + raise ValueError( + "semantic closure visibility obligations must be AFTER observations" + ) + if obligation.evidence_policy_ref != ( + "definition:spatialcf/upright-se2/visibility-evidence-policy/1.0" + ): + raise ValueError( + "visibility evidence policy does not match the upright policy" + ) + atom = _direct_predicate_atom(obligation.formula) + if atom.predicate_ref != upright.UPRIGHT_SE2_VISIBILITY_PREDICATE_REF: + raise ValueError( + "visibility obligation must use the upright visibility predicate" + ) + observation_id = _validate_visibility_operands(atom, authority.scene) + if observation_id in actual_by_observation_id: + raise ValueError( + "semantic closure visibility obligations must not duplicate observations" + ) + actual_by_observation_id[observation_id] = obligation + expected_by_observation_id = { + observation.observation_id: observation for observation in expected_observations + } + if set(actual_by_observation_id) != set(expected_by_observation_id): + raise ValueError( + "semantic closure visibility obligations must cover the exact observation roster" + ) + + +def _direct_predicate_atom(formula: object) -> PredicateAtom: + if type(formula) is not PredicateAtom: + raise ValueError( + "semantic obligations must use direct grounded predicate atoms" + ) + if not formula.operands: + raise ValueError("semantic predicate atoms must carry explicit operands") + return formula + + +def _validate_target_relation_operands( + atom: PredicateAtom, + *, + subject_id: str, + reference_id: str, + phase: str, +) -> str: + if len(atom.operands) != 4: + raise ValueError("target relation predicate requires four explicit operands") + _require_reference_operand( + atom.operands[0], + schema_ref="schema:spatialcf/upright-se2/object-ref/1.0", + kind=ValueKind.OBJECT_REF, + reference=subject_id, + ) + _require_reference_operand( + atom.operands[1], + schema_ref="schema:spatialcf/upright-se2/object-ref/1.0", + kind=ValueKind.OBJECT_REF, + reference=reference_id, + ) + relation = _require_symbol_operand( + atom.operands[2], + schema_ref="schema:spatialcf/upright-se2/relation-symbol/1.0", + ) + if relation not in { + "relation:LEFT", + "relation:RIGHT", + "relation:FRONT", + "relation:BEHIND", + "relation:NEAR", + "relation:FAR", + }: + raise ValueError("target relation operand must name one registered relation") + if ( + _require_symbol_operand( + atom.operands[3], + schema_ref="schema:spatialcf/upright-se2/phase-symbol/1.0", + ) + != f"phase:{phase}" + ): + raise ValueError("target relation phase operand does not match its obligation") + return relation + + +def _validate_preservation_operands( + atom: PredicateAtom, + subject_id: str, + phase: str, +) -> None: + if len(atom.operands) != 3: + raise ValueError("preservation predicate requires three explicit operands") + _require_reference_operand( + atom.operands[0], + schema_ref="schema:spatialcf/upright-se2/entity-ref/1.0", + kind=ValueKind.ENTITY_REF, + reference=f"entity:{subject_id}", + ) + if ( + _require_symbol_operand( + atom.operands[1], + schema_ref="schema:spatialcf/upright-se2/preservation-selector/1.0", + ) + != "preservation:FROZEN_NONPRIMARY_LEAVES" + ): + raise ValueError("preservation selector must name the frozen non-primary state") + if ( + _require_symbol_operand( + atom.operands[2], + schema_ref="schema:spatialcf/upright-se2/phase-symbol/1.0", + ) + != f"phase:{phase}" + ): + raise ValueError("preservation phase operand does not match its formula") + + +def _validate_visibility_operands(atom: PredicateAtom, scene: CanonicalScene) -> str: + if len(atom.operands) != 5: + raise ValueError("visibility predicate requires five explicit operands") + camera_id = _require_reference_operand( + atom.operands[0], + schema_ref="schema:spatialcf/upright-se2/camera-ref/1.0", + kind=ValueKind.CAMERA_REF, + ) + object_id = _require_reference_operand( + atom.operands[1], + schema_ref="schema:spatialcf/upright-se2/object-ref/1.0", + kind=ValueKind.OBJECT_REF, + ) + metric_id = _require_symbol_operand( + atom.operands[2], + schema_ref="schema:spatialcf/upright-se2/visibility-metric-symbol/1.0", + ) + observation_id = _require_id_operand( + atom.operands[3], + schema_ref="schema:spatialcf/upright-se2/observation-ref/1.0", + ) + if ( + _require_symbol_operand( + atom.operands[4], + schema_ref="schema:spatialcf/upright-se2/phase-symbol/1.0", + ) + != "phase:AFTER" + ): + raise ValueError("visibility phase operand must be AFTER") + observations = _known_exact_source_values( + scene.baseline_observations, + "baseline observations", + ) + matching = tuple( + observation + for observation in observations + if observation.observation_id == observation_id + ) + if len(matching) != 1: + raise ValueError("visibility operand must name one exact baseline observation") + observation = matching[0] + if ( + camera_id != observation.camera_id + or object_id != observation.object_id + or metric_id != observation.metric_definition_id + ): + raise ValueError( + "visibility operands do not bind their exact baseline observation" + ) + return observation_id + + +def _require_reference_operand( + value: TypedValue, + *, + schema_ref: str, + kind: ValueKind, + reference: str | None = None, +) -> str: + if ( + value.value_schema_ref != schema_ref + or type(value.payload) is not ReferenceValue + or value.payload.kind is not kind + ): + raise ValueError( + "semantic reference operand has the wrong schema or reference kind" + ) + if reference is not None and value.payload.reference != reference: + raise ValueError( + "semantic reference operand does not bind the named scene authority" + ) + return value.payload.reference + + +def _require_symbol_operand(value: TypedValue, *, schema_ref: str) -> str: + if ( + value.value_schema_ref != schema_ref + or type(value.payload) is not EnumSymbolValue + ): + raise ValueError("semantic symbol operand has the wrong schema") + return value.payload.symbol + + +def _require_id_operand(value: TypedValue, *, schema_ref: str) -> str: + if ( + value.value_schema_ref != schema_ref + or type(value.payload) is not CanonicalIdValue + ): + raise ValueError("semantic identifier operand has the wrong schema") + return value.payload.value + + +def _expected_state_leaves( + scene: CanonicalScene, + subject_id: str, +) -> tuple[StateVariableRef, ...]: + rows: list[tuple[str, str]] = [ + *((subject_id, role) for role in (*_PRIMARY_ROLES, "subject-world-z")), + *((subject_id, role) for role in _DERIVED_ROLES), + ] + for family_name, identifier in _SOURCE_FIELDS: + role = f"frozen-source-{family_name}" + rows.extend( + (entity_id, role) + for entity_id in _fact_ids(scene, family_name.replace("-", "_"), identifier) + ) + rows.append((scene.scene_id, f"frozen-source-{family_name}-set")) + return _sorted_bytes(*(_state_leaf(entity_id, role) for entity_id, role in rows)) + + +def _closed_entity_index( + scene: CanonicalScene, + subject_id: str, +) -> tuple[str, ...]: + return _sorted_bytes( + scene.scene_id, + _input_entity_id(subject_id), + "entity:upright-se2-policy", + *( + entity_id + for family_name, identifier in _SOURCE_FIELDS + for entity_id in _fact_ids(scene, family_name.replace("-", "_"), identifier) + ), + ) + + +def _fact_ids( + scene: CanonicalScene, field_name: str, identifier: str +) -> tuple[str, ...]: + facts = getattr(scene, field_name) + return tuple( + sorted( + { + getattr(value, identifier) + for values in (facts.values, facts.inner_values, facts.outer_values) + if values is not None + for value in values + } + ) + ) + + +def _state_leaf(entity_id: str, role: str) -> StateVariableRef: + return StateVariableRef( + state_variable_schema_ref=f"schema:spatialcf/upright-se2/{role}/1.0", + state_schema_ref=_STATE_SCHEMA_REF, + fact_family_ref=_STATE_FAMILY_REF, + entity_or_fact_key=entity_id, + field_path_ref=f"field-path:upright-se2-{role}", + ) + + +def _validate_operational_closure( + solve_request: CounterfactualSolveRequest, + registration: upright.UprightSE2ProfileRegistration, + *, + operation_kind: str = "CARDINAL", +) -> None: + solve_policy = upright.decode_upright_se2_solve_policy_definition_payload( + solve_request.solve_policy_definition_bundle + ) + if ( + solve_policy.profile_registration_sha256 + != registration.profile_registration_sha256 + or solve_policy.objective_bound_policy_ref + != solve_request.solver_config.objective_bound_policy_ref + or solve_policy.exact_global_claim_definition_ref + != upright.UPRIGHT_SE2_EXACT_GLOBAL_CLAIM_DEFINITION_REF + or solve_policy.finite_gap_claim_definition_ref + != upright.UPRIGHT_SE2_FINITE_GAP_CLAIM_DEFINITION_REF + ): + raise ValueError( + "solve policy definition bundle does not match the upright se2 closure" + ) + if operation_kind not in {"CARDINAL", "CONTINUOUS"}: + raise ValueError("operational closure operation kind is unknown") + continuous = operation_kind == "CONTINUOUS" + _validate_registry( + solve_request.implementation_registry_snapshot, + registration, + continuous=continuous, + ) + if canonical_json_bytes( + solve_request.backend_descriptor_bundle + ) != canonical_json_bytes( + _backend_descriptor_bundle(registration, continuous=continuous) + ): + raise ValueError( + "backend descriptor bundle does not match the upright se2 closure" + ) + if canonical_json_bytes(solve_request.solver_config) != canonical_json_bytes( + _solver_config() + ): + raise ValueError("solver config does not match the upright se2 closure") + if canonical_json_bytes(solve_request.proof_policy) != canonical_json_bytes( + _proof_policy(continuous=continuous) + ): + raise ValueError("proof policy does not match the upright se2 closure") + _validate_request_resource_policy(solve_request.resource_policy) + if canonical_json_bytes( + solve_request.backend_routing_policy + ) != canonical_json_bytes(_backend_routing_policy()): + raise ValueError( + "backend routing policy does not match the upright se2 closure" + ) + + +def _validate_registry( + registry: ImplementationRegistrySnapshot, + registration: upright.UprightSE2ProfileRegistration, + *, + continuous: bool = False, +) -> None: + build_by_owner = dict(registry.implementation_build_hashes) + if ( + build_by_owner.get(upright.UPRIGHT_SE2_COMPILER_OWNER_REF) + != upright.UPRIGHT_SE2_COMPILER_BUILD_SHA256 + ): + raise ValueError("compiler build does not match the fixed upright se2 compiler") + if canonical_json_bytes(registry) != canonical_json_bytes( + _implementation_registry(registration, continuous=continuous) + ): + raise ValueError( + "implementation registry does not match the upright se2 closure" + ) + + +def _validate_intervention_authorization( + solve_request: CounterfactualSolveRequest, + compiler_input: _CompilerInput, + authority: _SceneAuthority, +) -> upright.UprightSE2TranslationDomain: + authorization = solve_request.semantic_problem.intervention_authorization + if authorization.editable_entity_ids != ( + _input_entity_id(authority.subject.object_id), + ): + raise ValueError("upright se2 authorization must permit exactly the subject") + if authorization.allowed_operator_refs != (compiler_input.operator_ref,): + raise ValueError( + "upright se2 authorization must permit exactly the requested operator" + ) + if authorization.authorized_primary_write_set != _primary_write_set( + compiler_input.subject_id + ): + raise ValueError("upright se2 authorization must contain only subject X/Y/yaw") + if ( + authorization.maximum_program_steps != 1 + or authorization.maximum_edited_entities != 1 + ): + raise ValueError( + "upright se2 authorization requires one step and one edited entity" + ) + if authorization.required_derived_rule_refs != (_DERIVED_RULE_REF,): + raise ValueError("upright se2 authorization must require the derived fact rule") + if authorization.complete_state_delta_policy_ref != ( + "definition:spatialcf/upright-se2/complete-state-delta/1.0" + ): + raise ValueError("upright se2 authorization must use the complete state policy") + if compiler_input.operation_kind == "CARDINAL": + if compiler_input.operator_ref not in ( + upright.UPRIGHT_SE2_CARDINAL_OWN_PIVOT_OPERATOR_REF, + upright.UPRIGHT_SE2_CARDINAL_REFERENCE_PIVOT_OPERATOR_REF, + ): + raise ValueError( + "cardinal compiler input must name a registered cardinal operator" + ) + if type(compiler_input.yaw_argument) is not upright.CardinalYaw: + raise ValueError("cardinal requests must carry one cardinal yaw argument") + elif compiler_input.operation_kind == "CONTINUOUS": + if compiler_input.operator_ref not in ( + upright.UPRIGHT_SE2_CONTINUOUS_OWN_PIVOT_OPERATOR_REF, + upright.UPRIGHT_SE2_CONTINUOUS_REFERENCE_PIVOT_OPERATOR_REF, + ): + raise ValueError( + "continuous compiler input must name a registered continuous operator" + ) + if not isinstance( + compiler_input.yaw_argument, + (upright.ContinuousYawArc, upright.ContinuousYawFullCircle), + ): + raise ValueError("continuous requests must carry one continuous yaw domain") + else: + raise ValueError("compiler input operation kind must be CARDINAL or CONTINUOUS") + return _translation_domain_from_authorization( + authorization.variable_bounds, + authority.subject.object_id, + ) + + +def _validate_explicit_pose_yaw( + compiler_input: _CompilerInput, + solve_request: CounterfactualSolveRequest, + authority: _SceneAuthority, +) -> upright.CanonicalSO2Angle: + explicit_yaw = upright.CanonicalSO2Angle(turns=compiler_input.subject_yaw_turns) + rotation = authority.subject.pose.world_from_object.rotation + upright.validate_directed_yaw_quaternion_consistency(explicit_yaw, rotation) + upright.ExplicitPoseYawBinding.seal( + entity_id=compiler_input.subject_id, + base_pose_sha256=canonical_sha256( + { + "scene_state_sha256": solve_request.semantic_problem.scene_state.scene_state_sha256, + "subject_object_id": compiler_input.subject_id, + "object_pivot_pose": authority.subject.pose, + }, + domain=_POSE_STATE_HASH_DOMAIN, + ), + explicit_yaw=explicit_yaw, + yaw_to_pose_rule_ref=upright.UPRIGHT_SE2_YAW_TO_POSE_RULE_REF, + ) + return explicit_yaw + + +def _resolve_pivot_binding( + compiler_input: _CompilerInput, + solve_request: CounterfactualSolveRequest, + authority: _SceneAuthority, +) -> upright.FixedPivotBinding: + if compiler_input.operator_ref in ( + upright.UPRIGHT_SE2_CARDINAL_OWN_PIVOT_OPERATOR_REF, + upright.UPRIGHT_SE2_CONTINUOUS_OWN_PIVOT_OPERATOR_REF, + ): + pivot_mode = upright.PivotMode.OWN + pivot_entity_id = compiler_input.subject_id + pivot_pose = authority.subject.pose + else: + pivot_mode = upright.PivotMode.REFERENCE + pivot_entity_id = compiler_input.reference_id + pivot_pose = authority.reference.pose + return upright.FixedPivotBinding.seal( + subject_id=compiler_input.subject_id, + pivot_mode=pivot_mode, + pivot_entity_id=pivot_entity_id, + pivot_state_sha256=canonical_sha256( + { + "scene_state_sha256": solve_request.semantic_problem.scene_state.scene_state_sha256, + "pivot_entity_id": pivot_entity_id, + "object_pivot_pose": pivot_pose, + }, + domain=_PIVOT_STATE_HASH_DOMAIN, + ), + ) + + +def _validate_continuous_request( + compiler_input: _CompilerInput, + pivot_binding: upright.FixedPivotBinding, +) -> None: + yaw_domain = compiler_input.yaw_argument + if not isinstance( + yaw_domain, + (upright.ContinuousYawArc, upright.ContinuousYawFullCircle), + ): + raise TypeError("continuous request must carry a continuous yaw domain") + upright.ContinuousYawAuthorization.seal( + subject_id=compiler_input.subject_id, + operator_ref=compiler_input.operator_ref, + pivot_binding=pivot_binding, + yaw_domain=yaw_domain, + ) + + +def _translation_domain_from_authorization( + bounds: tuple[object, ...], + subject_id: str, +) -> upright.UprightSE2TranslationDomain: + expected = { + "subject-world-x": _state_leaf(subject_id, "subject-world-x"), + "subject-world-y": _state_leaf(subject_id, "subject-world-y"), + } + if len(bounds) != len(expected): + raise ValueError( + "upright se2 authorization requires complete world-XY variable bounds" + ) + resolved: dict[str, tuple[upright.ExactDyadic, upright.ExactDyadic]] = {} + for bound in bounds: + role = next( + ( + candidate + for candidate, state_leaf in expected.items() + if bound.state_variable_ref == state_leaf + ), + None, + ) + if role is None: + raise ValueError( + "world-XY variable bounds must target the subject X/Y leaves" + ) + if ( + bound.value_schema_ref, + bound.frame_ref, + bound.unit_ref, + bound.topology_ref, + ) != ( + _REAL_SCHEMA_REF, + _WORLD_XY_FRAME_REF, + _METRE_UNIT_REF, + _CLOSED_INTERVAL_TOPOLOGY_REF, + ): + raise ValueError( + "world-XY variable bounds do not use the authorized type semantics" + ) + domain = bound.typed_domain + if ( + domain.value_schema_ref != _REAL_SCHEMA_REF + or type(domain.payload) is not IntervalValue + or domain.payload.endpoint_schema_ref != _REAL_SCHEMA_REF + or type(domain.payload.lower) is not FiniteRealValue + or type(domain.payload.upper) is not FiniteRealValue + or not domain.payload.lower_closed + or not domain.payload.upper_closed + ): + raise ValueError( + "world-XY variable bounds must use closed finite-real intervals" + ) + resolved[role] = ( + _dyadic_from_float(domain.payload.lower.value), + _dyadic_from_float(domain.payload.upper.value), + ) + if set(resolved) != set(expected): + raise ValueError("world-XY variable bounds must be complete") + return upright.UprightSE2TranslationDomain( + x_lower=resolved["subject-world-x"][0], + x_upper=resolved["subject-world-x"][1], + y_lower=resolved["subject-world-y"][0], + y_upper=resolved["subject-world-y"][1], + ) + + +def _compile_cardinal( + *, + solve_request: CounterfactualSolveRequest, + registration: upright.UprightSE2ProfileRegistration, + compiler_input: _CompilerInput, + authority: _SceneAuthority, + base_yaw: upright.CanonicalSO2Angle, + translation_domain: upright.UprightSE2TranslationDomain, + authorization: upright.CardinalYawAuthorization, + executable_policy_bundle: upright.UprightSE2ExecutablePolicyBundle, +) -> upright.UprightSE2Compilation: + operation = upright.UprightSE2CardinalOperation( + authorization=authorization, + translation_domain=translation_domain, + inverse_quarter_turns_ccw=cardinal_inverse_quarter_turns(authorization.yaw.q), + maximum_program_steps=1, + maximum_edited_entities=1, + ) + endpoint_recipe = _endpoint_construction_recipe( + solve_request=solve_request, + authority=authority, + operation=operation, + base_yaw=base_yaw, + ) + footprint = _state_footprint(solve_request, compiler_input.subject_id) + grounded_obligations = _grounded_obligations(solve_request) + semantic_closure = upright.build_upright_se2_semantic_closure( + profile_registration=registration, + semantic_problem=solve_request.semantic_problem, + definition_bundle=solve_request.semantic_problem.definition_bundle, + grounded_obligations=grounded_obligations, + objective_expression=solve_request.semantic_problem.objective_expression, + executable_policy_bundle=executable_policy_bundle, + resource_policy=solve_request.resource_policy, + ) + closure = upright.UprightSE2CompilerClosure.seal( + profile_registration_sha256=registration.profile_registration_sha256, + definition_bundle_sha256=solve_request.semantic_problem.definition_bundle.definition_bundle_sha256, + solve_policy_definition_bundle_sha256=( + solve_request.solve_policy_definition_bundle.definition_bundle_sha256 + ), + semantic_closure_sha256=semantic_closure.semantic_closure_sha256, + policy_bundle_sha256=semantic_closure.policy_bundle_sha256, + resource_policy_sha256=solve_request.resource_policy.resource_policy_sha256, + compiler_owner_ref=upright.UPRIGHT_SE2_COMPILER_OWNER_REF, + compiler_build_sha256=upright.UPRIGHT_SE2_COMPILER_BUILD_SHA256, + ) + cell = _compiled_cell(operation) + return upright.UprightSE2Compilation.seal( + solve_request_sha256=solve_request.solve_request_sha256, + source_solve_request=solve_request, + closure=closure, + operation=operation, + endpoint_construction_recipe=endpoint_recipe, + state_footprint=footprint, + grounded_obligations=grounded_obligations, + semantic_closure=semantic_closure, + compiled_cells=(cell,), + ) + + +def compile_upright_se2_continuous( + solve_request: CounterfactualSolveRequest, +) -> upright.UprightSE2ContinuousCompilation: + """Compile a registered continuous request through the sole M3 compiler. + + The public sibling intentionally rejects cardinal requests rather than + converting them or widening the retained cardinal compilation API. + """ + + compiled = compile_upright_se2(solve_request) + if type(compiled) is not upright.UprightSE2ContinuousCompilation: + raise ValueError("continuous compiler requires one continuous M3 request") + return compiled + + +def _compile_continuous( + solve_request: CounterfactualSolveRequest, + *, + registration: upright.UprightSE2ProfileRegistration, + compiler_input: _CompilerInput, + authority: _SceneAuthority, + executable_policy_bundle: upright.UprightSE2ExecutablePolicyBundle, + translation_domain: upright.UprightSE2TranslationDomain, + base_yaw: upright.CanonicalSO2Angle, + pivot_binding: upright.FixedPivotBinding, +) -> upright.UprightSE2ContinuousCompilation: + """Construct continuous roots only; no search, geometry, or checking.""" + + if not isinstance( + compiler_input.yaw_argument, + (upright.ContinuousYawArc, upright.ContinuousYawFullCircle), + ): + raise ValueError( # noqa: TRY004 - preserves the public invalid-domain contract. + "continuous compilation requires a continuous yaw domain" + ) + authorization = upright.ContinuousYawAuthorization.seal( + subject_id=compiler_input.subject_id, + operator_ref=compiler_input.operator_ref, + pivot_binding=pivot_binding, + yaw_domain=compiler_input.yaw_argument, + ) + operation = upright.UprightSE2ContinuousOperation( + authorization=authorization, + translation_domain=translation_domain, + maximum_program_steps=1, + maximum_edited_entities=1, + ) + budget = SO2AtomicBudgetV2(limit=_bridge_resource_cap(executable_policy_bundle)) + lift_outcome = compile_continuous_yaw_lift_v4( + operation.yaw_domain, + atomic_budget=budget, + ) + if lift_outcome.kind is not ContinuousYawIntervalKindV4.EXACT: + # This is an input/interval closure failure, not a backend conclusion. + raise ValueError( + "continuous compiler could not construct exact lifted yaw root: " + + ",".join(lift_outcome.finding_codes) + ) + assert lift_outcome.bounds is not None + lift = lift_outcome.bounds.lift + endpoint_recipe = upright.UprightSE2ContinuousEndpointConstructionRecipe.seal( + source_scene_state_sha256=( + solve_request.semantic_problem.scene_state.scene_state_sha256 + ), + operation_authorization_sha256=operation.authorization_sha256, + subject_id=authority.subject.object_id, + reference_id=authority.reference.object_id, + pivot_binding=operation.pivot_binding, + yaw_domain=operation.yaw_domain, + translation_domain=operation.translation_domain, + subject_before_pose=authority.subject.pose.world_from_object, + reference_before_pose=authority.reference.pose.world_from_object, + subject_yaw_turns=base_yaw, + evaluation_scene=authority.scene, + ) + footprint = _state_footprint(solve_request, compiler_input.subject_id) + grounded_obligations = _grounded_obligations(solve_request) + semantic_closure = upright.build_upright_se2_continuous_semantic_closure( + profile_registration=registration, + semantic_problem=solve_request.semantic_problem, + definition_bundle=solve_request.semantic_problem.definition_bundle, + grounded_obligations=grounded_obligations, + objective_expression=solve_request.semantic_problem.objective_expression, + executable_policy_bundle=executable_policy_bundle, + resource_policy=solve_request.resource_policy, + ) + closure = upright.UprightSE2CompilerClosure.seal( + profile_registration_sha256=registration.profile_registration_sha256, + definition_bundle_sha256=( + solve_request.semantic_problem.definition_bundle.definition_bundle_sha256 + ), + solve_policy_definition_bundle_sha256=( + solve_request.solve_policy_definition_bundle.definition_bundle_sha256 + ), + semantic_closure_sha256=semantic_closure.semantic_closure_sha256, + policy_bundle_sha256=semantic_closure.policy_bundle_sha256, + resource_policy_sha256=solve_request.resource_policy.resource_policy_sha256, + compiler_owner_ref=upright.UPRIGHT_SE2_COMPILER_OWNER_REF, + compiler_build_sha256=upright.UPRIGHT_SE2_COMPILER_BUILD_SHA256, + ) + interval = lift.intervals[0] + cell = upright.UprightSE2CompiledCell.seal( + cell_id=( + "cell:spatialcf/upright-se2/continuous/" + f"{operation.authorization_sha256}/{lift.continuous_yaw_lift_sha256}" + ), + authorization_sha256=operation.authorization_sha256, + x_lower=operation.translation_domain.x_lower, + x_upper=operation.translation_domain.x_upper, + y_lower=operation.translation_domain.y_lower, + y_upper=operation.translation_domain.y_upper, + yaw_interval=interval, + ) + return upright.UprightSE2ContinuousCompilation.seal( + solve_request_sha256=solve_request.solve_request_sha256, + source_solve_request=solve_request, + closure=closure, + operation=operation, + endpoint_construction_recipe=endpoint_recipe, + state_footprint=footprint, + grounded_obligations=grounded_obligations, + semantic_closure=semantic_closure, + compiled_cells=(cell,), + continuous_yaw_lift=lift, + ) + + +def _endpoint_construction_recipe( + *, + solve_request: CounterfactualSolveRequest, + authority: _SceneAuthority, + operation: upright.UprightSE2CardinalOperation, + base_yaw: upright.CanonicalSO2Angle, +) -> upright.UprightSE2EndpointConstructionRecipe: + """Bind source-only data required by later endpoint materialization.""" + + return upright.UprightSE2EndpointConstructionRecipe.seal( + source_scene_state_sha256=( + solve_request.semantic_problem.scene_state.scene_state_sha256 + ), + operation_authorization_sha256=operation.authorization_sha256, + subject_id=authority.subject.object_id, + reference_id=authority.reference.object_id, + pivot_binding=operation.pivot_binding, + quarter_turns_ccw=operation.quarter_turns_ccw, + translation_domain=operation.translation_domain, + subject_before_pose=authority.subject.pose.world_from_object, + reference_before_pose=authority.reference.pose.world_from_object, + subject_yaw_turns=base_yaw, + evaluation_scene=authority.scene, + ) + + +def materialize_upright_se2_endpoint( + compilation: upright.UprightSE2Compilation, + translation_xy_m: Vec2, +) -> upright.UprightSE2MaterializedEndpoint: + """Purely materialize one authorized endpoint from a domain compilation. + + The caller selects ``translation_xy_m`` only here. This function has no + solver/backend/checker dependency and returns no feasibility or execution + result. + """ + + if type(compilation) is not upright.UprightSE2Compilation: + raise TypeError("materialization requires an UprightSE2Compilation") + if type(translation_xy_m) is not Vec2: + raise TypeError("materialization requires an exact Vec2 translation") + recipe = compilation.endpoint_construction_recipe + after_state = _after_state_template(recipe, translation_xy_m) + program = _materialized_program(compilation, after_state, translation_xy_m) + return upright.UprightSE2MaterializedEndpoint.seal( + upright_se2_compilation_sha256=compilation.upright_se2_compilation_sha256, + compilation=compilation, + endpoint_construction_recipe=recipe, + translation_xy_m=translation_xy_m, + after_state=after_state, + program=program, + ) + + +def materialize_upright_se2_continuous_endpoint( + compilation: upright.UprightSE2ContinuousCompilation, + translation_xy_m: Vec2, + selected_lifted_yaw: upright.ExactDyadic, +) -> upright.UprightSE2ContinuousMaterializedEndpoint: + """Materialize one authorized continuous endpoint without evaluating it. + + The selected point remains untrusted until the Task 8 checker performs a + fresh replay. This compiler seam owns only source-bound endpoint/program + construction; it does not call a geometry kernel, search, or checker. + """ + + if type(compilation) is not upright.UprightSE2ContinuousCompilation: + raise TypeError("continuous materialization requires continuous compilation") + if type(translation_xy_m) is not Vec2: + raise TypeError("continuous materialization requires an exact Vec2") + if type(selected_lifted_yaw) is not upright.ExactDyadic: + raise TypeError("continuous materialization requires an exact dyadic yaw") + recipe = compilation.endpoint_construction_recipe + selected = selected_lifted_yaw.as_fraction + root = compilation.compiled_cells[0].yaw_interval + if not root.lower.as_fraction <= selected <= root.upper.as_fraction: + raise ValueError("continuous endpoint yaw is outside compiled root") + if ( + isinstance(recipe.yaw_domain, upright.ContinuousYawFullCircle) + and selected == root.upper.as_fraction + ): + raise ValueError("continuous endpoint rejects the upper full-circle seam alias") + x = Fraction.from_float(translation_xy_m.x) + y = Fraction.from_float(translation_xy_m.y) + if not ( + recipe.translation_domain.x_lower.as_fraction + <= x + <= recipe.translation_domain.x_upper.as_fraction + and recipe.translation_domain.y_lower.as_fraction + <= y + <= recipe.translation_domain.y_upper.as_fraction + ): + raise ValueError("continuous endpoint translation is outside compiled root") + after_state = _continuous_after_state_template( + recipe, + translation_xy_m, + selected_lifted_yaw, + ) + source_problem = compilation.source_solve_request.semantic_problem + after_scene = upright._materialized_expected_after_scene_state( + compilation, after_state + ) + program = EditProgram.seal( + program_id="program:spatialcf/upright-se2/continuous-materialized-endpoint", + semantic_problem_sha256=source_problem.semantic_problem_sha256, + action_space_profile_sha256=( + compilation.semantic_closure.profile_registration.action_space_profile.action_space_profile_sha256 + ), + steps=( + OperationInvocation( + operator_ref=compilation.operation.authorization.operator_ref, + arguments=_continuous_materialized_program_arguments( + compilation, + translation_xy_m, + selected_lifted_yaw, + ), + ), + ), + before_state_sha256=source_problem.scene_state.scene_state_sha256, + after_scene_state=after_scene, + after_scene_state_sha256=after_scene.scene_state_sha256, + state_delta_manifest=compilation.state_footprint.state_delta_manifest, + grounded_obligation_set_sha256=( + compilation.grounded_obligations.grounded_obligation_set_sha256 + ), + ) + return upright.UprightSE2ContinuousMaterializedEndpoint.seal( + continuous_upright_se2_compilation_sha256=( + compilation.continuous_upright_se2_compilation_sha256 + ), + compilation=compilation, + endpoint_construction_recipe=recipe, + translation_xy_m=translation_xy_m, + selected_lifted_yaw=selected_lifted_yaw, + after_state=after_state, + program=program, + ) + + +def _continuous_materialized_program_arguments( + compilation: upright.UprightSE2ContinuousCompilation, + translation_xy_m: Vec2, + selected_lifted_yaw: upright.ExactDyadic, +) -> tuple[OperationArgument, ...]: + """Use the domain's shared source-bound continuous invocation wire.""" + + return upright._continuous_materialized_program_arguments( + compilation, + translation_xy_m, + selected_lifted_yaw, + ) + + +def _continuous_after_state_template( + recipe: upright.UprightSE2ContinuousEndpointConstructionRecipe, + translation_xy_m: Vec2, + selected_lifted_yaw: upright.ExactDyadic, +) -> upright.UprightSE2AfterStateTemplate: + """Derive a continuous selected pose from a compiler-bound recipe only.""" + + subject_after, yaw_after, after_pose, reference_pivot = ( + upright._continuous_materialized_expected_pose( + recipe, + translation_xy_m, + selected_lifted_yaw, + ) + ) + subject_pose = recipe.subject_before_pose + return upright.UprightSE2AfterStateTemplate.seal( + subject_id=recipe.subject_id, + subject_before_pose=subject_pose, + evaluation_scene=recipe.evaluation_scene, + subject_pivot_xy_m=subject_after, + subject_pivot_z_m=after_pose.translation.z, + subject_pose=after_pose, + subject_yaw_turns=yaw_after, + reference_pivot_xy_m=reference_pivot, + collision_facts=_derived_after_facts( + recipe.evaluation_scene, recipe.subject_id, after_pose, "COLLISION" + ), + support_facts=_derived_after_facts( + recipe.evaluation_scene, recipe.subject_id, after_pose, "SUPPORT" + ), + relation_facts=_derived_after_facts( + recipe.evaluation_scene, recipe.subject_id, after_pose, "RELATION" + ), + visibility_facts=_derived_after_facts( + recipe.evaluation_scene, recipe.subject_id, after_pose, "VISIBILITY" + ), + ) + + +def _materialized_program( + compilation: upright.UprightSE2Compilation, + after_state: upright.UprightSE2AfterStateTemplate, + translation_xy_m: Vec2, +) -> EditProgram: + """Build the retained generic program from the sealed compiler roots only.""" + + source_problem = compilation.source_solve_request.semantic_problem + before_state = source_problem.scene_state + complete_after_state = upright._materialized_expected_after_scene_state( + compilation, after_state + ) + return EditProgram.seal( + program_id="program:spatialcf/upright-se2/materialized-endpoint", + semantic_problem_sha256=source_problem.semantic_problem_sha256, + action_space_profile_sha256=( + compilation.semantic_closure.profile_registration.action_space_profile.action_space_profile_sha256 + ), + steps=( + OperationInvocation( + operator_ref=compilation.operation.authorization.operator_ref, + arguments=upright._materialized_program_arguments( + compilation, translation_xy_m + ), + ), + ), + before_state_sha256=before_state.scene_state_sha256, + after_scene_state=complete_after_state, + after_scene_state_sha256=complete_after_state.scene_state_sha256, + state_delta_manifest=compilation.state_footprint.state_delta_manifest, + grounded_obligation_set_sha256=( + compilation.grounded_obligations.grounded_obligation_set_sha256 + ), + ) + + +def _after_state_template( + recipe: upright.UprightSE2EndpointConstructionRecipe, + translation_xy_m: Vec2, +) -> upright.UprightSE2AfterStateTemplate: + """Derive an endpoint template only from a sealed recipe and input point.""" + + subject_pose = recipe.subject_before_pose + reference_pose = recipe.reference_before_pose + subject = Vec2(x=subject_pose.translation.x, y=subject_pose.translation.y) + reference = Vec2(x=reference_pose.translation.x, y=reference_pose.translation.y) + pivot = ( + subject + if recipe.pivot_binding.pivot_mode is upright.PivotMode.OWN + else reference + ) + subject_after = _yaw_then_translate( + subject, + pivot, + translation_xy_m, + recipe.quarter_turns_ccw, + ) + yaw_after = _canonical_yaw_after(recipe.subject_yaw_turns, recipe.quarter_turns_ccw) + after_rotation = _apply_cardinal_quaternion( + subject_pose.rotation, + recipe.quarter_turns_ccw, + primary_expected_yaw=yaw_after, + ) + after_pose = RigidTransformV2( + translation=Vec3( + x=subject_after.x, + y=subject_after.y, + z=_canonical_zero(subject_pose.translation.z), + ), + rotation=after_rotation, + ) + return upright.UprightSE2AfterStateTemplate.seal( + subject_id=recipe.subject_id, + subject_before_pose=subject_pose, + evaluation_scene=recipe.evaluation_scene, + subject_pivot_xy_m=subject_after, + subject_pivot_z_m=_canonical_zero(subject_pose.translation.z), + subject_pose=after_pose, + subject_yaw_turns=yaw_after, + reference_pivot_xy_m=reference, + collision_facts=_derived_after_facts( + recipe.evaluation_scene, + recipe.subject_id, + after_pose, + "COLLISION", + ), + support_facts=_derived_after_facts( + recipe.evaluation_scene, + recipe.subject_id, + after_pose, + "SUPPORT", + ), + relation_facts=_derived_after_facts( + recipe.evaluation_scene, + recipe.subject_id, + after_pose, + "RELATION", + ), + visibility_facts=_derived_after_facts( + recipe.evaluation_scene, + recipe.subject_id, + after_pose, + "VISIBILITY", + ), + ) + + +def _canonical_yaw_after( + yaw_before: upright.CanonicalSO2Angle, + q: int, +) -> upright.CanonicalSO2Angle: + turns = Fraction.from_float(yaw_before.turns) + _CARDINAL_TURN_FRACTIONS[q] + while turns < Fraction(-1, 2): + turns += 1 + while turns >= Fraction(1, 2): + turns -= 1 + return upright.CanonicalSO2Angle(turns=_canonical_zero(float(turns))) + + +def _apply_cardinal_quaternion( + rotation: Quaternion, + q: int, + *, + primary_expected_yaw: upright.CanonicalSO2Angle, +) -> Quaternion: + return upright._bound_cardinal_quaternion( + rotation, + q, + primary_expected_yaw=primary_expected_yaw, + ) + + +def _derived_after_facts( + scene: CanonicalScene, + subject_id: str, + after_pose: RigidTransformV2, + fact_kind: str, +) -> tuple[upright.UprightSE2DerivedAfterFact, ...]: + if fact_kind == "COLLISION": + sources = _known_exact_source_values(scene.collision_bodies, "collision bodies") + identifier = "body_id" + elif fact_kind == "SUPPORT": + subject = next( + object_ + for object_ in _known_exact_source_values(scene.objects, "objects") + if object_.object_id == subject_id + ) + support_id = subject.support_assignment.surface_id + sources = tuple( + surface + for surface in _known_exact_source_values( + scene.support_surfaces, + "support surfaces", + ) + if surface.surface_id == support_id + ) + identifier = "surface_id" + elif fact_kind == "RELATION": + sources = tuple( + geometry + for geometry in _known_exact_source_values( + scene.geometry_instances, + "geometry instances", + ) + if geometry.role.value == "RELATION" + ) + identifier = "geometry_id" + elif fact_kind == "VISIBILITY": + sources = _known_exact_source_values( + scene.baseline_observations, + "baseline observations", + ) + identifier = "observation_id" + else: + raise ValueError("derived fact kind is not supported") + return _sorted_bytes( + *( + upright.UprightSE2DerivedAfterFact( + fact_kind=fact_kind, + source_fact_id=getattr(source, identifier), + source_fact_sha256=canonical_sha256( + source, + domain=upright.UPRIGHT_SE2_DERIVED_SOURCE_HASH_DOMAIN, + ), + source_fact=source, + after_subject_pose=after_pose, + ) + for source in sources + ) + ) + + +def _yaw_then_translate( + point: Vec2, + pivot: Vec2, + translation: Vec2, + q: int, +) -> Vec2: + rotated_x, rotated_y = rotate_cardinal_xy(point.x - pivot.x, point.y - pivot.y, q) + return Vec2( + x=_canonical_zero(pivot.x + rotated_x + translation.x), + y=_canonical_zero(pivot.y + rotated_y + translation.y), + ) + + +def _state_footprint( + solve_request: CounterfactualSolveRequest, + subject_id: str, +) -> upright.UprightSE2StateFootprint: + leaves = ( + solve_request.semantic_problem.scene_state.canonical_state_leaf_index.leaves + ) + primary = _primary_write_set(subject_id) + derived = _derived_write_set(subject_id) + primary_bytes = {canonical_json_bytes(leaf) for leaf in primary} + derived_bytes = {canonical_json_bytes(leaf) for leaf in derived} + frozen = tuple( + leaf + for leaf in leaves + if canonical_json_bytes(leaf) not in primary_bytes | derived_bytes + ) + if len(primary) + len(derived) + len(frozen) != len(leaves): + raise ValueError("complete state leaf partition is not disjoint") + leaf_index_sha256 = solve_request.semantic_problem.scene_state.canonical_state_leaf_index.state_leaf_index_sha256 + manifest = StateDeltaManifest.seal( + authorized_primary_writes=primary, + recomputed_derived_writes=derived, + unchanged_leaves_digest=canonical_sha256( + frozen, + domain=_UNCHANGED_LEAVES_HASH_DOMAIN, + ), + complete_before_leaf_index_sha256=leaf_index_sha256, + complete_after_leaf_index_sha256=leaf_index_sha256, + ) + return upright.UprightSE2StateFootprint.seal( + state_delta_manifest=manifest, + frozen_leaf_refs=frozen, + ) + + +def _primary_write_set(subject_id: str) -> tuple[StateVariableRef, ...]: + return _sorted_bytes(*(_state_leaf(subject_id, role) for role in _PRIMARY_ROLES)) + + +def _derived_write_set(subject_id: str) -> tuple[StateVariableRef, ...]: + return _sorted_bytes(*(_state_leaf(subject_id, role) for role in _DERIVED_ROLES)) + + +def _compiled_cell( + operation: upright.UprightSE2CardinalOperation, +) -> upright.UprightSE2CompiledCell: + yaw = _CARDINAL_TURN_FRACTIONS[operation.quarter_turns_ccw] + return upright.UprightSE2CompiledCell.seal( + cell_id=( + f"cell:spatialcf/upright-se2/cardinal/{operation.authorization_sha256}" + ), + authorization_sha256=operation.authorization_sha256, + x_lower=operation.translation_domain.x_lower, + x_upper=operation.translation_domain.x_upper, + y_lower=operation.translation_domain.y_lower, + y_upper=operation.translation_domain.y_upper, + yaw_interval=upright.LiftedYawInterval( + lower=_dyadic_from_fraction(yaw), + upper=_dyadic_from_fraction(yaw), + seam_ownership="NONE", + ), + ) + + +def _dyadic_from_float(value: float) -> upright.ExactDyadic: + return _dyadic_from_fraction(Fraction.from_float(value)) + + +def _dyadic_from_fraction(value: Fraction) -> upright.ExactDyadic: + return upright.ExactDyadic( + numerator=value.numerator, + denominator=value.denominator, + ) + + +def _grounded_obligations( + solve_request: CounterfactualSolveRequest, +) -> GroundedObligationSet: + problem = solve_request.semantic_problem + source_definition_refs = tuple( + definition.definition_ref + for definition in problem.definition_bundle.definitions + ) + return GroundedObligationSet.seal( + before_preconditions=tuple( + GroundedObligation( + context=context, + source_definition_refs=source_definition_refs, + ) + for context in problem.before_preconditions + ), + after_goals=( + GroundedObligation( + context=problem.after_goal, + source_definition_refs=source_definition_refs, + ), + ), + preservation_invariants=tuple( + GroundedObligation( + context=context, + source_definition_refs=source_definition_refs, + ) + for context in problem.preservation_invariants + ), + observation_obligations=tuple( + GroundedObligation( + context=context, + source_definition_refs=source_definition_refs, + ) + for context in problem.explicit_observation_obligations + ), + grounding_entity_sets=(), + ) + + +def _continuous_capability_mismatch( + solve_request: CounterfactualSolveRequest, +) -> TypedCompilationOutcome: + descriptor = solve_request.backend_descriptor_bundle.backend_descriptors[0] + mismatch = CapabilityMismatch( + backend_ref=descriptor.backend_ref, + backend_descriptor_sha256=descriptor.backend_descriptor_sha256, + missing_capability_refs=upright.UPRIGHT_SE2_CONTINUOUS_CAPABILITY_REFS, + reason_claim_definition_ref=_CONTINUOUS_UNAVAILABLE_REF, + ) + selection = BackendSelectionRecord.seal( + semantic_problem_sha256=solve_request.semantic_problem_sha256, + solve_request_sha256=solve_request.solve_request_sha256, + implementation_registry_snapshot_sha256=( + solve_request.implementation_registry_snapshot.implementation_registry_snapshot_sha256 + ), + backend_descriptor_bundle_sha256=( + solve_request.backend_descriptor_bundle.backend_descriptor_bundle_sha256 + ), + backend_routing_policy_sha256=( + solve_request.backend_routing_policy.backend_routing_policy_sha256 + ), + ordered_candidate_backend_refs=(descriptor.backend_ref,), + capability_rows=(mismatch,), + selection_disposition="NO_SELECTION", + selection_disposition_claim_ref=_CONTINUOUS_UNAVAILABLE_REF, + deterministic_selection_reason_ref=_CONTINUOUS_UNAVAILABLE_REF, + ) + resource_usage = _zero_resource_usage() + return TypedCompilationOutcome.seal( + semantic_problem_sha256=solve_request.semantic_problem_sha256, + solve_request_sha256=solve_request.solve_request_sha256, + backend_selection_record_sha256=selection.backend_selection_record_sha256, + selected_backend_ref=descriptor.backend_ref, + selected_backend_descriptor_sha256=descriptor.backend_descriptor_sha256, + compilation_reason_claim_definition_ref=_CONTINUOUS_UNAVAILABLE_REF, + partial_artifact_refs=(), + resource_usage=resource_usage, + ) + + +def _zero_resource_usage() -> ResourceUsage: + return ResourceUsage( + accounting_claim_definition_ref=( + "definition:spatialcf/upright-se2/resource-accounting/1.0" + ), + entries=( + _ResourceUsageEntry( + resource_definition_ref="definition:spatialcf/upright-se2/resource/1.0", + used=0.0, + ), + ), + exhausted=False, + ) + + +def _source_requested_gap( + source_compilation: PlanarTranslateCompilation, +) -> upright.UprightSE2ExactRational: + """Lift the retained source binary64 gap exactly into the M3 request.""" + + source_gap = source_compilation.source_artifacts.config.target_optimality_gap + numerator, denominator = source_gap.as_integer_ratio() + return upright.UprightSE2ExactRational(numerator=numerator, denominator=denominator) + + +def _solve_policy_bundle( + registration: upright.UprightSE2ProfileRegistration, + *, + requested_gap: upright.UprightSE2ExactRational, +) -> DefinitionBundle: + return upright.build_upright_se2_solve_policy_definition_bundle( + registration=registration, + requested_gap=requested_gap, + objective_bound_policy_ref=_solver_config().objective_bound_policy_ref, + ) + + +def _closure_definition_bundle( + *, + definition_ref: str, + payload_schema_ref: str, + registration: upright.UprightSE2ProfileRegistration, +) -> DefinitionBundle: + return DefinitionBundle.seal( + definitions=( + CanonicalDefinitionEnvelope.seal( + definition_ref=definition_ref, + definition_kind_ref=_DEFINITION_KIND_REF, + payload_schema_ref=payload_schema_ref, + payload=TypedValue( + value_schema_ref=payload_schema_ref, + payload=RecordValue( + fields=( + NamedTypedValue( + name="profile_registration_sha256", + value=TypedValue( + value_schema_ref=_DIGEST_SCHEMA_REF, + payload=DigestValue( + value=registration.profile_registration_sha256 + ), + ), + ), + ) + ), + ), + ), + ) + ) + + +def _implementation_registry( + registration: upright.UprightSE2ProfileRegistration, + *, + continuous: bool = False, +) -> ImplementationRegistrySnapshot: + _ = registration + semantic_definition_bindings = tuple( + ImplementationOwnerBinding( + definition_or_capability_ref=definition_ref, + implementation_owner_ref=upright.UPRIGHT_SE2_COMPILER_OWNER_REF, + ) + for definition_ref in ( + *upright.UPRIGHT_SE2_PREDICATE_DEFINITION_REFS, + upright.UPRIGHT_SE2_OBJECTIVE_DEFINITION_REF, + upright.UPRIGHT_SE2_SEMANTIC_CLOSURE_DEFINITION_REF, + ) + ) + return ImplementationRegistrySnapshot.seal( + definition_and_capability_owner_bindings=_sorted_bytes( + *semantic_definition_bindings, + ImplementationOwnerBinding( + definition_or_capability_ref=upright.UPRIGHT_SE2_PROFILE_CAPABILITY_REF, + implementation_owner_ref=upright.UPRIGHT_SE2_COMPILER_OWNER_REF, + ), + ImplementationOwnerBinding( + definition_or_capability_ref=( + upright.UPRIGHT_SE2_CARDINAL_COMPILER_CAPABILITY_REF + ), + implementation_owner_ref=upright.UPRIGHT_SE2_COMPILER_OWNER_REF, + ), + ImplementationOwnerBinding( + definition_or_capability_ref=( + upright.UPRIGHT_SE2_CARDINAL_BACKEND_CAPABILITY_REF + ), + implementation_owner_ref=upright.UPRIGHT_SE2_BACKEND_OWNER_REF, + ), + ImplementationOwnerBinding( + definition_or_capability_ref=( + upright.UPRIGHT_SE2_CARDINAL_CHECKER_CAPABILITY_REF + ), + implementation_owner_ref=upright.UPRIGHT_SE2_CHECKER_OWNER_REF, + ), + *( + ( + ImplementationOwnerBinding( + definition_or_capability_ref=( + upright.UPRIGHT_SE2_CONTINUOUS_COMPILER_CAPABILITY_REF + ), + implementation_owner_ref=upright.UPRIGHT_SE2_COMPILER_OWNER_REF, + ), + ImplementationOwnerBinding( + definition_or_capability_ref=( + upright.UPRIGHT_SE2_CONTINUOUS_BACKEND_CAPABILITY_REF + ), + implementation_owner_ref=upright.UPRIGHT_SE2_BACKEND_OWNER_REF, + ), + ImplementationOwnerBinding( + definition_or_capability_ref=( + upright.UPRIGHT_SE2_CONTINUOUS_CHECKER_CAPABILITY_REF + ), + implementation_owner_ref=upright.UPRIGHT_SE2_CHECKER_OWNER_REF, + ), + ) + if continuous + else () + ), + ImplementationOwnerBinding( + definition_or_capability_ref=( + upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF + ), + implementation_owner_ref=upright.UPRIGHT_SE2_BACKEND_OWNER_REF, + ), + ImplementationOwnerBinding( + definition_or_capability_ref=( + upright.UPRIGHT_SE2_PREDICATE_VERIFIER_CAPABILITY_REF + ), + implementation_owner_ref=upright.UPRIGHT_SE2_CHECKER_OWNER_REF, + ), + ImplementationOwnerBinding( + definition_or_capability_ref=( + upright.UPRIGHT_SE2_OBJECTIVE_EVALUATOR_CAPABILITY_REF + ), + implementation_owner_ref=upright.UPRIGHT_SE2_BACKEND_OWNER_REF, + ), + ImplementationOwnerBinding( + definition_or_capability_ref=( + upright.UPRIGHT_SE2_OBJECTIVE_VERIFIER_CAPABILITY_REF + ), + implementation_owner_ref=upright.UPRIGHT_SE2_CHECKER_OWNER_REF, + ), + ), + implementation_build_hashes=_sorted_bytes( + ( + upright.UPRIGHT_SE2_COMPILER_OWNER_REF, + upright.UPRIGHT_SE2_COMPILER_BUILD_SHA256, + ), + (upright.UPRIGHT_SE2_BACKEND_OWNER_REF, _BACKEND_BUILD_SHA256), + (upright.UPRIGHT_SE2_CHECKER_OWNER_REF, _CHECKER_BUILD_SHA256), + ), + dependency_lock_sha256=_DEPENDENCY_LOCK_SHA256, + ) + + +def _backend_descriptor_bundle( + registration: upright.UprightSE2ProfileRegistration, + *, + continuous: bool = False, +) -> BackendDescriptorBundle: + descriptor = SolverBackendDescriptor.seal( + backend_ref=_CONTINUOUS_BACKEND_REF if continuous else _BACKEND_REF, + implementation_build_sha256=_BACKEND_BUILD_SHA256, + supported_profile_hashes=( + registration.action_space_profile.action_space_profile_sha256, + ), + supported_predicate_capabilities=( + upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF, + ), + supported_operator_capabilities=( + upright.UPRIGHT_SE2_CONTINUOUS_COMPILER_CAPABILITY_REF + if continuous + else upright.UPRIGHT_SE2_CARDINAL_COMPILER_CAPABILITY_REF, + ), + supported_objective_capabilities=( + upright.UPRIGHT_SE2_OBJECTIVE_EVALUATOR_CAPABILITY_REF, + ), + supported_numeric_semantics=( + registration.semantics_profile.numeric_semantics_ref, + ), + emitted_proof_material_definition_refs=( + upright.UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_DEFINITION_REF + if continuous + else upright.UPRIGHT_SE2_PROOF_MATERIAL_DEFINITION_REF, + ), + compatible_checker_capability_refs=_sorted_bytes( + ( + upright.UPRIGHT_SE2_CONTINUOUS_CHECKER_CAPABILITY_REF + if continuous + else upright.UPRIGHT_SE2_CARDINAL_CHECKER_CAPABILITY_REF + ), + upright.UPRIGHT_SE2_PREDICATE_VERIFIER_CAPABILITY_REF, + upright.UPRIGHT_SE2_OBJECTIVE_VERIFIER_CAPABILITY_REF, + ), + resource_definition_refs=("definition:spatialcf/upright-se2/resource/1.0",), + ) + return BackendDescriptorBundle.seal( + backend_descriptors=(descriptor,), + unavailable_optional_backends=(), + ) + + +def _solver_config() -> CounterfactualSolverConfig: + return CounterfactualSolverConfig.seal( + solver_config_ref="definition:spatialcf/upright-se2/solver-config/1.0", + compilation_policy_ref="definition:spatialcf/upright-se2/compile/1.0", + proposal_policy_ref="definition:spatialcf/upright-se2/proposal/1.0", + objective_bound_policy_ref="definition:spatialcf/upright-se2/objective-bound/1.0", + determinism_policy_ref="definition:spatialcf/upright-se2/determinism/1.0", + ) + + +def _proof_policy(*, continuous: bool = False) -> ProofPolicy: + return ProofPolicy.seal( + proof_policy_ref="definition:spatialcf/upright-se2/proof-policy/1.0", + accepted_claim_definition_refs=_sorted_bytes( + upright.UPRIGHT_SE2_EXACT_GLOBAL_CLAIM_DEFINITION_REF, + upright.UPRIGHT_SE2_FINITE_GAP_CLAIM_DEFINITION_REF, + ), + required_checker_capability_refs=( + ( + upright.UPRIGHT_SE2_CONTINUOUS_CHECKER_CAPABILITY_REF + if continuous + else upright.UPRIGHT_SE2_CARDINAL_CHECKER_CAPABILITY_REF + ), + ), + publication_minimum_claim_ref=( + "definition:spatialcf/upright-se2/claim-certified-solution/1.0" + ), + permit_noncertified_terminal_records=True, + ) + + +def _resource_policy() -> ResourcePolicy: + return ResourcePolicy.seal( + resource_policy_ref="definition:spatialcf/upright-se2/resource-policy/1.0", + limits=( + ResourceLimit( + definition_ref="definition:spatialcf/upright-se2/resource/1.0", + finite_limit=1.0, + ), + ), + exhaustion_claim_ref="definition:spatialcf/upright-se2/resource-exhausted/1.0", + shared_ledger_policy_ref="definition:spatialcf/upright-se2/shared-ledger/1.0", + ) + + +def _validate_request_resource_policy(resource_policy: ResourcePolicy) -> None: + """Keep resource ownership fixed while leaving its request cap executable.""" + + registered = _resource_policy() + if ( + resource_policy.resource_policy_ref != registered.resource_policy_ref + or resource_policy.exhaustion_claim_ref != registered.exhaustion_claim_ref + or resource_policy.shared_ledger_policy_ref + != registered.shared_ledger_policy_ref + or tuple(limit.definition_ref for limit in resource_policy.limits) + != tuple(limit.definition_ref for limit in registered.limits) + ): + raise ValueError("resource policy does not match the upright se2 closure") + + +def _backend_routing_policy() -> BackendRoutingPolicy: + return BackendRoutingPolicy.seal( + routing_policy_ref="definition:spatialcf/upright-se2/routing/1.0", + capability_filter_definition_ref="definition:spatialcf/upright-se2/filter/1.0", + deterministic_order_definition_ref="definition:spatialcf/upright-se2/order/1.0", + portfolio_composition_definition_ref="definition:spatialcf/upright-se2/portfolio/1.0", + stop_condition_definition_ref="definition:spatialcf/upright-se2/stop/1.0", + resource_partition_definition_ref="definition:spatialcf/upright-se2/partition/1.0", + ) + + +def _sorted_bytes(*values): + return tuple(sorted(values, key=canonical_json_bytes)) diff --git a/src/spatialcf/core/upright_se2_verification.py b/src/spatialcf/core/upright_se2_verification.py new file mode 100644 index 0000000..3a5666a --- /dev/null +++ b/src/spatialcf/core/upright_se2_verification.py @@ -0,0 +1,2541 @@ +"""Fresh, checker-only replay for registered M3 Upright SE(2) submissions.""" + +from __future__ import annotations + +from fractions import Fraction + +from spatialcf.core._internal.kernels.projected_visibility import ( + evaluate_continuous_yaw_visibility_v4, + evaluate_fixed_cardinal_visibility_v3, +) +from spatialcf.core._internal.kernels.so2 import ( + CardinalKernelKindV3, + ContinuousYawIntervalKindV4, + SO2AtomicBudgetV2, + compile_continuous_yaw_lift_v4, + compile_lifted_turn_sin_cos_bounds_v4, +) +from spatialcf.core._internal.kernels.upright_box import ( + ClosedXYCellV3, + ContinuousYawCellPolicyV4, + compile_continuous_yaw_box_bounds_v4, + evaluate_continuous_yaw_cell_v4, + evaluate_fixed_cardinal_cell_v3, +) +from spatialcf.core.upright_se2_compiler import ( + build_upright_se2_cardinal_evaluation_inputs, + build_upright_se2_continuous_evaluation_inputs, + build_upright_se2_retained_owner_evaluation, + compile_upright_se2, + materialize_upright_se2_continuous_endpoint, + materialize_upright_se2_endpoint, +) +from spatialcf.domain import upright_se2 as upright +from spatialcf.domain.base import Vec2 +from spatialcf.domain.counterfactual import CounterfactualSolveRequest +from spatialcf.domain.definitions import TypedValue +from spatialcf.domain.outcomes import ( + BackendCompleteUnsatEvidence, + BackendProposalSubmission, + BackendSelectionRecord, + BackendSubmission, + BackendUnknownEvidence, + CheckedProofOutcome, + CheckerDisposition, + ProofMaterialEnvelope, + ResourceUsage, +) +from spatialcf.domain.serialization import canonical_json_bytes, canonical_sha256 + +__all__ = ("verify_upright_se2_submission",) + +_CHECKED_FACT_SCHEMA_REF = "schema:spatialcf/upright-se2/checked-replay-fact/1.0" +_CONTINUOUS_BACKEND_REF = "backend:spatialcf/upright-se2/continuous" +_CONTINUOUS_BACKEND_BUILD_SHA256 = "b" * 64 +_MAX_CONTINUOUS_EXACT_DYADIC_REFINEMENT_DEPTH = 3 +_UNKNOWN_NUMERIC_REASON_CLAIM_DEFINITION_REF = ( + "definition:spatialcf/upright-se2/backend-numeric-gap/1.0" +) +_UNKNOWN_UNSUPPORTED_REASON_CLAIM_DEFINITION_REF = ( + "definition:spatialcf/upright-se2/backend-unsupported-semantic-input/1.0" +) +_UNKNOWN_INCOMPLETE_REASON_CLAIM_DEFINITION_REF = ( + "definition:spatialcf/upright-se2/backend-incomplete-cardinal-coverage/1.0" +) + + +def verify_upright_se2_submission( + *, + solve_request: CounterfactualSolveRequest, + selection: BackendSelectionRecord, + compilation: upright.UprightSE2Compilation + | upright.UprightSE2ContinuousCompilation, + submission: BackendSubmission, + checker_policy: upright.UprightSE2CheckerReplayPolicy, +) -> CheckedProofOutcome: + """Replay a cardinal submission from roots and return no terminal record. + + The caller supplies the frozen routing record explicitly: a V2 submission + intentionally carries only its digest and must never cause checker-side + routing reconstruction. Every replay input is rebuilt from the source + request through the compiler bridge; proposal payloads are only compared + after that independent replay. + """ + + _require_exact(solve_request, CounterfactualSolveRequest, "solve request") + _require_exact(selection, BackendSelectionRecord, "selection") + if type(compilation) is upright.UprightSE2ContinuousCompilation: + return _verify_continuous_submission( + solve_request=solve_request, + selection=selection, + compilation=compilation, + submission=submission, + checker_policy=checker_policy, + ) + + _require_exact(compilation, upright.UprightSE2Compilation, "compilation") + _require_submission(submission) + _require_exact( + checker_policy, upright.UprightSE2CheckerReplayPolicy, "checker policy" + ) + if ( + checker_policy.proof_policy_sha256 + != solve_request.proof_policy.proof_policy_sha256 + ): + raise ValueError("checker policy does not match solve request") + solve_policy = upright.decode_upright_se2_solve_policy_definition_payload( + solve_request.solve_policy_definition_bundle + ) + if ( + checker_policy.solve_policy_definition_bundle_sha256 + != solve_request.solve_policy_definition_bundle.definition_bundle_sha256 + or checker_policy.requested_gap != solve_policy.requested_gap + or checker_policy.objective_bound_policy_ref + != solve_policy.objective_bound_policy_ref + or checker_policy.exact_global_claim_definition_ref + != solve_policy.exact_global_claim_definition_ref + or checker_policy.finite_gap_claim_definition_ref + != solve_policy.finite_gap_claim_definition_ref + ): + raise ValueError("checker policy does not replay solve-policy gap and claims") + if ( + checker_policy.checker_capability_ref + != upright.UPRIGHT_SE2_CARDINAL_CHECKER_CAPABILITY_REF + ): + raise ValueError("checker policy does not require the cardinal checker") + if selection.selection_disposition != "SELECTED": + raise ValueError("selection must be selected for a submitted proof") + if ( + selection.semantic_problem_sha256 != solve_request.semantic_problem_sha256 + or selection.solve_request_sha256 != solve_request.solve_request_sha256 + ): + raise ValueError("selection roots do not match solve request") + + replayed = compile_upright_se2(solve_request) + if type(replayed) is not upright.UprightSE2Compilation or canonical_json_bytes( + replayed + ) != canonical_json_bytes(compilation): + raise ValueError("fresh compilation replay does not match submission") + + evidence, envelope, evidence_claim_ref = _submission_evidence(submission) + if ( + evidence.semantic_problem_sha256 != solve_request.semantic_problem_sha256 + or evidence.solve_request_sha256 != solve_request.solve_request_sha256 + or evidence.backend_selection_record_sha256 + != selection.backend_selection_record_sha256 + or envelope.backend_selection_record_sha256 + != selection.backend_selection_record_sha256 + ): + raise ValueError("submission selection roots do not match") + if ( + evidence.proposal_backend_owner_ref == upright.UPRIGHT_SE2_CHECKER_OWNER_REF + or evidence.proposal_backend_capability_ref + == upright.UPRIGHT_SE2_CARDINAL_CHECKER_CAPABILITY_REF + ): + raise ValueError("proposal owner or capability cannot serve as checker") + if ( + type(submission) is BackendProposalSubmission + and evidence_claim_ref != upright.UPRIGHT_SE2_EXACT_GLOBAL_CLAIM_DEFINITION_REF + ): + raise ValueError("proposal claim does not bind registered untrusted evidence") + selected_descriptors = tuple( + descriptor + for descriptor in solve_request.backend_descriptor_bundle.backend_descriptors + if descriptor.backend_ref == selection.selected_backend_ref + ) + if len(selected_descriptors) != 1: + raise ValueError("selected backend descriptor does not close") + if ( + evidence.proposal_backend_ref != selection.selected_backend_ref + or evidence.proposal_backend_owner_ref != upright.UPRIGHT_SE2_BACKEND_OWNER_REF + or evidence.proposal_backend_capability_ref + != upright.UPRIGHT_SE2_CARDINAL_BACKEND_CAPABILITY_REF + or evidence.proposal_backend_build_sha256 + != selected_descriptors[0].implementation_build_sha256 + ): + raise ValueError("proposal backend identity does not bind selected backend") + + proof = _decode_proof(envelope) + if canonical_json_bytes(proof.compilation) != canonical_json_bytes(compilation): + raise ValueError("proof compilation does not match supplied compilation") + _check_cardinal_roster_and_coverage(compilation, proof) + if canonical_json_bytes(proof.total_resource_usage) != canonical_json_bytes( + evidence.resource_usage + ): + raise ValueError("submission resource usage does not match proof ledger") + fresh_feasible_leaf_objectives = _replay_cells(compilation, proof) + fresh_candidate_objectives = _replay_candidates(compilation, proof) + _check_selected_proposal_bounds(submission, proof, fresh_candidate_objectives) + _check_terminal_evidence(submission, proof, compilation) + _check_selected_unknown_reason( + solve_request=solve_request, + submission=submission, + proof=proof, + ) + checked_claim_ref = _submission_checked_claim( + submission, + proof, + checker_policy, + evidence_claim_ref, + fresh_feasible_leaf_objectives, + fresh_candidate_objectives, + ) + return CheckedProofOutcome.seal( + semantic_problem_sha256=solve_request.semantic_problem_sha256, + solve_request_sha256=solve_request.solve_request_sha256, + backend_selection_record_sha256=selection.backend_selection_record_sha256, + proof_material_sha256=envelope.proof_material_sha256, + checker_capability_ref=upright.UPRIGHT_SE2_CARDINAL_CHECKER_CAPABILITY_REF, + checker_build_sha256=upright.UPRIGHT_SE2_CHECKER_BUILD_SHA256, + checker_disposition=( + CheckerDisposition.ACCEPTED + if checked_claim_ref is not None + else CheckerDisposition.LIMITED + ), + checked_claim_definition_ref=( + evidence_claim_ref if checked_claim_ref is None else checked_claim_ref + ), + checked_fact_refs=_checked_fact_refs(proof, compilation), + ) + + +def _verify_continuous_submission( + *, + solve_request: CounterfactualSolveRequest, + selection: BackendSelectionRecord, + compilation: upright.UprightSE2ContinuousCompilation, + submission: BackendSubmission, + checker_policy: upright.UprightSE2CheckerReplayPolicy, +) -> CheckedProofOutcome: + """Freshly rebuild one continuous proof without consulting a proposal owner. + + Continuous proof transport deliberately records the proposal backend as the + producer of its kernel rows. That is not an authority shortcut here: this + function rebuilds the compilation, exact-dyadic tree, resource ledger, + retained-owner rows, concrete endpoints, and terminal strength from the + request roots before comparing those fresh bytes with the untrusted wire. + """ + + _require_exact(compilation, upright.UprightSE2ContinuousCompilation, "compilation") + _require_submission(submission) + _require_exact( + checker_policy, upright.UprightSE2CheckerReplayPolicy, "checker policy" + ) + _check_continuous_checker_policy(solve_request, checker_policy) + if selection.selection_disposition != "SELECTED": + raise ValueError("selection must be selected for a submitted proof") + if ( + selection.semantic_problem_sha256 != solve_request.semantic_problem_sha256 + or selection.solve_request_sha256 != solve_request.solve_request_sha256 + or selection.selected_backend_ref != _CONTINUOUS_BACKEND_REF + ): + raise ValueError("continuous selection roots do not match solve request") + + replayed = compile_upright_se2(solve_request) + if type(replayed) is not upright.UprightSE2ContinuousCompilation or ( + canonical_json_bytes(replayed) != canonical_json_bytes(compilation) + ): + raise ValueError( + "fresh continuous compilation replay does not match submission" + ) + + evidence, envelope, evidence_claim_ref = _submission_evidence(submission) + if ( + evidence.semantic_problem_sha256 != solve_request.semantic_problem_sha256 + or evidence.solve_request_sha256 != solve_request.solve_request_sha256 + or evidence.backend_selection_record_sha256 + != selection.backend_selection_record_sha256 + or envelope.backend_selection_record_sha256 + != selection.backend_selection_record_sha256 + ): + raise ValueError("continuous submission selection roots do not match") + if ( + evidence.proposal_backend_owner_ref == upright.UPRIGHT_SE2_CHECKER_OWNER_REF + or evidence.proposal_backend_capability_ref + == upright.UPRIGHT_SE2_CONTINUOUS_CHECKER_CAPABILITY_REF + ): + raise ValueError( + "continuous proposal owner or capability cannot serve as checker" + ) + if ( + type(submission) is BackendProposalSubmission + and evidence_claim_ref != upright.UPRIGHT_SE2_EXACT_GLOBAL_CLAIM_DEFINITION_REF + ): + raise ValueError("continuous proposal claim does not bind registered evidence") + selected_descriptors = tuple( + descriptor + for descriptor in solve_request.backend_descriptor_bundle.backend_descriptors + if descriptor.backend_ref == selection.selected_backend_ref + ) + if len(selected_descriptors) != 1: + raise ValueError("selected continuous backend descriptor does not close") + if ( + evidence.proposal_backend_ref != _CONTINUOUS_BACKEND_REF + or evidence.proposal_backend_ref != selection.selected_backend_ref + or evidence.proposal_backend_owner_ref != upright.UPRIGHT_SE2_BACKEND_OWNER_REF + or evidence.proposal_backend_capability_ref + != upright.UPRIGHT_SE2_CONTINUOUS_BACKEND_CAPABILITY_REF + or evidence.proposal_backend_build_sha256 != _CONTINUOUS_BACKEND_BUILD_SHA256 + or evidence.proposal_backend_build_sha256 + != selected_descriptors[0].implementation_build_sha256 + ): + raise ValueError("continuous proposal backend identity does not bind selection") + + proof = _decode_continuous_proof(envelope) + if canonical_json_bytes(proof.compilation) != canonical_json_bytes(compilation): + raise ValueError( + "continuous proof compilation does not match supplied compilation" + ) + _check_continuous_roster_and_coverage(compilation, proof) + fresh_proof, fresh_cell_total_intervals = _reconstruct_continuous_proof(compilation) + if canonical_json_bytes(fresh_proof) != canonical_json_bytes(proof): + raise ValueError( + "fresh continuous replay does not match proof tree or owner rows" + ) + if canonical_json_bytes(proof.total_resource_usage) != canonical_json_bytes( + evidence.resource_usage + ): + raise ValueError( + "continuous submission resource usage does not match proof ledger" + ) + _check_continuous_selected_proposal_bounds(submission, proof) + _check_continuous_terminal_evidence(submission, proof, compilation) + _check_selected_unknown_reason( + solve_request=solve_request, + submission=submission, + proof=fresh_proof, + ) + checked_claim_ref = _continuous_submission_checked_claim( + submission=submission, + proof=fresh_proof, + checker_policy=checker_policy, + evidence_claim_ref=evidence_claim_ref, + fresh_cell_total_intervals=fresh_cell_total_intervals, + ) + return CheckedProofOutcome.seal( + semantic_problem_sha256=solve_request.semantic_problem_sha256, + solve_request_sha256=solve_request.solve_request_sha256, + backend_selection_record_sha256=selection.backend_selection_record_sha256, + proof_material_sha256=envelope.proof_material_sha256, + checker_capability_ref=upright.UPRIGHT_SE2_CONTINUOUS_CHECKER_CAPABILITY_REF, + checker_build_sha256=upright.UPRIGHT_SE2_CHECKER_BUILD_SHA256, + checker_disposition=( + CheckerDisposition.ACCEPTED + if checked_claim_ref is not None + else CheckerDisposition.LIMITED + ), + checked_claim_definition_ref=( + evidence_claim_ref if checked_claim_ref is None else checked_claim_ref + ), + checked_fact_refs=_continuous_checked_fact_refs(proof, compilation), + ) + + +def _check_continuous_checker_policy( + solve_request: CounterfactualSolveRequest, + checker_policy: upright.UprightSE2CheckerReplayPolicy, +) -> None: + """Reuse the frozen policy gap root while selecting the distinct V4 checker.""" + + if ( + checker_policy.proof_policy_sha256 + != solve_request.proof_policy.proof_policy_sha256 + ): + raise ValueError("checker policy does not match solve request") + solve_policy = upright.decode_upright_se2_solve_policy_definition_payload( + solve_request.solve_policy_definition_bundle + ) + if ( + checker_policy.solve_policy_definition_bundle_sha256 + != solve_request.solve_policy_definition_bundle.definition_bundle_sha256 + or checker_policy.requested_gap != solve_policy.requested_gap + or checker_policy.objective_bound_policy_ref + != solve_policy.objective_bound_policy_ref + or checker_policy.exact_global_claim_definition_ref + != solve_policy.exact_global_claim_definition_ref + or checker_policy.finite_gap_claim_definition_ref + != solve_policy.finite_gap_claim_definition_ref + ): + raise ValueError("checker policy does not replay continuous solve-policy gap") + if ( + checker_policy.checker_capability_ref + != upright.UPRIGHT_SE2_CARDINAL_CHECKER_CAPABILITY_REF + or solve_request.proof_policy.required_checker_capability_refs + != (upright.UPRIGHT_SE2_CONTINUOUS_CHECKER_CAPABILITY_REF,) + ): + raise ValueError("continuous checker capability does not close") + + +def _decode_continuous_proof( + envelope: ProofMaterialEnvelope, +) -> upright.UprightSE2ContinuousProofMaterial: + if ( + envelope.proof_material_definition_ref + != upright.UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_DEFINITION_REF + or envelope.payload_schema_ref + != upright.UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF + or len(envelope.typed_payload) != 1 + ): + raise ValueError("proof envelope is not the registered continuous payload") + proof = upright.decode_upright_se2_continuous_proof_material( + envelope.typed_payload[0] + ) + if proof.continuous_proof_material_sha256 != canonical_sha256( + proof.model_dump( + mode="python", + exclude={"continuous_proof_material_sha256"}, + round_trip=True, + ), + domain=proof.HASH_DOMAIN, + ): + raise ValueError("continuous proof material digest is not canonical") + return proof + + +def _check_continuous_roster_and_coverage( + compilation: upright.UprightSE2ContinuousCompilation, + proof: upright.UprightSE2ContinuousProofMaterial, +) -> None: + """Bind the sole authorized lift root before any fresh owner invocation.""" + + if len(proof.continuous_tuple_roster) != 1: + raise ValueError("checker requires one request-authorized continuous tuple") + proof_tuple = proof.continuous_tuple_roster[0] + if ( + canonical_json_bytes(proof_tuple.compiled_cells) + != canonical_json_bytes(compilation.compiled_cells) + or proof_tuple.translation_domain + != compilation.endpoint_construction_recipe.translation_domain + or proof_tuple.continuous_yaw_lift != compilation.continuous_yaw_lift + or proof.coverage_artifact.authorization_sha256 + != compilation.operation.authorization_sha256 + ): + raise ValueError("checker continuous roster or coverage root mismatch") + leaves = tuple( + row.compiled_cell + for row in proof.evaluated_cells + if row.leaf_disposition is not None + ) + if canonical_json_bytes(leaves) != canonical_json_bytes( + proof.coverage_artifact.cells + ): + raise ValueError("checker continuous final leaf roster does not match coverage") + + +def _reconstruct_continuous_proof( + compilation: upright.UprightSE2ContinuousCompilation, +) -> tuple[ + upright.UprightSE2ContinuousProofMaterial, + tuple[tuple[Fraction, Fraction], ...], +]: + """Independently replay the bounded V4 tree from the sole compiled root. + + This intentionally has no backend argument, cache, or solver call. The + only computational dependencies are the public compiler bridge and the + retained V4 kernel owners, all of which are invoked again under one fresh + shared atomic budget. + """ + + request = compilation.source_solve_request + root = compilation.compiled_cells[0] + try: + root_inputs = build_upright_se2_continuous_evaluation_inputs(compilation, root) + budget = SO2AtomicBudgetV2(limit=root_inputs.resource_atomic_step_limit) + except (TypeError, ValueError, ArithmeticError): + budget = _fresh_continuous_fallback_budget(request) + rows: list[upright.UprightSE2ProofCellEvaluation] = [] + stages: list[upright.UprightSE2ProofStageDelta] = [] + candidates: list[upright.UprightSE2ContinuousProposalCandidate] = [] + cell_total_intervals: list[tuple[Fraction, Fraction]] = [] + requested_gap = upright.decode_upright_se2_solve_policy_definition_payload( + request.solve_policy_definition_bundle + ).requested_gap.as_fraction + + def incumbent_upper() -> Fraction | None: + if not candidates: + return None + return min( + candidate.point_objective.total_upper.as_fraction + for candidate in candidates + ) + + def add_unresolved( + cell: upright.UprightSE2CompiledCell, + evaluations: tuple[upright.UprightSE2RetainedOwnerEvaluation, ...], + label: str, + ) -> None: + row = _fresh_continuous_unresolved_row( + request=request, + cell=cell, + evaluations=evaluations, + label=label, + ) + rows.append(row) + stages.append(_fresh_continuous_stage(cell, row.owner_evaluations)) + + def visit(cell: upright.UprightSE2CompiledCell, depth: int) -> None: + evaluations, compound, visibility = _fresh_continuous_owner_evaluations( + compilation=compilation, + request=request, + cell=cell, + budget=budget, + ) + classification = _fresh_continuous_classification(compound, visibility) + point_cell = _fresh_continuous_point_cell(cell) + if canonical_json_bytes(point_cell) != canonical_json_bytes( + cell + ) and classification in ( + upright.UprightSE2ProofLeafDisposition.UNRESOLVED, + None, + ): + point_evaluations, point_compound, point_visibility = ( + _fresh_continuous_owner_evaluations( + compilation=compilation, + request=request, + cell=point_cell, + budget=budget, + ) + ) + point_classification = _fresh_continuous_classification( + point_compound, point_visibility + ) + point_row = upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=point_cell, + owner_evaluations=point_evaluations, + leaf_disposition=None, + complete_domain_empty=None, + ) + rows.append(point_row) + stages.append(_fresh_continuous_stage(point_cell, point_evaluations)) + if ( + point_classification + is upright.UprightSE2ProofLeafDisposition.INWARD_FEASIBLE + and point_compound is not None + and getattr(point_compound, "bounds", None) is not None + ): + parent_row = _fresh_continuous_incomplete_parent_row( + request=request, + cell=cell, + evaluations=evaluations, + label="continuous-feasible-incomplete-witness", + ) + rows.append(parent_row) + stages.append( + _fresh_continuous_stage(cell, parent_row.owner_evaluations) + ) + try: + candidates.append( + _fresh_continuous_candidate( + compilation=compilation, + final_inward_cell=parent_row, + point_row=point_row, + point_bounds=point_compound.bounds, + ) + ) + except (TypeError, ValueError, ArithmeticError): + pass + return + if classification is upright.UprightSE2ProofLeafDisposition.UNRESOLVED: + add_unresolved(cell, evaluations, "continuous-owner-nonexact") + return + if compound is None or getattr(compound, "bounds", None) is None: + add_unresolved(cell, evaluations, "continuous-missing-compound-bounds") + return + bounds = compound.bounds + if classification is upright.UprightSE2ProofLeafDisposition.OUTWARD_INFEASIBLE: + row = upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=cell, + owner_evaluations=evaluations, + leaf_disposition=classification, + complete_domain_empty=True, + ) + rows.append(row) + stages.append(_fresh_continuous_stage(cell, evaluations)) + return + if classification is None: + lower, upper = _fresh_continuous_objective_interval(bounds) + if _fresh_continuous_bounds_authorize_prune( + cell_lower=lower, + incumbent_upper=incumbent_upper(), + requested_gap=requested_gap, + ): + row = upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=cell, + owner_evaluations=evaluations, + leaf_disposition=upright.UprightSE2ProofLeafDisposition.PRUNED, + complete_domain_empty=False, + ) + rows.append(row) + stages.append(_fresh_continuous_stage(cell, evaluations)) + cell_total_intervals.append((lower, upper)) + return + children = _fresh_continuous_split_exact_dyadic_cell(cell, depth=depth) + if children and depth < _MAX_CONTINUOUS_EXACT_DYADIC_REFINEMENT_DEPTH: + internal = upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=cell, + owner_evaluations=evaluations, + leaf_disposition=None, + complete_domain_empty=None, + ) + rows.append(internal) + stages.append(_fresh_continuous_stage(cell, evaluations)) + for child in children: + visit(child, depth + 1) + return + add_unresolved(cell, evaluations, "continuous-finite-refinement-miss") + return + + if classification is not upright.UprightSE2ProofLeafDisposition.INWARD_FEASIBLE: + raise ValueError( + "continuous checker encountered an unknown leaf disposition" + ) + if canonical_json_bytes(point_cell) == canonical_json_bytes(cell): + final_row = upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=cell, + owner_evaluations=evaluations, + leaf_disposition=upright.UprightSE2ProofLeafDisposition.INWARD_FEASIBLE, + complete_domain_empty=False, + ) + rows.append(final_row) + stages.append(_fresh_continuous_stage(cell, evaluations)) + try: + candidates.append( + _fresh_continuous_candidate( + compilation=compilation, + final_inward_cell=final_row, + point_row=final_row, + point_bounds=bounds, + ) + ) + cell_total_intervals.append( + _fresh_continuous_objective_interval(bounds) + ) + except (TypeError, ValueError, ArithmeticError, OverflowError): + rows.pop() + stages.pop() + incomplete = _fresh_continuous_incomplete_parent_row( + request=request, + cell=cell, + evaluations=evaluations, + label="continuous-point-materialization-incomplete", + ) + rows.append(incomplete) + stages.append( + _fresh_continuous_stage(cell, incomplete.owner_evaluations) + ) + return + + point_evaluations, point_compound, point_visibility = ( + _fresh_continuous_owner_evaluations( + compilation=compilation, + request=request, + cell=point_cell, + budget=budget, + ) + ) + point_classification = _fresh_continuous_classification( + point_compound, point_visibility + ) + point_row = upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=point_cell, + owner_evaluations=point_evaluations, + leaf_disposition=None, + complete_domain_empty=None, + ) + rows.append(point_row) + stages.append(_fresh_continuous_stage(point_cell, point_evaluations)) + if ( + point_classification + is not upright.UprightSE2ProofLeafDisposition.INWARD_FEASIBLE + or point_compound is None + or getattr(point_compound, "bounds", None) is None + ): + incomplete = _fresh_continuous_incomplete_parent_row( + request=request, + cell=cell, + evaluations=evaluations, + label="continuous-inward-point-replay-incomplete", + ) + rows.append(incomplete) + stages.append(_fresh_continuous_stage(cell, incomplete.owner_evaluations)) + return + final_row = upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=cell, + owner_evaluations=evaluations, + leaf_disposition=upright.UprightSE2ProofLeafDisposition.INWARD_FEASIBLE, + complete_domain_empty=False, + ) + rows.append(final_row) + stages.append(_fresh_continuous_stage(cell, evaluations)) + try: + candidates.append( + _fresh_continuous_candidate( + compilation=compilation, + final_inward_cell=final_row, + point_row=point_row, + point_bounds=point_compound.bounds, + ) + ) + cell_total_intervals.append(_fresh_continuous_objective_interval(bounds)) + except (TypeError, ValueError, ArithmeticError, OverflowError): + rows.pop() + stages.pop() + incomplete = _fresh_continuous_incomplete_parent_row( + request=request, + cell=cell, + evaluations=evaluations, + label="continuous-point-materialization-incomplete", + ) + rows.append(incomplete) + stages.append(_fresh_continuous_stage(cell, incomplete.owner_evaluations)) + + visit(root, 0) + return ( + _fresh_continuous_proof_material( + compilation=compilation, + rows=tuple(rows), + stages=tuple(stages), + proposal_candidates=tuple(candidates), + ), + tuple(cell_total_intervals), + ) + + +def _fresh_continuous_owner_evaluations( + *, + compilation: upright.UprightSE2ContinuousCompilation, + request: CounterfactualSolveRequest, + cell: upright.UprightSE2CompiledCell, + budget: SO2AtomicBudgetV2, +) -> tuple[ + tuple[upright.UprightSE2RetainedOwnerEvaluation, ...], object | None, object | None +]: + """Call every registered V4 owner for one fresh continuous proof cell.""" + + evaluations: list[upright.UprightSE2RetainedOwnerEvaluation] = [] + + def record( + label: str, + outcome: object, + start_used: int, + *, + additional_exact_bounds: tuple[TypedValue, ...] = (), + ) -> object: + evaluations.append( + _fresh_continuous_transport_owner_outcome( + request=request, + cell=cell, + outcome=outcome, + evaluator_capability_ref=( + upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF + ), + label=label, + atomic_steps=budget.used - start_used, + additional_exact_bounds=additional_exact_bounds, + ) + ) + return outcome + + try: + inputs = build_upright_se2_continuous_evaluation_inputs(compilation, cell) + if budget.limit != inputs.resource_atomic_step_limit: + raise ValueError("continuous compiler bridge resource cap drifted") + start = budget.used + lift, yaw_bounds = _fresh_continuous_yaw_owner_inputs(compilation, cell, budget) + record("continuous-yaw-lift", lift, start) + if getattr(lift, "kind", None) is not ContinuousYawIntervalKindV4.EXACT: + return tuple(sorted(evaluations, key=canonical_json_bytes)), None, None + if yaw_bounds is None: + raise ValueError("exact continuous yaw lift lacks sin/cos bounds") + start = budget.used + record("continuous-yaw-sin-cos", yaw_bounds, start) + if getattr(yaw_bounds, "kind", None) is not ContinuousYawIntervalKindV4.EXACT: + return tuple(sorted(evaluations, key=canonical_json_bytes)), None, None + if yaw_bounds.bounds is None: + raise ValueError("exact continuous yaw bounds are absent") + + dynamic_subjects: list[object] = [] + for index, source_box in enumerate(inputs.subject_boxes): + start = budget.used + outcome = compile_continuous_yaw_box_bounds_v4( + source_box, + cell=inputs.cell, + pivot_xy=inputs.subject_pivot_xy, + yaw_bounds=yaw_bounds.bounds, + atomic_budget=budget, + ) + record(f"continuous-subject-box-{index}", outcome, start) + if outcome.kind is not ContinuousYawIntervalKindV4.EXACT: + return tuple(sorted(evaluations, key=canonical_json_bytes)), None, None + if outcome.bounds is None: + raise ValueError("exact continuous subject bounds are absent") + dynamic_subjects.append(outcome.bounds) + + start = budget.used + static_lift, static_yaw_bounds = _fresh_continuous_static_yaw_owner_inputs( + budget + ) + record("continuous-static-yaw-lift", static_lift, start) + if getattr(static_lift, "kind", None) is not ContinuousYawIntervalKindV4.EXACT: + return tuple(sorted(evaluations, key=canonical_json_bytes)), None, None + if static_yaw_bounds is None: + raise ValueError("exact static continuous yaw lift lacks bounds") + start = budget.used + record("continuous-static-yaw-sin-cos", static_yaw_bounds, start) + if ( + getattr(static_yaw_bounds, "kind", None) + is not ContinuousYawIntervalKindV4.EXACT + ): + return tuple(sorted(evaluations, key=canonical_json_bytes)), None, None + if static_yaw_bounds.bounds is None: + raise ValueError("exact static continuous yaw bounds are absent") + static_cell = ClosedXYCellV3(Fraction(), Fraction(), Fraction(), Fraction()) + + def static_box(label: str, source_box: object) -> object | None: + start_used = budget.used + outcome = compile_continuous_yaw_box_bounds_v4( + source_box, + cell=static_cell, + pivot_xy=(Fraction(), Fraction()), + yaw_bounds=static_yaw_bounds.bounds, + atomic_budget=budget, + ) + record(label, outcome, start_used) + if outcome.kind is not ContinuousYawIntervalKindV4.EXACT: + return None + return outcome.bounds + + static_obstacles: list[object] = [] + for index, source_box in enumerate(inputs.obstacle_boxes): + boxed = static_box(f"continuous-obstacle-box-{index}", source_box) + if boxed is None: + return tuple(sorted(evaluations, key=canonical_json_bytes)), None, None + static_obstacles.append(boxed) + static_reference = static_box("continuous-reference-box", inputs.reference_box) + if static_reference is None: + return tuple(sorted(evaluations, key=canonical_json_bytes)), None, None + + visibility_input = inputs.visibility_inputs[0] + start = budget.used + visual_subject_outcome = compile_continuous_yaw_box_bounds_v4( + visibility_input.subject, + cell=inputs.cell, + pivot_xy=inputs.subject_pivot_xy, + yaw_bounds=yaw_bounds.bounds, + atomic_budget=budget, + ) + record("continuous-visibility-subject-box", visual_subject_outcome, start) + if visual_subject_outcome.kind is not ContinuousYawIntervalKindV4.EXACT: + return tuple(sorted(evaluations, key=canonical_json_bytes)), None, None + if visual_subject_outcome.bounds is None: + raise ValueError("exact continuous visibility subject bounds are absent") + visual_subject = visual_subject_outcome.bounds + visual_occluders: list[object] = [] + for index, source_box in enumerate(visibility_input.occluders): + if source_box.box_id == visibility_input.subject.box_id: + visual_occluders.append(visual_subject) + continue + boxed = static_box(f"continuous-visibility-occluder-{index}", source_box) + if boxed is None: + return tuple(sorted(evaluations, key=canonical_json_bytes)), None, None + visual_occluders.append(boxed) + visual_occluders = sorted( + visual_occluders, key=lambda value: canonical_json_bytes(value.box.box_id) + ) + start = budget.used + visibility = evaluate_continuous_yaw_visibility_v4( + context=visibility_input.context, + subject=visual_subject, + moving_subject_id=visibility_input.moving_subject_id, + occluders=tuple(visual_occluders), + required_occluder_ids=visibility_input.required_occluder_ids, + policy=visibility_input.policy, + atomic_budget=budget, + ) + record("continuous-visibility", visibility, start) + if visibility.kind is not ContinuousYawIntervalKindV4.EXACT: + return ( + tuple(sorted(evaluations, key=canonical_json_bytes)), + None, + visibility, + ) + + start = budget.used + compound = evaluate_continuous_yaw_cell_v4( + cell=inputs.cell, + yaw_bounds=yaw_bounds.bounds, + subject_boxes=tuple(dynamic_subjects), + obstacle_boxes=tuple(static_obstacles), + support_surface=inputs.support_surface, + relation=inputs.relation, + reference_box=static_reference, + near_far_threshold=inputs.near_far_threshold, + policy=ContinuousYawCellPolicyV4(cardinal_policy=inputs.cell_policy), + visibility=visibility, + atomic_budget=budget, + subject_pivot_xy=inputs.subject_pivot_xy, + objective_subject_pivot_xy=inputs.objective_subject_pivot_xy, + ) + point_objective_bound: tuple[TypedValue, ...] = () + if ( + compound.kind is ContinuousYawIntervalKindV4.EXACT + and cell.x_lower == cell.x_upper + and cell.y_lower == cell.y_upper + and cell.yaw_interval.lower == cell.yaw_interval.upper + ): + if compound.bounds is None: + raise ValueError("exact continuous point cell lacks compound bounds") + point_objective_bound = ( + upright._retained_point_objective_value( + _fresh_continuous_objective(compound.bounds) + ), + ) + record( + "continuous-compound-cell", + compound, + start, + additional_exact_bounds=point_objective_bound, + ) + return ( + tuple(sorted(evaluations, key=canonical_json_bytes)), + compound, + visibility, + ) + except (TypeError, ValueError, ArithmeticError, OverflowError) as error: + evaluations.append( + _fresh_continuous_transport_owner_outcome( + request=request, + cell=cell, + outcome=None, + evaluator_capability_ref=( + upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF + ), + label="continuous-bridge-or-owner-incomplete", + forced_kind=_fresh_continuous_forced_kind(error), + atomic_steps=0, + ) + ) + return tuple(sorted(evaluations, key=canonical_json_bytes)), None, None + + +def _fresh_continuous_transport_owner_outcome( + *, + request: CounterfactualSolveRequest, + cell: upright.UprightSE2CompiledCell, + outcome: object | None, + evaluator_capability_ref: str, + label: str, + atomic_steps: int, + forced_kind: upright.UprightSE2RetainedOwnerOutcomeKind | None = None, + additional_exact_bounds: tuple[TypedValue, ...] = (), +) -> upright.UprightSE2RetainedOwnerEvaluation: + """Serialize fresh V4 owner output through the compiler-owned transport seam.""" + + if forced_kind is None: + if outcome is None: + raise TypeError("continuous retained owner outcome is required") + kernel_kind = getattr(outcome, "kind", None) + if type(kernel_kind) is not ContinuousYawIntervalKindV4: + raise TypeError("retained owner returned an unknown continuous outcome") + proof_kind = _fresh_continuous_outcome_kind(kernel_kind) + raw_rows = tuple(getattr(outcome, "proof_rows", ())) + raw_findings = tuple(getattr(outcome, "finding_codes", ())) + bounds = getattr(outcome, "bounds", None) + if ( + proof_kind is upright.UprightSE2RetainedOwnerOutcomeKind.EXACT + and bounds is None + ): + raise ValueError("exact continuous retained owner outcome requires bounds") + else: + proof_kind = forced_kind + raw_rows = (f"proof:spatialcf/upright-se2/{label}/incomplete",) + raw_findings = (forced_kind.value,) + bounds = None + if type(atomic_steps) is not int or atomic_steps < 0: + raise TypeError("continuous retained owner atomic usage must be exact") + return build_upright_se2_retained_owner_evaluation( + compiled_cell=cell, + owner_ref=upright.UPRIGHT_SE2_BACKEND_OWNER_REF, + evaluator_capability_ref=evaluator_capability_ref, + outcome_kind=proof_kind, + raw_proof_rows=raw_rows, + raw_findings=raw_findings, + atomic_steps=atomic_steps, + resource_delta=_resource_delta( + request, + used=float(atomic_steps), + exhausted=( + proof_kind is upright.UprightSE2RetainedOwnerOutcomeKind.RESOURCE_LIMIT + ), + ), + label=label, + exact_bound_value=bounds, + additional_exact_bounds=additional_exact_bounds, + continuous_exact_rationals=True, + ) + + +def _fresh_continuous_outcome_kind( + kind: ContinuousYawIntervalKindV4, +) -> upright.UprightSE2RetainedOwnerOutcomeKind: + return { + ContinuousYawIntervalKindV4.EXACT: upright.UprightSE2RetainedOwnerOutcomeKind.EXACT, + ContinuousYawIntervalKindV4.NUMERIC_GAP: upright.UprightSE2RetainedOwnerOutcomeKind.NUMERIC_GAP, + ContinuousYawIntervalKindV4.RESOURCE_LIMIT: upright.UprightSE2RetainedOwnerOutcomeKind.RESOURCE_LIMIT, + ContinuousYawIntervalKindV4.UNSUPPORTED: upright.UprightSE2RetainedOwnerOutcomeKind.UNSUPPORTED, + }[kind] + + +def _fresh_continuous_yaw_owner_inputs( + compilation: upright.UprightSE2ContinuousCompilation, + cell: upright.UprightSE2CompiledCell, + budget: SO2AtomicBudgetV2, +) -> tuple[object, object | None]: + """Derive the public V4 yaw owner inputs from the root/lift, never proof rows.""" + + root = compilation.compiled_cells[0] + if canonical_json_bytes(cell.yaw_interval) == canonical_json_bytes( + root.yaw_interval + ): + yaw_domain = compilation.operation.yaw_domain + else: + lower = cell.yaw_interval.lower.as_fraction + upper = cell.yaw_interval.upper.as_fraction + sweep = upper - lower + if not Fraction() <= sweep < Fraction(1): + raise ArithmeticError("numeric gap: lifted child sweep is outside ARC") + yaw_domain = upright.ContinuousYawArc( + start_angle=upright.CanonicalSO2Angle( + turns=_fresh_continuous_exact_float( + _fresh_canonical_continuous_turn(lower), + label="lifted child start", + ) + ), + ccw_sweep_turns=_fresh_continuous_exact_float( + sweep, label="lifted child sweep" + ), + ) + lift = compile_continuous_yaw_lift_v4(yaw_domain, atomic_budget=budget) + if lift.kind is not ContinuousYawIntervalKindV4.EXACT: + return lift, None + if lift.bounds is None: + raise ValueError("exact continuous lift has no bounds") + return lift, compile_lifted_turn_sin_cos_bounds_v4( + lift.bounds, + atomic_budget=budget, + ) + + +def _fresh_continuous_static_yaw_owner_inputs( + budget: SO2AtomicBudgetV2, +) -> tuple[object, object | None]: + lift = compile_continuous_yaw_lift_v4( + upright.ContinuousYawArc( + start_angle=upright.CanonicalSO2Angle(turns=0.0), + ccw_sweep_turns=0.0, + ), + atomic_budget=budget, + ) + if lift.kind is not ContinuousYawIntervalKindV4.EXACT: + return lift, None + if lift.bounds is None: + raise ValueError("exact static continuous lift has no bounds") + return lift, compile_lifted_turn_sin_cos_bounds_v4( + lift.bounds, + atomic_budget=budget, + ) + + +def _fresh_canonical_continuous_turn(value: Fraction) -> Fraction: + while value < Fraction(-1, 2): + value += 1 + while value >= Fraction(1, 2): + value -= 1 + return value + + +def _fresh_continuous_exact_float(value: Fraction, *, label: str) -> float: + result = float(value) + if Fraction.from_float(result) != value: + raise ArithmeticError(f"numeric gap: {label} is not an exact binary64 dyadic") + return 0.0 if result == 0.0 else result + + +def _fresh_continuous_classification( + cell_outcome: object | None, + visibility_outcome: object | None, +) -> upright.UprightSE2ProofLeafDisposition | None: + if ( + cell_outcome is None + or visibility_outcome is None + or getattr(cell_outcome, "kind", None) is not ContinuousYawIntervalKindV4.EXACT + or getattr(visibility_outcome, "kind", None) + is not ContinuousYawIntervalKindV4.EXACT + ): + return upright.UprightSE2ProofLeafDisposition.UNRESOLVED + bounds = getattr(cell_outcome, "bounds", None) + visibility = getattr(visibility_outcome, "bounds", None) + if bounds is None or visibility is None: + return upright.UprightSE2ProofLeafDisposition.UNRESOLVED + if ( + bounds.outer_hard_constraint_failure + or bounds.relation_outer_failure + or visibility.classification == "OUTWARD" + ): + return upright.UprightSE2ProofLeafDisposition.OUTWARD_INFEASIBLE + if ( + bounds.inner_hard_constraint_proven + and bounds.relation_inner_success + and visibility.classification == "INWARD" + ): + return upright.UprightSE2ProofLeafDisposition.INWARD_FEASIBLE + return None + + +def _fresh_continuous_objective( + bounds: object, +) -> upright.UprightSE2ProposalPointObjective: + """Transport the retained five-term V4 objective interval unchanged.""" + + semantic_terms = bounds.semantic_objective_terms + weighted_terms = bounds.weighted_objective_terms + terms = tuple( + upright.UprightSE2ProposalPointTerm( + term_id=term_id, + lower=upright.UprightSE2ExactRational( + numerator=lower.numerator, + denominator=lower.denominator, + ), + upper=upright.UprightSE2ExactRational( + numerator=upper.numerator, + denominator=upper.denominator, + ), + ) + for term_id, lower, upper in semantic_terms + ) + total_lower = sum( + (lower for _term_id, lower, _upper in weighted_terms), start=Fraction(0) + ) + total_upper = sum( + (upper for _term_id, _lower, upper in weighted_terms), start=Fraction(0) + ) + return upright.UprightSE2ProposalPointObjective( + terms=terms, + total_lower=upright.UprightSE2ExactRational( + numerator=total_lower.numerator, + denominator=total_lower.denominator, + ), + total_upper=upright.UprightSE2ExactRational( + numerator=total_upper.numerator, + denominator=total_upper.denominator, + ), + ) + + +def _fresh_continuous_objective_interval(bounds: object) -> tuple[Fraction, Fraction]: + weighted_terms = bounds.weighted_objective_terms + return ( + sum((lower for _term, lower, _upper in weighted_terms), start=Fraction(0)), + sum((upper for _term, _lower, upper in weighted_terms), start=Fraction(0)), + ) + + +def _fresh_continuous_point_cell( + cell: upright.UprightSE2CompiledCell, +) -> upright.UprightSE2CompiledCell: + """Choose the fixed lower-owned exact-dyadic interior witness point.""" + + x = ( + cell.x_lower + if cell.x_lower == cell.x_upper + else _fresh_continuous_dyadic( + (cell.x_lower.as_fraction + cell.x_upper.as_fraction) / 2 + ) + ) + y = ( + cell.y_lower + if cell.y_lower == cell.y_upper + else _fresh_continuous_dyadic( + (cell.y_lower.as_fraction + cell.y_upper.as_fraction) / 2 + ) + ) + u = ( + cell.yaw_interval.lower + if cell.yaw_interval.lower == cell.yaw_interval.upper + else _fresh_continuous_dyadic( + (cell.yaw_interval.lower.as_fraction + cell.yaw_interval.upper.as_fraction) + / 2 + ) + ) + if ( + x == cell.x_lower == cell.x_upper + and y == cell.y_lower == cell.y_upper + and u == cell.yaw_interval.lower == cell.yaw_interval.upper + ): + return cell + return upright.UprightSE2CompiledCell.seal( + cell_id=f"{cell.cell_id}/proposal-point", + authorization_sha256=cell.authorization_sha256, + x_lower=x, + x_upper=x, + y_lower=y, + y_upper=y, + yaw_interval=upright.LiftedYawInterval( + lower=u, + upper=u, + seam_ownership="NONE", + ), + ) + + +def _fresh_continuous_split_exact_dyadic_cell( + cell: upright.UprightSE2CompiledCell, + *, + depth: int, +) -> tuple[upright.UprightSE2CompiledCell, ...]: + """Reconstruct the frozen cyclic X/Y/U split, IDs, and seam ownership.""" + + if type(depth) is not int or depth < 0: + raise ValueError("continuous split depth must be non-negative") + dimensions = ( + ("x", cell.x_lower.as_fraction, cell.x_upper.as_fraction), + ("y", cell.y_lower.as_fraction, cell.y_upper.as_fraction), + ( + "u", + cell.yaw_interval.lower.as_fraction, + cell.yaw_interval.upper.as_fraction, + ), + ) + selected: str | None = None + for offset in range(len(dimensions)): + axis, lower, upper = dimensions[(depth + offset) % len(dimensions)] + if lower < upper: + selected = axis + break + if selected is None: + return () + + def child( + *, + lower: upright.ExactDyadic, + upper: upright.ExactDyadic, + side: str, + ) -> upright.UprightSE2CompiledCell: + if selected == "x": + return upright.UprightSE2CompiledCell.seal( + cell_id=f"{cell.cell_id}/split-x-{side}", + authorization_sha256=cell.authorization_sha256, + x_lower=lower, + x_upper=upper, + y_lower=cell.y_lower, + y_upper=cell.y_upper, + yaw_interval=cell.yaw_interval, + ) + if selected == "y": + return upright.UprightSE2CompiledCell.seal( + cell_id=f"{cell.cell_id}/split-y-{side}", + authorization_sha256=cell.authorization_sha256, + x_lower=cell.x_lower, + x_upper=cell.x_upper, + y_lower=lower, + y_upper=upper, + yaw_interval=cell.yaw_interval, + ) + seam = "NONE" + if side == "lower" and cell.yaw_interval.seam_ownership == "LOWER_OWNS_SEAM": + seam = "LOWER_OWNS_SEAM" + elif ( + side == "upper" + and cell.yaw_interval.seam_ownership == "UPPER_OWNS_ENDPOINT" + ): + seam = "UPPER_OWNS_ENDPOINT" + return upright.UprightSE2CompiledCell.seal( + cell_id=f"{cell.cell_id}/split-u-{side}", + authorization_sha256=cell.authorization_sha256, + x_lower=cell.x_lower, + x_upper=cell.x_upper, + y_lower=cell.y_lower, + y_upper=cell.y_upper, + yaw_interval=upright.LiftedYawInterval( + lower=lower, + upper=upper, + seam_ownership=seam, + ), + ) + + if selected == "x": + lower, upper = cell.x_lower, cell.x_upper + elif selected == "y": + lower, upper = cell.y_lower, cell.y_upper + else: + lower, upper = cell.yaw_interval.lower, cell.yaw_interval.upper + midpoint = _fresh_continuous_dyadic((lower.as_fraction + upper.as_fraction) / 2) + return ( + child(lower=lower, upper=midpoint, side="lower"), + child(lower=midpoint, upper=upper, side="upper"), + ) + + +def _fresh_continuous_dyadic(value: Fraction) -> upright.ExactDyadic: + if value.denominator & (value.denominator - 1): + raise ValueError("continuous subdivision endpoint is not dyadic") + return upright.ExactDyadic(numerator=value.numerator, denominator=value.denominator) + + +def _fresh_continuous_candidate( + *, + compilation: upright.UprightSE2ContinuousCompilation, + final_inward_cell: upright.UprightSE2ProofCellEvaluation, + point_row: upright.UprightSE2ProofCellEvaluation, + point_bounds: object, +) -> upright.UprightSE2ContinuousProposalCandidate: + """Freshly materialize and bind the concrete endpoint from exact point owners.""" + + point_cell = point_row.compiled_cell + objective = _fresh_continuous_objective(point_bounds) + point = Vec2( + x=float(point_cell.x_lower.as_fraction), + y=float(point_cell.y_lower.as_fraction), + ) + endpoint = materialize_upright_se2_continuous_endpoint( + compilation, + point, + point_cell.yaw_interval.lower, + ) + point_evaluation = upright.UprightSE2ProposalPointEvaluation.seal( + point_cell_evaluation=point_row, + point_objective=objective, + ) + return upright.UprightSE2ContinuousProposalCandidate.seal( + final_inward_cell=final_inward_cell, + selected_translation_xy_m=point, + selected_lifted_yaw=point_cell.yaw_interval.lower, + point_evaluation=point_evaluation, + point_objective=objective, + materialized_endpoint=endpoint, + program=endpoint.program, + ) + + +def _fresh_continuous_unresolved_row( + *, + request: CounterfactualSolveRequest, + cell: upright.UprightSE2CompiledCell, + evaluations: tuple[upright.UprightSE2RetainedOwnerEvaluation, ...], + label: str, +) -> upright.UprightSE2ProofCellEvaluation: + """Represent a nonexact or finite-search boundary as UNKNOWN-only evidence.""" + + owner_evaluations = evaluations + if not any( + evaluation.outcome_kind is not upright.UprightSE2RetainedOwnerOutcomeKind.EXACT + for evaluation in owner_evaluations + ): + owner_evaluations = tuple( + sorted( + ( + *owner_evaluations, + _fresh_continuous_transport_owner_outcome( + request=request, + cell=cell, + outcome=None, + evaluator_capability_ref=( + upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF + ), + label=label, + forced_kind=( + upright.UprightSE2RetainedOwnerOutcomeKind.FINITE_MISS + ), + atomic_steps=0, + ), + ), + key=canonical_json_bytes, + ) + ) + return upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=cell, + owner_evaluations=owner_evaluations, + leaf_disposition=upright.UprightSE2ProofLeafDisposition.UNRESOLVED, + complete_domain_empty=False, + ) + + +def _fresh_continuous_incomplete_parent_row( + *, + request: CounterfactualSolveRequest, + cell: upright.UprightSE2CompiledCell, + evaluations: tuple[upright.UprightSE2RetainedOwnerEvaluation, ...], + label: str, +) -> upright.UprightSE2ProofCellEvaluation: + incomplete = _fresh_continuous_transport_owner_outcome( + request=request, + cell=cell, + outcome=None, + evaluator_capability_ref=upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF, + label=label, + forced_kind=upright.UprightSE2RetainedOwnerOutcomeKind.INCOMPLETE, + atomic_steps=0, + ) + return upright.UprightSE2ProofCellEvaluation.seal( + compiled_cell=cell, + owner_evaluations=tuple( + sorted((*evaluations, incomplete), key=canonical_json_bytes) + ), + leaf_disposition=upright.UprightSE2ProofLeafDisposition.UNRESOLVED, + complete_domain_empty=False, + ) + + +def _fresh_continuous_bounds_authorize_prune( + *, + cell_lower: Fraction, + incumbent_upper: Fraction | None, + requested_gap: Fraction, +) -> bool: + if incumbent_upper is None: + return False + return cell_lower > incumbent_upper or ( + requested_gap > 0 and cell_lower >= incumbent_upper - requested_gap + ) + + +def _fresh_continuous_forced_kind( + error: BaseException, +) -> upright.UprightSE2RetainedOwnerOutcomeKind: + if isinstance(error, ArithmeticError): + return upright.UprightSE2RetainedOwnerOutcomeKind.NUMERIC_GAP + if "unsupported" in str(error).lower(): + return upright.UprightSE2RetainedOwnerOutcomeKind.UNSUPPORTED + return upright.UprightSE2RetainedOwnerOutcomeKind.INCOMPLETE + + +def _fresh_continuous_fallback_budget( + request: CounterfactualSolveRequest, +) -> SO2AtomicBudgetV2: + limits = request.resource_policy.limits + if len(limits) != 1: + raise ValueError("continuous upright se2 requires one resource limit") + finite_limit = limits[0].finite_limit + if type(finite_limit) is not float or finite_limit < 1.0: + raise ValueError("continuous upright se2 resource limit is unusable") + return SO2AtomicBudgetV2(limit=int(finite_limit)) + + +def _fresh_continuous_stage( + cell: upright.UprightSE2CompiledCell, + evaluations: tuple[upright.UprightSE2RetainedOwnerEvaluation, ...], +) -> upright.UprightSE2ProofStageDelta: + """Record one non-reset fresh stage for every replayed proof cell.""" + + ordered = tuple(sorted(evaluations, key=canonical_json_bytes)) + return upright.UprightSE2ProofStageDelta.seal( + stage_ref=( + "stage:spatialcf/upright-se2/continuous-retained-evaluation/" + f"{cell.compiled_cell_sha256}" + ), + owner_evaluations=ordered, + resource_delta=_fresh_continuous_aggregate_usage( + tuple(evaluation.resource_delta for evaluation in ordered) + ), + ) + + +def _fresh_continuous_proof_material( + *, + compilation: upright.UprightSE2ContinuousCompilation, + rows: tuple[upright.UprightSE2ProofCellEvaluation, ...], + stages: tuple[upright.UprightSE2ProofStageDelta, ...], + proposal_candidates: tuple[upright.UprightSE2ContinuousProposalCandidate, ...], +) -> upright.UprightSE2ContinuousProofMaterial: + """Seal only the independently reconstructed proof transport for comparison.""" + + ordered_rows = tuple( + sorted( + rows, key=lambda row: _fresh_continuous_cell_order_key(row.compiled_cell) + ) + ) + leaves = tuple(row for row in ordered_rows if row.leaf_disposition is not None) + unresolved = tuple( + row + for row in leaves + if row.leaf_disposition is upright.UprightSE2ProofLeafDisposition.UNRESOLVED + ) + pruned = tuple( + row + for row in leaves + if row.leaf_disposition is upright.UprightSE2ProofLeafDisposition.PRUNED + ) + proposals = tuple( + row + for row in leaves + if row.leaf_disposition + is upright.UprightSE2ProofLeafDisposition.INWARD_FEASIBLE + ) + ordered_stages = tuple(sorted(stages, key=canonical_json_bytes)) + ordered_candidates = tuple( + sorted(proposal_candidates, key=lambda candidate: candidate.canonical_order_key) + ) + ledger = upright.UprightSE2ProofResourceLedger.seal( + stage_deltas=ordered_stages, + canonical_total_resource_usage=_fresh_continuous_aggregate_usage( + tuple(stage.resource_delta for stage in ordered_stages) + ), + ) + return upright.UprightSE2ContinuousProofMaterial.seal( + solve_request_sha256=compilation.solve_request_sha256, + semantic_closure_sha256=compilation.semantic_closure.semantic_closure_sha256, + continuous_upright_se2_compilation_sha256=( + compilation.continuous_upright_se2_compilation_sha256 + ), + compilation=compilation, + continuous_tuple_roster=( + upright.UprightSE2ContinuousProofTuple.seal( + authorization=compilation.operation.authorization, + reference_id=compilation.endpoint_construction_recipe.reference_id, + translation_domain=compilation.operation.translation_domain, + continuous_yaw_lift=compilation.continuous_yaw_lift, + compiled_cells=compilation.compiled_cells, + ), + ), + coverage_artifact=upright.UprightSE2CoverageArtifact.seal( + authorization_sha256=compilation.operation.authorization_sha256, + cells=tuple(row.compiled_cell for row in leaves), + unresolved_cell_sha256s=tuple( + sorted(row.compiled_cell.compiled_cell_sha256 for row in unresolved) + ), + ), + compiled_cell_sha256s=tuple( + row.compiled_cell.compiled_cell_sha256 for row in leaves + ), + evaluated_cells=ordered_rows, + proposal_order=proposals, + proposal_candidates=ordered_candidates, + incumbent_candidate_sha256=( + None + if not ordered_candidates + else ordered_candidates[0].continuous_proposal_candidate_sha256 + ), + prune_decisions=tuple( + upright.UprightSE2ProofPruneDecision.seal( + cell_evaluation=row, + prune_reason_codes=( + "reason:spatialcf/upright-se2/continuous-objective-bound", + ), + ) + for row in pruned + ), + unresolved_frontier=tuple( + upright.UprightSE2ProofFrontierRow.seal( + cell_evaluation=row, + frontier_reason_codes=( + "reason:spatialcf/upright-se2/continuous-owner-nonexact", + ), + ) + for row in unresolved + ), + resource_ledger=ledger, + ) + + +def _fresh_continuous_cell_order_key( + cell: upright.UprightSE2CompiledCell, +) -> tuple[Fraction, Fraction, bytes]: + return ( + cell.yaw_interval.lower.as_fraction, + cell.yaw_interval.upper.as_fraction, + canonical_json_bytes(cell.cell_id), + ) + + +def _fresh_continuous_aggregate_usage( + usages: tuple[ResourceUsage, ...], +) -> ResourceUsage: + if not usages: + raise ValueError("resource aggregation requires at least one usage") + accounting_refs = {usage.accounting_claim_definition_ref for usage in usages} + if len(accounting_refs) != 1: + raise ValueError("resource aggregation requires one accounting claim") + totals: dict[str, float] = {} + for usage in usages: + for entry in usage.entries: + totals[entry.resource_definition_ref] = ( + totals.get(entry.resource_definition_ref, 0.0) + entry.used + ) + return ResourceUsage.model_validate( + { + "accounting_claim_definition_ref": next(iter(accounting_refs)), + "entries": tuple( + { + "resource_definition_ref": reference, + "used": used, + } + for reference, used in sorted( + totals.items(), key=lambda item: canonical_json_bytes(item[0]) + ) + ), + "exhausted": any(usage.exhausted for usage in usages), + } + ) + + +def _check_continuous_selected_proposal_bounds( + submission: BackendSubmission, + proof: upright.UprightSE2ContinuousProofMaterial, +) -> None: + if type(submission) is not BackendProposalSubmission: + return + if not proof.proposal_candidates: + raise ValueError("continuous proposal submission lacks a fresh candidate") + candidate = proof.proposal_candidates[0] + proposal = submission.proposal + if ( + proposal.program_sha256 != candidate.program.program_sha256 + or proposal.after_scene_state_sha256 + != candidate.program.after_scene_state_sha256 + or proposal.objective_lower_bound + != float(candidate.point_objective.total_lower.as_fraction) + or proposal.objective_upper_bound + != float(candidate.point_objective.total_upper.as_fraction) + ): + raise ValueError( + "outer continuous proposal does not bind fresh selected witness" + ) + + +def _check_continuous_terminal_evidence( + submission: BackendSubmission, + proof: upright.UprightSE2ContinuousProofMaterial, + compilation: upright.UprightSE2ContinuousCompilation, +) -> None: + leaves = tuple( + row for row in proof.evaluated_cells if row.leaf_disposition is not None + ) + if type(submission) is BackendCompleteUnsatEvidence: + if ( + submission.authorized_domain_sha256 + != compilation.endpoint_construction_recipe.translation_domain_sha256 + or submission.complete_domain_coverage_artifact_sha256 + != proof.coverage_artifact.coverage_artifact_sha256 + or not leaves + or any( + row.leaf_disposition + is not upright.UprightSE2ProofLeafDisposition.OUTWARD_INFEASIBLE + or row.complete_domain_empty is not True + for row in leaves + ) + ): + raise ValueError("complete continuous UNSAT lacks full outward coverage") + elif type(submission) is BackendProposalSubmission: + if not proof.proposal_candidates: + raise ValueError("continuous proposal submission lacks a checked candidate") + elif ( + type(submission) is BackendUnknownEvidence + and submission.resource_usage != proof.total_resource_usage + ): + raise ValueError("continuous unknown resource usage does not match proof") + + +def _continuous_submission_checked_claim( + *, + submission: BackendSubmission, + proof: upright.UprightSE2ContinuousProofMaterial, + checker_policy: upright.UprightSE2CheckerReplayPolicy, + evidence_claim_ref: str, + fresh_cell_total_intervals: tuple[tuple[Fraction, Fraction], ...], +) -> str | None: + """Apply fresh exact-equality and directed `U_witness - L_global` criteria.""" + + if type(submission) is BackendCompleteUnsatEvidence: + return evidence_claim_ref + if type(submission) is not BackendProposalSubmission: + return None + if ( + proof.unresolved_frontier + or not proof.proposal_candidates + or not fresh_cell_total_intervals + ): + return None + witness = proof.proposal_candidates[0].point_objective + global_lower = min(lower for lower, _upper in fresh_cell_total_intervals) + candidate_total_intervals = tuple( + ( + candidate.point_objective.total_lower.as_fraction, + candidate.point_objective.total_upper.as_fraction, + ) + for candidate in proof.proposal_candidates + ) + comparator_totals = (*fresh_cell_total_intervals, *candidate_total_intervals) + if ( + comparator_totals + and all(lower == upper for lower, upper in comparator_totals) + and len(set(comparator_totals)) == 1 + ): + return checker_policy.exact_global_claim_definition_ref + if ( + witness.total_upper.as_fraction - global_lower + <= checker_policy.requested_gap.as_fraction + ): + return checker_policy.finite_gap_claim_definition_ref + return None + + +def _continuous_checked_fact_refs( + proof: upright.UprightSE2ContinuousProofMaterial, + compilation: upright.UprightSE2ContinuousCompilation, +) -> tuple[dict[str, str], ...]: + values = ( + proof.continuous_proof_material_sha256, + compilation.continuous_upright_se2_compilation_sha256, + proof.coverage_artifact.coverage_artifact_sha256, + proof.resource_ledger.proof_resource_ledger_sha256, + ) + return tuple( + sorted( + ( + { + "artifact_schema_ref": _CHECKED_FACT_SCHEMA_REF, + "artifact_sha256": canonical_sha256( + value, + domain=( + "spatialcf/counterfactual/upright-se2/" + "continuous-checked-fact/1.0" + ), + ), + } + for value in values + ), + key=canonical_json_bytes, + ) + ) + + +def _submission_evidence( + submission: BackendSubmission, +) -> tuple[ + BackendProposalSubmission | BackendCompleteUnsatEvidence | BackendUnknownEvidence, + ProofMaterialEnvelope, + str, +]: + if type(submission) is BackendProposalSubmission: + proposal = submission.proposal + if submission.backend_proposal_sha256 != proposal.backend_proposal_sha256: + raise ValueError("proposal wrapper does not bind proposal") + return proposal, proposal.proof_material, proposal.proposal_claim_definition_ref + if type(submission) is BackendCompleteUnsatEvidence: + if ( + submission.complete_domain_claim_definition_ref + != "definition:spatialcf/upright-se2/claim-proven-unsat/1.0" + ): + raise ValueError( + "complete-domain UNSAT claim does not bind registered evidence" + ) + return ( + submission, + submission.proof_material, + submission.complete_domain_claim_definition_ref, + ) + if type(submission) is BackendUnknownEvidence: + return ( + submission, + submission.proof_material, + submission.reason_claim_definition_ref, + ) + raise TypeError("checker accepts only exact M3 backend submissions") + + +def _require_submission(submission: object) -> None: + """Reject bypassed/tampered discriminated wires before inspecting evidence.""" + + model_type = type(submission) + if model_type not in ( + BackendProposalSubmission, + BackendCompleteUnsatEvidence, + BackendUnknownEvidence, + ): + raise TypeError("checker accepts only exact M3 backend submissions") + checked = model_type.model_validate( + submission.model_dump(mode="python", round_trip=True), strict=True + ) + if canonical_json_bytes(checked) != canonical_json_bytes(submission): + raise ValueError("submission must use canonical bytes") + + +def _decode_proof(envelope: ProofMaterialEnvelope) -> upright.UprightSE2ProofMaterial: + if ( + envelope.proof_material_definition_ref + != upright.UPRIGHT_SE2_PROOF_MATERIAL_DEFINITION_REF + or envelope.payload_schema_ref + != upright.UPRIGHT_SE2_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF + or len(envelope.typed_payload) != 1 + ): + raise ValueError("proof envelope is not the registered cardinal payload") + proof = upright.decode_upright_se2_proof_material(envelope.typed_payload[0]) + if proof.proof_material_sha256 != canonical_sha256( + proof.model_dump( + mode="python", + exclude={"proof_material_sha256"}, + round_trip=True, + ), + domain=proof.HASH_DOMAIN, + ): + raise ValueError("proof material digest is not canonical") + return proof + + +def _cardinal_replay_rows( + proof: upright.UprightSE2ProofMaterial, +) -> tuple[tuple[upright.UprightSE2ProofCellEvaluation, str | None], ...]: + """Retain canonical cell order while replaying associated points first.""" + + rows = {row.compiled_cell.cell_id: row for row in proof.evaluated_cells} + point_parents: dict[str, str] = {} + for parent in proof.evaluated_cells: + cell = parent.compiled_cell + if cell.x_lower == cell.x_upper and cell.y_lower == cell.y_upper: + continue + point = rows.get(f"{cell.cell_id}/proposal-point") + if point is None: + continue + point_cell = point.compiled_cell + if ( + point.leaf_disposition is not None + or point.complete_domain_empty is not None + or point_cell.authorization_sha256 != cell.authorization_sha256 + or point_cell.yaw_interval != cell.yaw_interval + ): + raise ValueError("cardinal point-first row does not bind its parent") + for lower, upper, point_lower, point_upper in ( + (cell.x_lower, cell.x_upper, point_cell.x_lower, point_cell.x_upper), + (cell.y_lower, cell.y_upper, point_cell.y_lower, point_cell.y_upper), + ): + if point_lower != point_upper or not ( + lower == point_lower == upper + if lower == upper + else lower.as_fraction < point_lower.as_fraction < upper.as_fraction + ): + raise ValueError("cardinal point-first row is not strict interior") + point_parents[point_cell.cell_id] = cell.cell_id + + ordered = [] + seen: set[str] = set() + for row in proof.evaluated_cells: + cell_id = row.compiled_cell.cell_id + point_id = f"{cell_id}/proposal-point" + if point_id in point_parents and point_id not in seen: + ordered.append((rows[point_id], cell_id)) + seen.add(point_id) + if cell_id not in seen: + ordered.append((row, point_parents.get(cell_id))) + seen.add(cell_id) + return tuple(ordered) + + +def _fresh_cardinal_incomplete_parent( + request: CounterfactualSolveRequest, + row: upright.UprightSE2ProofCellEvaluation, +) -> tuple[upright.UprightSE2RetainedOwnerEvaluation, ...]: + """Close an unevaluated parent only after its fresh nonexact point.""" + + if ( + row.leaf_disposition is not upright.UprightSE2ProofLeafDisposition.UNRESOLVED + or row.complete_domain_empty is not False + ): + raise ValueError("nonexact cardinal point requires an unresolved parent") + return ( + build_upright_se2_retained_owner_evaluation( + compiled_cell=row.compiled_cell, + owner_ref=upright.UPRIGHT_SE2_BACKEND_OWNER_REF, + evaluator_capability_ref=upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF, + outcome_kind=upright.UprightSE2RetainedOwnerOutcomeKind.INCOMPLETE, + raw_proof_rows=( + "proof:spatialcf/upright-se2/point-first-parent-incomplete/incomplete", + ), + raw_findings=("INCOMPLETE",), + atomic_steps=0, + resource_delta=_resource_delta(request, used=0.0, exhausted=False), + label="point-first-parent-incomplete", + ), + ) + + +def _replay_cells( + compilation: upright.UprightSE2Compilation, + proof: upright.UprightSE2ProofMaterial, +) -> tuple[upright.UprightSE2ProposalPointObjective, ...]: + """Replay point-first owner rows under one fresh shared resource budget.""" + + budget: SO2AtomicBudgetV2 | None = None + replayed_steps = 0 + exhausted = False + incomplete_parents: set[str] = set() + fresh_feasible_leaf_objectives = [] + for row, point_parent_id in _cardinal_replay_rows(proof): + if row.compiled_cell.cell_id in incomplete_parents: + expected_evaluations = _fresh_cardinal_incomplete_parent( + compilation.source_solve_request, row + ) + if canonical_json_bytes(row.owner_evaluations) != canonical_json_bytes( + expected_evaluations + ): + raise ValueError( + "fresh nonexact point does not bind cardinal incomplete parent" + ) + continue + inputs = build_upright_se2_cardinal_evaluation_inputs( + compilation, row.compiled_cell + ) + if budget is None: + budget = SO2AtomicBudgetV2(limit=inputs.resource_atomic_step_limit) + elif budget.limit != inputs.resource_atomic_step_limit: + raise ValueError("fresh cardinal bridge resource cap drifted") + box = evaluate_fixed_cardinal_cell_v3( + cell=inputs.cell, + quarter_turns_ccw=inputs.quarter_turns_ccw, + subject_boxes=inputs.subject_boxes, + obstacle_boxes=inputs.obstacle_boxes, + support_surface=inputs.support_surface, + relation=inputs.relation, + reference_box=inputs.reference_box, + near_far_threshold=inputs.near_far_threshold, + policy=inputs.cell_policy, + atomic_budget=budget, + subject_pivot_xy=inputs.subject_pivot_xy, + objective_subject_pivot_xy=inputs.objective_subject_pivot_xy, + ) + visibility = tuple( + evaluate_fixed_cardinal_visibility_v3( + context=item.context, + cell=item.cell, + subject=item.subject, + moving_subject_id=item.moving_subject_id, + occluders=item.occluders, + required_occluder_ids=item.required_occluder_ids, + policy=item.policy, + atomic_budget=budget, + ) + for item in inputs.visibility_inputs + ) + replayed_steps += sum( + outcome.atomic_steps_used for outcome in (box, *visibility) + ) + exhausted = exhausted or any( + outcome.kind is CardinalKernelKindV3.RESOURCE_LIMIT + for outcome in (box, *visibility) + ) + if point_parent_id is not None and any( + outcome.kind is not CardinalKernelKindV3.EXACT + for outcome in (box, *visibility) + ): + incomplete_parents.add(point_parent_id) + expected_leaf = _classify_leaf(box, visibility) + if row.leaf_disposition is not None and not _fresh_leaf_matches_submission( + submitted=row.leaf_disposition, fresh=expected_leaf + ): + raise ValueError("fresh kernel replay does not match retained leaf") + expected_evaluations = _fresh_owner_evaluations( + solve_request=compilation.source_solve_request, + row=row, + box=box, + visibility=visibility, + include_finite_refinement_miss=( + expected_leaf is None + and row.leaf_disposition + is upright.UprightSE2ProofLeafDisposition.UNRESOLVED + ), + ) + if canonical_json_bytes(row.owner_evaluations) != canonical_json_bytes( + expected_evaluations + ): + raise ValueError( + "fresh kernel replay does not match complete retained owner evaluations" + ) + if ( + row.leaf_disposition + is upright.UprightSE2ProofLeafDisposition.INWARD_FEASIBLE + ): + fresh_feasible_leaf_objectives.append( + _objective_from_box_bounds(box.bounds) + ) + if budget is None or budget.used != replayed_steps: + raise ValueError("fresh cardinal owner usage does not close shared budget") + fresh_usage = _resource_delta( + compilation.source_solve_request, + used=float(replayed_steps), + exhausted=exhausted, + ) + if canonical_json_bytes(fresh_usage) != canonical_json_bytes( + proof.total_resource_usage + ): + raise ValueError("fresh kernel replay resource count does not match ledger") + return tuple(fresh_feasible_leaf_objectives) + + +def _replay_candidates( + compilation: upright.UprightSE2Compilation, + proof: upright.UprightSE2ProofMaterial, +) -> tuple[upright.UprightSE2ProposalPointObjective, ...]: + fresh_objectives = [] + for candidate in proof.proposal_candidates: + endpoint = materialize_upright_se2_endpoint( + compilation, candidate.selected_translation_xy_m + ) + if canonical_json_bytes(endpoint) != canonical_json_bytes( + candidate.materialized_endpoint + ) or canonical_json_bytes(endpoint.program) != canonical_json_bytes( + candidate.program + ): + raise ValueError("fresh endpoint materialization does not match proposal") + if ( + endpoint.program.state_delta_manifest + != compilation.state_footprint.state_delta_manifest + or endpoint.program.grounded_obligation_set_sha256 + != compilation.grounded_obligations.grounded_obligation_set_sha256 + or endpoint.program.after_scene_state_sha256 + != candidate.program.after_scene_state_sha256 + ): + raise ValueError("fresh endpoint does not bind state delta or obligations") + objective = _fresh_point_objective( + compilation, candidate.point_evaluation.point_cell_evaluation.compiled_cell + ) + if canonical_json_bytes(objective) != canonical_json_bytes( + candidate.point_objective + ): + raise ValueError("fresh point objective does not match proposal") + fresh_objectives.append(objective) + return tuple(fresh_objectives) + + +def _fresh_point_objective( + compilation: upright.UprightSE2Compilation, + point_cell: upright.UprightSE2CompiledCell, +) -> upright.UprightSE2ProposalPointObjective: + """Recompute all five raw point intervals through fresh owner calls.""" + + inputs = build_upright_se2_cardinal_evaluation_inputs(compilation, point_cell) + budget = SO2AtomicBudgetV2(limit=inputs.resource_atomic_step_limit) + box = evaluate_fixed_cardinal_cell_v3( + cell=inputs.cell, + quarter_turns_ccw=inputs.quarter_turns_ccw, + subject_boxes=inputs.subject_boxes, + obstacle_boxes=inputs.obstacle_boxes, + support_surface=inputs.support_surface, + relation=inputs.relation, + reference_box=inputs.reference_box, + near_far_threshold=inputs.near_far_threshold, + policy=inputs.cell_policy, + atomic_budget=budget, + subject_pivot_xy=inputs.subject_pivot_xy, + objective_subject_pivot_xy=inputs.objective_subject_pivot_xy, + ) + visibility = tuple( + evaluate_fixed_cardinal_visibility_v3( + context=item.context, + cell=item.cell, + subject=item.subject, + moving_subject_id=item.moving_subject_id, + occluders=item.occluders, + required_occluder_ids=item.required_occluder_ids, + policy=item.policy, + atomic_budget=budget, + ) + for item in inputs.visibility_inputs + ) + if any( + outcome.kind is not CardinalKernelKindV3.EXACT for outcome in (box, *visibility) + ): + raise ValueError("fresh point replay is not exact") + semantic_terms = box.bounds.common_cell_semantic_objective_terms + weighted_terms = box.bounds.common_cell_objective_terms + terms = tuple( + upright.UprightSE2ProposalPointTerm( + term_id=term_id, + lower=upright.UprightSE2ExactRational( + numerator=lower.numerator, denominator=lower.denominator + ), + upper=upright.UprightSE2ExactRational( + numerator=upper.numerator, denominator=upper.denominator + ), + ) + for term_id, lower, upper in semantic_terms + ) + lower = sum((item[1] for item in weighted_terms), start=Fraction(0)) + upper = sum((item[2] for item in weighted_terms), start=Fraction(0)) + return upright.UprightSE2ProposalPointObjective( + terms=terms, + total_lower=upright.UprightSE2ExactRational( + numerator=lower.numerator, denominator=lower.denominator + ), + total_upper=upright.UprightSE2ExactRational( + numerator=upper.numerator, denominator=upper.denominator + ), + ) + + +def _fresh_owner_evaluations( + *, + solve_request: CounterfactualSolveRequest, + row: upright.UprightSE2ProofCellEvaluation, + box: object, + visibility: tuple[object, ...], + include_finite_refinement_miss: bool, +) -> tuple[upright.UprightSE2RetainedOwnerEvaluation, ...]: + """Serialize fresh retained DTOs through the shared compiler seam only.""" + + point_row = row.compiled_cell.cell_id.endswith("/proposal-point") + exact = all( + outcome.kind is CardinalKernelKindV3.EXACT for outcome in (box, *visibility) + ) + additional_exact_bounds = () + if exact: + additional_exact_bounds = ( + upright._retained_point_objective_value( + _objective_from_box_bounds(box.bounds) + ), + ) + label_prefix = "fixed-cardinal-point" if point_row else "fixed-cardinal" + outcomes = ((f"{label_prefix}-cell", box),) + tuple( + (f"{label_prefix}-visibility-{index}", outcome) + for index, outcome in enumerate(visibility) + ) + kernel_evaluations = tuple( + sorted( + ( + build_upright_se2_retained_owner_evaluation( + compiled_cell=row.compiled_cell, + owner_ref=upright.UPRIGHT_SE2_BACKEND_OWNER_REF, + evaluator_capability_ref=( + upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF + ), + outcome_kind=_proof_kind(outcome.kind), + raw_proof_rows=tuple(outcome.proof_rows), + raw_findings=tuple(outcome.finding_codes), + atomic_steps=outcome.atomic_steps_used, + resource_delta=_resource_delta( + solve_request, + used=float(outcome.atomic_steps_used), + exhausted=(outcome.kind is CardinalKernelKindV3.RESOURCE_LIMIT), + ), + label=label, + exact_bound_value=outcome.bounds, + additional_exact_bounds=( + additional_exact_bounds if index == 0 else () + ), + ) + for index, (label, outcome) in enumerate(outcomes) + ), + key=canonical_json_bytes, + ) + ) + if not include_finite_refinement_miss: + return kernel_evaluations + if ( + row.leaf_disposition is not upright.UprightSE2ProofLeafDisposition.UNRESOLVED + or row.complete_domain_empty is not False + ): + raise ValueError("finite refinement miss must close one unresolved leaf") + finite_miss = build_upright_se2_retained_owner_evaluation( + compiled_cell=row.compiled_cell, + owner_ref=upright.UPRIGHT_SE2_BACKEND_OWNER_REF, + evaluator_capability_ref=upright.UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF, + outcome_kind=upright.UprightSE2RetainedOwnerOutcomeKind.FINITE_MISS, + raw_proof_rows=( + "proof:spatialcf/upright-se2/finite-refinement-miss/incomplete", + ), + raw_findings=("INCOMPLETE",), + atomic_steps=0, + resource_delta=_resource_delta( + solve_request, + used=0.0, + exhausted=False, + ), + label="finite-refinement-miss", + ) + return tuple(sorted((*kernel_evaluations, finite_miss), key=canonical_json_bytes)) + + +def _objective_from_box_bounds( + bounds: object, +) -> upright.UprightSE2ProposalPointObjective: + semantic_terms = bounds.common_cell_semantic_objective_terms + weighted_terms = bounds.common_cell_objective_terms + total_lower = sum( + (lower for _term_id, lower, _upper in weighted_terms), start=Fraction(0) + ) + total_upper = sum( + (upper for _term_id, _lower, upper in weighted_terms), start=Fraction(0) + ) + return upright.UprightSE2ProposalPointObjective( + terms=tuple( + upright.UprightSE2ProposalPointTerm( + term_id=term_id, + lower=upright.UprightSE2ExactRational( + numerator=lower.numerator, denominator=lower.denominator + ), + upper=upright.UprightSE2ExactRational( + numerator=upper.numerator, denominator=upper.denominator + ), + ) + for term_id, lower, upper in semantic_terms + ), + total_lower=upright.UprightSE2ExactRational( + numerator=total_lower.numerator, + denominator=total_lower.denominator, + ), + total_upper=upright.UprightSE2ExactRational( + numerator=total_upper.numerator, + denominator=total_upper.denominator, + ), + ) + + +def _resource_delta( + solve_request: CounterfactualSolveRequest, + *, + used: float, + exhausted: bool, +) -> ResourceUsage: + limits = solve_request.resource_policy.limits + if len(limits) != 1: + raise ValueError("upright se2 requires one exact resource limit") + return ResourceUsage.model_validate( + { + "accounting_claim_definition_ref": ( + solve_request.resource_policy.shared_ledger_policy_ref + ), + "entries": ( + { + "resource_definition_ref": limits[0].definition_ref, + "used": used, + }, + ), + "exhausted": exhausted, + } + ) + + +def _check_terminal_evidence( + submission: BackendSubmission, + proof: upright.UprightSE2ProofMaterial, + compilation: upright.UprightSE2Compilation, +) -> None: + del compilation + leaves = tuple( + row for row in proof.evaluated_cells if row.leaf_disposition is not None + ) + if type(submission) is BackendCompleteUnsatEvidence: + if ( + submission.authorized_domain_sha256 + != proof.compilation.endpoint_construction_recipe.translation_domain_sha256 + or submission.complete_domain_coverage_artifact_sha256 + != proof.coverage_artifact.coverage_artifact_sha256 + or not leaves + or any( + row.leaf_disposition + is not upright.UprightSE2ProofLeafDisposition.OUTWARD_INFEASIBLE + or row.complete_domain_empty is not True + for row in leaves + ) + ): + raise ValueError("complete UNSAT evidence lacks full outward coverage") + elif type(submission) is BackendProposalSubmission: + if not proof.proposal_candidates: + raise ValueError("proposal submission lacks a checked candidate") + elif ( + type(submission) is BackendUnknownEvidence + and submission.resource_usage != proof.total_resource_usage + ): + raise ValueError("unknown evidence resource usage does not match proof") + + +def _check_selected_unknown_reason( + *, + solve_request: CounterfactualSolveRequest, + submission: BackendSubmission, + proof: ( + upright.UprightSE2ProofMaterial | upright.UprightSE2ContinuousProofMaterial + ), +) -> None: + """Bind selected UNKNOWN evidence to freshly verified owner outcomes only.""" + + if type(submission) is not BackendUnknownEvidence: + return + outcome_kinds = { + evaluation.outcome_kind + for row in proof.evaluated_cells + for evaluation in row.owner_evaluations + } + if upright.UprightSE2RetainedOwnerOutcomeKind.RESOURCE_LIMIT in outcome_kinds: + expected_reason_claim_definition_ref = ( + solve_request.resource_policy.exhaustion_claim_ref + ) + elif upright.UprightSE2RetainedOwnerOutcomeKind.NUMERIC_GAP in outcome_kinds: + expected_reason_claim_definition_ref = ( + _UNKNOWN_NUMERIC_REASON_CLAIM_DEFINITION_REF + ) + elif upright.UprightSE2RetainedOwnerOutcomeKind.UNSUPPORTED in outcome_kinds: + expected_reason_claim_definition_ref = ( + _UNKNOWN_UNSUPPORTED_REASON_CLAIM_DEFINITION_REF + ) + elif ( + upright.UprightSE2RetainedOwnerOutcomeKind.INCOMPLETE in outcome_kinds + or upright.UprightSE2RetainedOwnerOutcomeKind.FINITE_MISS in outcome_kinds + or proof.unresolved_frontier + or any( + row.leaf_disposition is upright.UprightSE2ProofLeafDisposition.UNRESOLVED + for row in proof.evaluated_cells + ) + ): + expected_reason_claim_definition_ref = ( + _UNKNOWN_INCOMPLETE_REASON_CLAIM_DEFINITION_REF + ) + else: + raise ValueError("unknown evidence lacks fresh owner outcome support") + if submission.reason_claim_definition_ref != expected_reason_claim_definition_ref: + raise ValueError("unknown evidence reason does not bind fresh owner outcomes") + + +def _submission_checked_claim( + submission: BackendSubmission, + proof: upright.UprightSE2ProofMaterial, + checker_policy: upright.UprightSE2CheckerReplayPolicy, + evidence_claim_ref: str, + fresh_feasible_leaf_objectives: tuple[ + upright.UprightSE2ProposalPointObjective, ... + ], + fresh_candidate_objectives: tuple[upright.UprightSE2ProposalPointObjective, ...], +) -> str | None: + """Classify only complete freshly replayed cardinal evidence.""" + + if type(submission) is BackendCompleteUnsatEvidence: + return evidence_claim_ref + if type(submission) is not BackendProposalSubmission: + return None + leaves = tuple(row for row in proof.evaluated_cells if row.leaf_disposition) + if ( + proof.unresolved_frontier + or proof.prune_decisions + or not leaves + or not fresh_feasible_leaf_objectives + or len(fresh_candidate_objectives) != len(proof.proposal_candidates) + ): + return None + return _classify_complete_candidate_claim( + fresh_feasible_leaf_objectives=fresh_feasible_leaf_objectives, + fresh_candidate_objectives=fresh_candidate_objectives, + checker_policy=checker_policy, + ) + + +def _classify_complete_candidate_claim( + *, + fresh_feasible_leaf_objectives: tuple[ + upright.UprightSE2ProposalPointObjective, ... + ], + fresh_candidate_objectives: tuple[upright.UprightSE2ProposalPointObjective, ...], + checker_policy: upright.UprightSE2CheckerReplayPolicy, +) -> str | None: + """Apply the request policy to all fresh leaf and selected-point totals.""" + + if not fresh_feasible_leaf_objectives or not fresh_candidate_objectives: + return None + global_lower = min( + objective.total_lower.as_fraction + for objective in fresh_feasible_leaf_objectives + ) + totals = tuple( + (objective.total_lower.as_fraction, objective.total_upper.as_fraction) + for objective in ( + *fresh_feasible_leaf_objectives, + *fresh_candidate_objectives, + ) + ) + if all(lower == upper for lower, upper in totals) and len(set(totals)) == 1: + return checker_policy.exact_global_claim_definition_ref + selected = fresh_candidate_objectives[0] + if ( + selected.total_upper.as_fraction - global_lower + <= checker_policy.requested_gap.as_fraction + ): + return checker_policy.finite_gap_claim_definition_ref + return None + + +def _check_selected_proposal_bounds( + submission: BackendSubmission, + proof: upright.UprightSE2ProofMaterial, + fresh_candidate_objectives: tuple[upright.UprightSE2ProposalPointObjective, ...], +) -> None: + """Bind outer proposal floats to the canonical first fresh candidate only.""" + + if type(submission) is not BackendProposalSubmission: + return + if not proof.proposal_candidates or not fresh_candidate_objectives: + raise ValueError("proposal submission lacks a fresh selected candidate") + selected = proof.proposal_candidates[0] + fresh_selected = fresh_candidate_objectives[0] + proposal = submission.proposal + if ( + proposal.program_sha256 != selected.program.program_sha256 + or proposal.after_scene_state_sha256 + != selected.program.after_scene_state_sha256 + ): + raise ValueError("outer proposal does not bind the selected candidate program") + if proposal.objective_lower_bound != float( + fresh_selected.total_lower.as_fraction + ) or proposal.objective_upper_bound != float( + fresh_selected.total_upper.as_fraction + ): + raise ValueError( + "outer proposal objective bounds do not bind fresh selected candidate" + ) + + +def _classify_interval_claim( + *, + witness: upright.UprightSE2ProposalPointObjective, + global_lower: Fraction, + checker_policy: upright.UprightSE2CheckerReplayPolicy, +) -> str | None: + """Classify one freshly replayed objective interval without inventing a tie.""" + + witness_lower = witness.total_lower.as_fraction + witness_upper = witness.total_upper.as_fraction + if witness_lower == witness_upper and witness_lower == global_lower: + return checker_policy.exact_global_claim_definition_ref + if witness_upper - global_lower <= checker_policy.requested_gap.as_fraction: + return checker_policy.finite_gap_claim_definition_ref + return None + + +def _check_cardinal_roster_and_coverage( + compilation: upright.UprightSE2Compilation, + proof: upright.UprightSE2ProofMaterial, +) -> None: + """Bind the decoded proof's sole cardinal root and full XY leaf roster.""" + + if len(proof.cardinal_tuple_roster) != 1: + raise ValueError("checker requires one request-authorized cardinal tuple") + proof_tuple = proof.cardinal_tuple_roster[0] + if ( + canonical_json_bytes(proof_tuple.compiled_cells) + != canonical_json_bytes(compilation.compiled_cells) + or proof_tuple.translation_domain + != compilation.endpoint_construction_recipe.translation_domain + or proof.coverage_artifact.authorization_sha256 + != compilation.operation.authorization_sha256 + ): + raise ValueError("checker cardinal roster or translation coverage mismatch") + leaves = tuple( + row.compiled_cell + for row in proof.evaluated_cells + if row.leaf_disposition is not None + ) + if canonical_json_bytes(leaves) != canonical_json_bytes( + proof.coverage_artifact.cells + ): + raise ValueError("checker final leaf roster does not match coverage artifact") + + +def _checked_fact_refs( + proof: upright.UprightSE2ProofMaterial, + compilation: upright.UprightSE2Compilation, +) -> tuple[dict[str, str], ...]: + values = ( + proof.proof_material_sha256, + compilation.upright_se2_compilation_sha256, + proof.coverage_artifact.coverage_artifact_sha256, + proof.resource_ledger.proof_resource_ledger_sha256, + ) + return tuple( + sorted( + ( + { + "artifact_schema_ref": _CHECKED_FACT_SCHEMA_REF, + "artifact_sha256": canonical_sha256( + value, + domain="spatialcf/counterfactual/upright-se2/checked-fact/3.0", + ), + } + for value in values + ), + key=canonical_json_bytes, + ) + ) + + +def _proof_kind( + kind: CardinalKernelKindV3, +) -> upright.UprightSE2RetainedOwnerOutcomeKind: + return { + CardinalKernelKindV3.EXACT: upright.UprightSE2RetainedOwnerOutcomeKind.EXACT, + CardinalKernelKindV3.NUMERIC_GAP: upright.UprightSE2RetainedOwnerOutcomeKind.NUMERIC_GAP, + CardinalKernelKindV3.RESOURCE_LIMIT: upright.UprightSE2RetainedOwnerOutcomeKind.RESOURCE_LIMIT, + CardinalKernelKindV3.UNSUPPORTED: upright.UprightSE2RetainedOwnerOutcomeKind.UNSUPPORTED, + }[kind] + + +def _is_kernel_owner_evaluation( + evaluation: upright.UprightSE2RetainedOwnerEvaluation, +) -> bool: + return any( + "fixed-cardinal-cell" in row or "fixed-cardinal-visibility" in row + for row in evaluation.proof_rows + ) + + +def _classify_leaf(box: object, visibility: tuple[object, ...]): + if any( + outcome.kind is not CardinalKernelKindV3.EXACT for outcome in (box, *visibility) + ): + return upright.UprightSE2ProofLeafDisposition.UNRESOLVED + if ( + box.bounds.outer_hard_constraint_slack < 0 + or box.bounds.relation_outer_failure + or any(outcome.bounds.outer_failure for outcome in visibility) + ): + return upright.UprightSE2ProofLeafDisposition.OUTWARD_INFEASIBLE + if ( + box.bounds.inner_hard_constraint_slack >= 0 + and box.bounds.relation_inner_success + and all(outcome.bounds.inner_success for outcome in visibility) + ): + return upright.UprightSE2ProofLeafDisposition.INWARD_FEASIBLE + return None + + +def _fresh_leaf_matches_submission(*, submitted: object, fresh: object) -> bool: + """Bind terminal leaves, reserving ``UNRESOLVED`` for exact ambiguity.""" + + return submitted is fresh or ( + submitted is upright.UprightSE2ProofLeafDisposition.UNRESOLVED and fresh is None + ) + + +def _require_exact(value: object, model_type: type, label: str) -> None: + if type(value) is not model_type: + raise TypeError(f"{label} must be an exact {model_type.__name__}") + checked = model_type.model_validate( + value.model_dump(mode="python", round_trip=True), strict=True + ) + if canonical_json_bytes(checked) != canonical_json_bytes(value): + raise ValueError(f"{label} must use canonical bytes") diff --git a/src/spatialcf/domain/outcomes.py b/src/spatialcf/domain/outcomes.py index f54bde0..9b71450 100644 --- a/src/spatialcf/domain/outcomes.py +++ b/src/spatialcf/domain/outcomes.py @@ -26,8 +26,13 @@ from spatialcf.domain.serialization import canonical_json_bytes __all__ = ( + "BackendCompleteUnsatEvidence", "BackendProposal", + "BackendProposalSubmission", "BackendSelectionRecord", + "BackendSubmission", + "BackendTerminalEvidence", + "BackendUnknownEvidence", "CapabilityMatch", "CapabilityMismatch", "CertifiedSolutionCertificate", @@ -304,6 +309,106 @@ def _validate_untrusted_proposal(self) -> Self: return self +class BackendProposalSubmission(HashBoundCanonicalModel): + """The additive submission-v2 wrapper for one retained finite-bound proposal.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/backend-proposal-submission/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "backend_proposal_submission_sha256" + + submission_kind: Literal["PROPOSAL"] = "PROPOSAL" + proposal: BackendProposal + backend_proposal_sha256: Sha256Digest + backend_proposal_submission_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_retained_proposal(self) -> Self: + if self.backend_proposal_sha256 != self.proposal.backend_proposal_sha256: + raise ValueError("submission proposal digest does not match retained proposal") + return self + + +class _BackendTerminalEvidenceBase(HashBoundCanonicalModel): + """Shared pre-check terminal evidence fields with no objective/program payload.""" + + semantic_problem_sha256: Sha256Digest + solve_request_sha256: Sha256Digest + backend_selection_record_sha256: Sha256Digest + proposal_backend_ref: BackendRef + proposal_backend_owner_ref: OwnerRef + proposal_backend_capability_ref: CapabilityRef + proposal_backend_build_sha256: Sha256Digest + proof_material: ProofMaterialEnvelope + proof_material_sha256: Sha256Digest + resource_usage: ResourceUsage + partial_artifact_refs: tuple[_ArtifactReference, ...] = () + + @model_validator(mode="after") + def _validate_terminal_evidence_closure(self) -> Self: + material = self.proof_material + if self.proof_material_sha256 != material.proof_material_sha256: + raise ValueError("terminal proof material digest does not match envelope") + if ( + self.semantic_problem_sha256, + self.solve_request_sha256, + self.backend_selection_record_sha256, + self.proposal_backend_ref, + ) != ( + material.semantic_problem_sha256, + material.solve_request_sha256, + material.backend_selection_record_sha256, + material.proposal_backend_ref, + ): + raise ValueError("terminal evidence roots do not match proof material") + _require_sorted_unique_by_bytes( + self.partial_artifact_refs, + "terminal partial artifacts", + ) + return self + + +class BackendCompleteUnsatEvidence(_BackendTerminalEvidenceBase): + """Untrusted complete-domain empty evidence without fabricated bounds.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/backend-complete-unsat-evidence/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "backend_complete_unsat_evidence_sha256" + + submission_kind: Literal["COMPLETE_DOMAIN_UNSAT_EVIDENCE"] = ( + "COMPLETE_DOMAIN_UNSAT_EVIDENCE" + ) + complete_domain_claim_definition_ref: DefinitionRef + authorized_domain_sha256: Sha256Digest + complete_domain_coverage_artifact_sha256: Sha256Digest + backend_complete_unsat_evidence_sha256: Sha256Digest + + +class BackendUnknownEvidence(_BackendTerminalEvidenceBase): + """Untrusted selected-backend unknown evidence without fabricated bounds.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/backend-unknown-evidence/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "backend_unknown_evidence_sha256" + + submission_kind: Literal["UNKNOWN_EVIDENCE"] = "UNKNOWN_EVIDENCE" + reason_claim_definition_ref: DefinitionRef + backend_unknown_evidence_sha256: Sha256Digest + + +BackendTerminalEvidence: TypeAlias = Annotated[ + BackendCompleteUnsatEvidence | BackendUnknownEvidence, + Field(discriminator="submission_kind"), +] + +BackendSubmission: TypeAlias = Annotated[ + BackendProposalSubmission | BackendCompleteUnsatEvidence | BackendUnknownEvidence, + Field(discriminator="submission_kind"), +] + + class CheckerDisposition(StrEnum): """The complete structural disposition vocabulary for one trusted checker.""" diff --git a/src/spatialcf/domain/upright_se2.py b/src/spatialcf/domain/upright_se2.py new file mode 100644 index 0000000..1b29505 --- /dev/null +++ b/src/spatialcf/domain/upright_se2.py @@ -0,0 +1,8874 @@ +"""Immutable Task 1 contracts for the ``spatialcf/upright_se2@1`` profile. + +This module owns only declarative profile, authorization, and proof-wire data. +It does not compile a scene, transform geometry, invoke a kernel, select a +backend, or solve a counterfactual. +""" + +from __future__ import annotations + +import math +from dataclasses import dataclass +from enum import StrEnum +from fractions import Fraction +from itertools import pairwise +from typing import Annotated, ClassVar, Literal, Self, TypeAlias, TypeVar + +from pydantic import Field, StrictBool, StrictInt, model_validator + +from spatialcf.domain.base import ( + CanonicalId, + CanonicalModel, + FactAvailabilityV2, + FactCompletenessV2, + FactSetV2, + FiniteFloat, + Quaternion, + RigidTransformV2, + Sha256Digest, + Vec2, + Vec3, +) +from spatialcf.domain.compatibility import PlanarTranslateCompilation +from spatialcf.domain.counterfactual import ( + CounterfactualProblemIR, + CounterfactualSolveRequest, + EditProgram, + ExtensionFact, + ExtensionFactBundle, + SceneStateEnvelope, +) +from spatialcf.domain.definitions import ( + BooleanValue, + CanonicalDefinitionEnvelope, + CanonicalIdValue, + CapabilityRef, + DefinitionBundle, + DefinitionRef, + DigestValue, + EnumSymbolValue, + FiniteOrderedTupleValue, + FiniteRealValue, + HashBoundCanonicalModel, + IntegerValue, + IntervalValue, + NamedTypedValue, + RecordValue, + ReferenceValue, + TypedValue, + ValueKind, +) +from spatialcf.domain.geometry import ( + CollisionBodyFactV2, + GeometryInstanceV2, + GeometryRoleV2, +) +from spatialcf.domain.operators import ( + OperationArgument, + OperationInvocation, + StateDeltaManifest, + StateVariableRef, +) +from spatialcf.domain.outcomes import ResourceUsage +from spatialcf.domain.predicates import ( + AfterGoal, + BeforePrecondition, + GroundedObligation, + GroundedObligationSet, + ObservationObligation, + PredicateAtom, + PreservationInvariant, +) +from spatialcf.domain.profiles import ( + ActionSpaceProfile, + ObjectiveExpression, + ObjectiveTerm, + OwnerRef, + ProofPolicy, + ResourcePolicy, + SemanticsProfile, +) +from spatialcf.domain.scene import ( + BaselineObservation, + CanonicalScene, + ObjectPose, + SupportSurfaceFact, +) +from spatialcf.domain.serialization import canonical_json_bytes, canonical_sha256 + +__all__ = ( + "UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_DEFINITION_REF", + "UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_DISCRIMINATOR", + "UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF", + "UPRIGHT_SE2_DERIVED_SOURCE_HASH_DOMAIN", + "UPRIGHT_SE2_PROOF_MATERIAL_DEFINITION_REF", + "UPRIGHT_SE2_PROOF_MATERIAL_DISCRIMINATOR", + "UPRIGHT_SE2_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF", + "UPRIGHT_SE2_SEMANTIC_CLOSURE_DEFINITION_REF", + "UPRIGHT_SE2_YAW_TO_POSE_RULE_REF", + "CanonicalSO2Angle", + "CardinalYaw", + "CardinalYawAuthorization", + "ContinuousYawArc", + "ContinuousYawAuthorization", + "ContinuousYawDomain", + "ContinuousYawFullCircle", + "ContinuousYawLift", + "ExactDyadic", + "ExplicitPoseYawBinding", + "FixedPivotBinding", + "LiftedYawInterval", + "PivotMode", + "UprightSE2AfterStateTemplate", + "UprightSE2BackendAvailability", + "UprightSE2CardinalOperation", + "UprightSE2CardinalProofTuple", + "UprightSE2CheckerReplayPolicy", + "UprightSE2Compilation", + "UprightSE2CompiledCell", + "UprightSE2CompilerClosure", + "UprightSE2ContinuousCompilation", + "UprightSE2ContinuousEndpointConstructionRecipe", + "UprightSE2ContinuousMaterializedEndpoint", + "UprightSE2ContinuousOperation", + "UprightSE2ContinuousProofMaterial", + "UprightSE2ContinuousProofTuple", + "UprightSE2ContinuousProposalCandidate", + "UprightSE2ContinuousSemanticClosure", + "UprightSE2CoverageArtifact", + "UprightSE2DerivedAfterFact", + "UprightSE2EndpointConstructionRecipe", + "UprightSE2ExactRational", + "UprightSE2ExecutablePolicyBundle", + "UprightSE2ExecutablePolicyValue", + "UprightSE2FiveTermObjectivePolicy", + "UprightSE2M2Q0Construction", + "UprightSE2M2Q0MappingDefinition", + "UprightSE2M2Q0MappingRow", + "UprightSE2M2Q0SourceFreeConstructionRow", + "UprightSE2MaterializedEndpoint", + "UprightSE2ObjectiveTermPolicy", + "UprightSE2ProfileRegistration", + "UprightSE2ProofCellEvaluation", + "UprightSE2ProofFrontierRow", + "UprightSE2ProofLeafDisposition", + "UprightSE2ProofMaterial", + "UprightSE2ProofPruneDecision", + "UprightSE2ProofResourceLedger", + "UprightSE2ProofStageDelta", + "UprightSE2ProposalCandidate", + "UprightSE2ProposalPointEvaluation", + "UprightSE2ProposalPointObjective", + "UprightSE2ProposalPointTerm", + "UprightSE2RetainedOwnerEvaluation", + "UprightSE2RetainedOwnerOutcomeKind", + "UprightSE2SemanticClosure", + "UprightSE2SemanticDefinition", + "UprightSE2SemanticOwnerBinding", + "UprightSE2SolvePolicyDefinitionPayload", + "UprightSE2StateFootprint", + "UprightSE2TranslationDomain", + "UprightSE2VerificationBundle", + "build_upright_se2_checker_replay_policy", + "build_upright_se2_continuous_semantic_closure", + "build_upright_se2_executable_policy_bundle", + "build_upright_se2_objective_expression", + "build_upright_se2_q0_target_objective_policy", + "build_upright_se2_semantic_closure", + "build_upright_se2_semantic_definition_bundle", + "build_upright_se2_solve_policy_definition_bundle", + "canonical_yaw_from_upright_quaternion", + "decode_upright_se2_continuous_proof_material", + "decode_upright_se2_objective_policy", + "decode_upright_se2_proof_material", + "decode_upright_se2_solve_policy_definition_payload", + "encode_upright_se2_continuous_proof_material", + "encode_upright_se2_proof_material", + "request_bound_executable_policy_bundle_from_problem", + "validate_directed_yaw_quaternion_consistency", + "validate_required_upright_support_surface", + "validate_upright_se2_executable_policy_visibility_binding", +) + + +UPRIGHT_SE2_PROFILE_REF = "spatialcf/upright_se2@1" +UPRIGHT_SE2_SEMANTICS_PROFILE_REF = "spatialcf/upright_se2/semantics@1" +UPRIGHT_SE2_YAW_TO_POSE_RULE_REF = "definition:spatialcf/upright-se2/yaw-to-pose/1.0" +UPRIGHT_SE2_DERIVED_SOURCE_HASH_DOMAIN = ( + "spatialcf/counterfactual/upright-se2/derived-source-fact/3.0" +) +UPRIGHT_SE2_PROOF_MATERIAL_DEFINITION_REF = ( + "definition:spatialcf/upright-se2/proof-material/1.0" +) +UPRIGHT_SE2_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF = ( + "schema:spatialcf/upright-se2/cardinal-proof-material/1.0" +) +UPRIGHT_SE2_PROOF_MATERIAL_DISCRIMINATOR = "UPRIGHT_SE2_CARDINAL_PROOF_MATERIAL_V1" +UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_DEFINITION_REF = ( + "definition:spatialcf/upright-se2/continuous-proof-material/1.0" +) +UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF = ( + "schema:spatialcf/upright-se2/continuous-proof-material/1.0" +) +UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_DISCRIMINATOR = ( + "UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_V1" +) +UPRIGHT_SE2_SOLVE_POLICY_DEFINITION_REF = ( + "definition:spatialcf/upright-se2/solve-policy/1.0" +) +UPRIGHT_SE2_SOLVE_POLICY_PAYLOAD_SCHEMA_REF = ( + "schema:spatialcf/upright-se2/solve-policy-closure/1.0" +) +UPRIGHT_SE2_EXACT_GLOBAL_CLAIM_DEFINITION_REF = ( + "definition:spatialcf/upright-se2/claim-certified-solution/1.0" +) +UPRIGHT_SE2_FINITE_GAP_CLAIM_DEFINITION_REF = ( + "definition:spatialcf/upright-se2/claim-finite-gap-solution/1.0" +) +_UPRIGHT_SE2_PIVOT_STATE_HASH_DOMAIN = ( + "spatialcf/counterfactual/upright-se2/pivot-state/3.0" +) +_UPRIGHT_SE2_UNCHANGED_LEAVES_HASH_DOMAIN = ( + "spatialcf/counterfactual/upright-se2/unchanged-leaves/3.0" +) +_UPRIGHT_SE2_YAW_TO_POSE_MAX_ULPS = 8 +_UPRIGHT_SE2_COMPILER_INPUT_FAMILY_REF = ( + "definition:spatialcf/upright-se2/compiler-input/1.0" +) +_UPRIGHT_SE2_COMPILER_INPUT_SCHEMA_REF = ( + "schema:spatialcf/upright-se2/compiler-input/1.0" +) +_UPRIGHT_SE2_COMPILER_INPUT_FACT_KEY = "fact-key:spatialcf/upright-se2/compiler-input" +_UPRIGHT_SE2_STATE_FAMILY_REF = "definition:spatialcf/upright-se2/state/1.0" +_UPRIGHT_SE2_STATE_SCHEMA_REF = "schema:spatialcf/upright-se2/state/1.0" +_UPRIGHT_SE2_REAL_SCHEMA_REF = "schema:spatialcf/upright-se2/finite-real/1.0" +_UPRIGHT_SE2_INTEGER_SCHEMA_REF = "schema:spatialcf/upright-se2/integer/1.0" +_UPRIGHT_SE2_ID_SCHEMA_REF = "schema:spatialcf/upright-se2/canonical-id/1.0" +_UPRIGHT_SE2_DIGEST_SCHEMA_REF = "schema:spatialcf/upright-se2/digest/1.0" +_UPRIGHT_SE2_ENUM_SCHEMA_REF = "schema:spatialcf/upright-se2/enum-symbol/1.0" +_UPRIGHT_SE2_YAW_ARGUMENT_SCHEMA_REF = "schema:spatialcf/upright-se2/yaw-argument/1.0" +_UPRIGHT_SE2_WORLD_XY_FRAME_REF = "definition:spatialcf/upright-se2/world-xy/1.0" +_UPRIGHT_SE2_METRE_UNIT_REF = "definition:spatialcf/upright-se2/metre/1.0" +_UPRIGHT_SE2_CLOSED_INTERVAL_TOPOLOGY_REF = ( + "definition:spatialcf/upright-se2/closed-interval/1.0" +) +_UPRIGHT_SE2_DERIVED_RULE_REF = ( + "definition:spatialcf/upright-se2/derived-pose-and-facts/1.0" +) +_UPRIGHT_SE2_PRIMARY_ROLES = ( + "subject-world-x", + "subject-world-y", + "subject-explicit-yaw", +) +_UPRIGHT_SE2_DERIVED_ROLES = ( + "subject-derived-canonical-pose", + "subject-derived-collision", + "subject-derived-support", + "subject-derived-relation", + "subject-derived-visibility", +) +_UPRIGHT_SE2_CARDINAL_TURN_FRACTIONS = { + 0: Fraction(0), + 1: Fraction(1, 4), + 2: Fraction(-1, 2), + 3: Fraction(-1, 4), +} +_UPRIGHT_SE2_CARDINAL_QUATERNIONS = { + 0: (0.0, 1.0), + 1: (0.7071067811865476, 0.7071067811865476), + 2: (1.0, 0.0), + 3: (-0.7071067811865476, 0.7071067811865476), +} + +UPRIGHT_SE2_CARDINAL_OWN_PIVOT_OPERATOR_REF = ( + "definition:spatialcf/upright-se2/cardinal-own-pivot/1.0" +) +UPRIGHT_SE2_CARDINAL_REFERENCE_PIVOT_OPERATOR_REF = ( + "definition:spatialcf/upright-se2/cardinal-reference-pivot/1.0" +) +UPRIGHT_SE2_CONTINUOUS_OWN_PIVOT_OPERATOR_REF = ( + "definition:spatialcf/upright-se2/continuous-own-pivot/1.0" +) +UPRIGHT_SE2_CONTINUOUS_REFERENCE_PIVOT_OPERATOR_REF = ( + "definition:spatialcf/upright-se2/continuous-reference-pivot/1.0" +) + +UPRIGHT_SE2_OPERATOR_REFS = tuple( + sorted( + ( + UPRIGHT_SE2_CARDINAL_OWN_PIVOT_OPERATOR_REF, + UPRIGHT_SE2_CARDINAL_REFERENCE_PIVOT_OPERATOR_REF, + UPRIGHT_SE2_CONTINUOUS_OWN_PIVOT_OPERATOR_REF, + UPRIGHT_SE2_CONTINUOUS_REFERENCE_PIVOT_OPERATOR_REF, + ), + key=canonical_json_bytes, + ) +) + +UPRIGHT_SE2_PROFILE_CAPABILITY_REF = "capability:spatialcf/upright-se2/profile/1" +UPRIGHT_SE2_CARDINAL_COMPILER_CAPABILITY_REF = ( + "capability:spatialcf/upright-se2/cardinal/compiler/1" +) +UPRIGHT_SE2_CARDINAL_BACKEND_CAPABILITY_REF = ( + "capability:spatialcf/upright-se2/cardinal/backend/1" +) +UPRIGHT_SE2_CARDINAL_CHECKER_CAPABILITY_REF = ( + "capability:spatialcf/upright-se2/cardinal/checker/1" +) +UPRIGHT_SE2_CONTINUOUS_COMPILER_CAPABILITY_REF = ( + "capability:spatialcf/upright-se2/continuous/compiler/1" +) +UPRIGHT_SE2_CONTINUOUS_BACKEND_CAPABILITY_REF = ( + "capability:spatialcf/upright-se2/continuous/backend/1" +) +UPRIGHT_SE2_CONTINUOUS_CHECKER_CAPABILITY_REF = ( + "capability:spatialcf/upright-se2/continuous/checker/1" +) +UPRIGHT_SE2_CARDINAL_CAPABILITY_REFS = tuple( + sorted( + ( + UPRIGHT_SE2_CARDINAL_COMPILER_CAPABILITY_REF, + UPRIGHT_SE2_CARDINAL_BACKEND_CAPABILITY_REF, + UPRIGHT_SE2_CARDINAL_CHECKER_CAPABILITY_REF, + ), + key=canonical_json_bytes, + ) +) +UPRIGHT_SE2_CONTINUOUS_CAPABILITY_REFS = tuple( + sorted( + ( + UPRIGHT_SE2_CONTINUOUS_COMPILER_CAPABILITY_REF, + UPRIGHT_SE2_CONTINUOUS_BACKEND_CAPABILITY_REF, + UPRIGHT_SE2_CONTINUOUS_CHECKER_CAPABILITY_REF, + ), + key=canonical_json_bytes, + ) +) +UPRIGHT_SE2_STAGED_CAPABILITY_REFS = tuple( + sorted( + ( + *UPRIGHT_SE2_CARDINAL_CAPABILITY_REFS, + *UPRIGHT_SE2_CONTINUOUS_CAPABILITY_REFS, + ), + key=canonical_json_bytes, + ) +) +UPRIGHT_SE2_BACKEND_OWNER_REF = "owner:spatialcf/upright-se2/backend" +UPRIGHT_SE2_CHECKER_OWNER_REF = "owner:spatialcf/upright-se2/checker" +UPRIGHT_SE2_COMPILER_OWNER_REF = "owner:spatialcf/upright-se2/compiler" +UPRIGHT_SE2_COMPILER_BUILD_SHA256 = "c" * 64 +UPRIGHT_SE2_CHECKER_BUILD_SHA256 = "a" * 64 + +UPRIGHT_SE2_COLLISION_PREDICATE_REF = ( + "definition:spatialcf/upright-se2/collision-clearance-contact/1.0" +) +UPRIGHT_SE2_SUPPORT_PREDICATE_REF = ( + "definition:spatialcf/upright-se2/same-surface-support/1.0" +) +UPRIGHT_SE2_TARGET_RELATION_PREDICATE_REF = ( + "definition:spatialcf/upright-se2/target-relation/1.0" +) +UPRIGHT_SE2_PRESERVATION_PREDICATE_REF = ( + "definition:spatialcf/upright-se2/preservation/1.0" +) +UPRIGHT_SE2_VISIBILITY_PREDICATE_REF = ( + "definition:spatialcf/upright-se2/fixed-camera-visibility/1.0" +) +UPRIGHT_SE2_PREDICATE_DEFINITION_REFS = tuple( + sorted( + ( + UPRIGHT_SE2_COLLISION_PREDICATE_REF, + UPRIGHT_SE2_SUPPORT_PREDICATE_REF, + UPRIGHT_SE2_TARGET_RELATION_PREDICATE_REF, + UPRIGHT_SE2_PRESERVATION_PREDICATE_REF, + UPRIGHT_SE2_VISIBILITY_PREDICATE_REF, + ), + key=canonical_json_bytes, + ) +) +UPRIGHT_SE2_OBJECTIVE_DEFINITION_REF = ( + "definition:spatialcf/upright-se2/objective-five-term/1.0" +) +UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF = ( + "capability:spatialcf/upright-se2/semantic-predicate-evaluator/1" +) +UPRIGHT_SE2_PREDICATE_VERIFIER_CAPABILITY_REF = ( + "capability:spatialcf/upright-se2/semantic-predicate-verifier/1" +) +UPRIGHT_SE2_OBJECTIVE_EVALUATOR_CAPABILITY_REF = ( + "capability:spatialcf/upright-se2/semantic-objective-evaluator/1" +) +UPRIGHT_SE2_OBJECTIVE_VERIFIER_CAPABILITY_REF = ( + "capability:spatialcf/upright-se2/semantic-objective-verifier/1" +) +UPRIGHT_SE2_PREDICATE_CAPABILITY_REFS = tuple( + sorted( + ( + UPRIGHT_SE2_PROFILE_CAPABILITY_REF, + UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF, + UPRIGHT_SE2_PREDICATE_VERIFIER_CAPABILITY_REF, + ), + key=canonical_json_bytes, + ) +) +UPRIGHT_SE2_OBJECTIVE_CAPABILITY_REFS = tuple( + sorted( + ( + UPRIGHT_SE2_OBJECTIVE_EVALUATOR_CAPABILITY_REF, + UPRIGHT_SE2_OBJECTIVE_VERIFIER_CAPABILITY_REF, + ), + key=canonical_json_bytes, + ) +) + +_ValueT = TypeVar("_ValueT") + + +@dataclass(frozen=True) +class _UprightSE2SourceInput: + """The exact cardinal compiler-input wire carried by a bound solve request.""" + + operator_ref: DefinitionRef + subject_id: CanonicalId + reference_id: CanonicalId + subject_yaw_turns: CanonicalSO2Angle + quarter_turns_ccw: int + + +@dataclass(frozen=True) +class _UprightSE2ContinuousSourceInput: + """The exact continuous compiler-input wire bound by the additive closure.""" + + operator_ref: DefinitionRef + subject_id: CanonicalId + reference_id: CanonicalId + subject_yaw_turns: CanonicalSO2Angle + yaw_domain: ContinuousYawDomain + + +def _require_sorted_unique_by_bytes(values: tuple[_ValueT, ...], label: str) -> None: + encoded = tuple(canonical_json_bytes(value) for value in values) + if encoded != tuple(sorted(encoded)): + raise ValueError(f"{label} must be sorted") + if len(set(encoded)) != len(encoded): + raise ValueError(f"{label} must not contain duplicate entries") + + +def _is_negative_zero(value: float) -> bool: + return value == 0.0 and math.copysign(1.0, value) < 0.0 + + +def _fraction_from_float(value: float) -> Fraction: + return Fraction.from_float(value) + + +def _m2_q0_source_leaf_values( + source_compilation: PlanarTranslateCompilation, +) -> dict[str, object]: + """Return every retained M2 leaf under its canonical provenance selector.""" + + values: dict[str, object] = {} + + def visit(value: object, selector: str) -> None: + if type(value) is dict: + if not value: + values[selector] = value + return + for key in sorted(value, key=canonical_json_bytes): + visit(value[key], f"{selector}/{key}") + return + if type(value) in (list, tuple): + if not value: + values[selector] = value + return + for index, item in enumerate(value): + visit(item, f"{selector}/{index}") + return + values[selector] = value + + visit( + source_compilation.model_dump(mode="python", round_trip=True), + "source:compilation", + ) + return values + + +def _m2_q0_source_leaf_selectors( + source_compilation: PlanarTranslateCompilation, +) -> frozenset[str]: + """Return every retained M2 leaf selector in canonical source-tree order.""" + + return frozenset(_m2_q0_source_leaf_values(source_compilation)) + + +class CanonicalSO2Angle(CanonicalModel): + """One canonical finite binary64 turn value in the half-open SO(2) wire.""" + + turns: FiniteFloat + + @model_validator(mode="after") + def _validate_turns(self) -> Self: + if _is_negative_zero(self.turns): + raise ValueError("negative zero turns are not canonical") + if not -0.5 <= self.turns < 0.5: + raise ValueError("canonical turns must be in [-0.5, 0.5)") + if self.turns == 0.0: + object.__setattr__(self, "turns", 0.0) + return self + + +def canonical_yaw_from_upright_quaternion(rotation: Quaternion) -> CanonicalSO2Angle: + """Derive the registered directed SO(2) view of one yaw-only quaternion. + + The cardinal compiler never uses this conversion for a cardinal transform: + it is solely the profile-owned consistency view for an already-normalized + base or endpoint quaternion. The ``Quaternion`` contract has already + selected the unique sign representative, so the half-turn seam maps to + canonical ``-0.5`` rather than admitting a second directed wire value. + """ + + if rotation.x != 0.0 or rotation.y != 0.0: + raise ValueError("directed yaw quaternion must be yaw-only") + turns = math.atan2(rotation.z, rotation.w) / math.pi + if turns == 0.5: + turns = -0.5 + if not -0.5 <= turns < 0.5: + raise ValueError("directed yaw quaternion is ambiguous") + return CanonicalSO2Angle(turns=0.0 if turns == 0.0 else turns) + + +def validate_directed_yaw_quaternion_consistency( + explicit_yaw: CanonicalSO2Angle, + rotation: Quaternion, +) -> None: + """Enforce the registered binary64 yaw-to-quaternion consistency rule. + + The canonical directed yaw remains primary state. Its quaternion view is + recovered with the deterministic ``atan2(z, w) / pi`` rule, then compared + in the SO(2) quotient using a bounded binary64 reconstruction allowance. + The allowance covers the final rounding of a normalized stored quaternion; + it never classifies a non-cardinal angle as cardinal. + """ + + derived_yaw = canonical_yaw_from_upright_quaternion(rotation) + distance = abs(explicit_yaw.turns - derived_yaw.turns) + distance = min(distance, 1.0 - distance) + tolerance = _UPRIGHT_SE2_YAW_TO_POSE_MAX_ULPS * max( + math.ulp(explicit_yaw.turns), + math.ulp(derived_yaw.turns), + ) + if distance > tolerance: + raise ValueError("pose yaw does not match the frozen directed base pose") + + +def _compose_upright_quaternion_from_primary_yaw( + *, + rotation: Quaternion, + delta_z: float, + delta_w: float, + primary_expected_yaw: CanonicalSO2Angle, +) -> Quaternion: + """Build one derived yaw view while keeping its canonical yaw primary.""" + + composed = Quaternion( + x=0.0, + y=0.0, + z=( + 0.0 + if rotation.z * delta_w + rotation.w * delta_z == 0.0 + else rotation.z * delta_w + rotation.w * delta_z + ), + w=( + 0.0 + if rotation.w * delta_w - rotation.z * delta_z == 0.0 + else rotation.w * delta_w - rotation.z * delta_z + ), + ) + try: + validate_directed_yaw_quaternion_consistency(primary_expected_yaw, composed) + except ValueError: + if primary_expected_yaw.turns == 0.0: + z, w = _UPRIGHT_SE2_CARDINAL_QUATERNIONS[0] + elif primary_expected_yaw.turns == 0.25: + z, w = _UPRIGHT_SE2_CARDINAL_QUATERNIONS[1] + elif primary_expected_yaw.turns == -0.5: + z, w = _UPRIGHT_SE2_CARDINAL_QUATERNIONS[2] + elif primary_expected_yaw.turns == -0.25: + z, w = _UPRIGHT_SE2_CARDINAL_QUATERNIONS[3] + else: + half_radians = math.pi * primary_expected_yaw.turns + z = math.sin(half_radians) + w = math.cos(half_radians) + reconstructed = Quaternion(x=0.0, y=0.0, z=z, w=w) + validate_directed_yaw_quaternion_consistency( + primary_expected_yaw, + reconstructed, + ) + return reconstructed + return composed + + +class CardinalYaw(CanonicalModel): + """The exact cardinal yaw wire, never a coerced floating-point turn.""" + + q: Annotated[StrictInt, Field(ge=0, le=3)] + + +class ContinuousYawArc(CanonicalModel): + """One closed directed SO(2) arc without a wire-level branch-cut split.""" + + kind: Literal["ARC"] = "ARC" + start_angle: CanonicalSO2Angle + ccw_sweep_turns: FiniteFloat + + @model_validator(mode="after") + def _validate_sweep(self) -> Self: + if _is_negative_zero(self.ccw_sweep_turns): + raise ValueError("negative zero sweep is not canonical") + if not 0.0 <= self.ccw_sweep_turns < 1.0: + raise ValueError("ccw sweep turns must be in [0, 1)") + if self.ccw_sweep_turns == 0.0: + object.__setattr__(self, "ccw_sweep_turns", 0.0) + return self + + @property + def is_closed_point(self) -> bool: + return self.ccw_sweep_turns == 0.0 + + @property + def crosses_branch_cut(self) -> bool: + start = _fraction_from_float(self.start_angle.turns) + upper = start + _fraction_from_float(self.ccw_sweep_turns) + return start < Fraction(1, 2) < upper + + @property + def contains_canonical_zero(self) -> bool: + start = _fraction_from_float(self.start_angle.turns) + upper = start + _fraction_from_float(self.ccw_sweep_turns) + return any( + start <= representative <= upper + for representative in (Fraction(0), Fraction(1)) + ) + + +class ContinuousYawFullCircle(CanonicalModel): + """The sole full-SO(2) branch; a sweep of one turn is not an alias.""" + + kind: Literal["FULL_CIRCLE"] = "FULL_CIRCLE" + + +ContinuousYawDomain: TypeAlias = Annotated[ + ContinuousYawArc | ContinuousYawFullCircle, + Field(discriminator="kind"), +] + + +class ExactDyadic(CanonicalModel): + """A normalized exact dyadic rational for a lifted turn endpoint.""" + + numerator: StrictInt + denominator: Annotated[StrictInt, Field(gt=0)] + + @model_validator(mode="after") + def _validate_normalized_dyadic(self) -> Self: + if self.denominator & (self.denominator - 1): + raise ValueError("dyadic denominator must be a power of two") + if self.numerator == 0 and self.denominator != 1: + raise ValueError("zero dyadic must use denominator one") + if self.denominator != 1 and self.numerator % 2 == 0: + raise ValueError("dyadic numerator and denominator must be normalized") + return self + + @property + def as_fraction(self) -> Fraction: + return Fraction(self.numerator, self.denominator) + + +class LiftedYawInterval(CanonicalModel): + """One closed exact-dyadic interval in the fixed ``-0.5`` proof lift.""" + + lower: ExactDyadic + upper: ExactDyadic + endpoint_closure: Literal["CLOSED"] = "CLOSED" + seam_ownership: Literal["NONE", "LOWER_OWNS_SEAM", "UPPER_OWNS_ENDPOINT"] + + @model_validator(mode="after") + def _validate_interval(self) -> Self: + if self.lower.as_fraction > self.upper.as_fraction: + raise ValueError("lifted interval lower endpoint must not exceed upper") + return self + + +class ContinuousYawLift(HashBoundCanonicalModel): + """The one canonical non-wrapping lifted interval closure for a yaw domain.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/continuous-yaw-lift/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "continuous_yaw_lift_sha256" + + yaw_domain: ContinuousYawDomain + lift_origin: ExactDyadic + intervals: tuple[LiftedYawInterval, ...] + continuous_yaw_lift_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_canonical_lift(self) -> Self: + if self.lift_origin.as_fraction != Fraction(-1, 2): + raise ValueError("lift origin must be exactly -0.5 turns") + if len(self.intervals) != 1: + raise ValueError("a canonical yaw domain has exactly one lifted interval") + interval = self.intervals[0] + if isinstance(self.yaw_domain, ContinuousYawFullCircle): + if ( + interval.lower.as_fraction, + interval.upper.as_fraction, + interval.seam_ownership, + ) != (Fraction(-1, 2), Fraction(1, 2), "LOWER_OWNS_SEAM"): + raise ValueError("full-circle lift must use the lower-owned seam") + return self + start = _fraction_from_float(self.yaw_domain.start_angle.turns) + upper = start + _fraction_from_float(self.yaw_domain.ccw_sweep_turns) + expected_seam = "UPPER_OWNS_ENDPOINT" if upper == Fraction(1, 2) else "NONE" + if (interval.lower.as_fraction, interval.upper.as_fraction) != (start, upper): + raise ValueError("arc lift endpoints must be exact dyadic unrolled values") + if interval.seam_ownership != expected_seam: + raise ValueError("arc seam ownership is not canonical") + return self + + +class PivotMode(StrEnum): + """The only two endpoint pivot bindings admitted by this profile.""" + + OWN = "OWN" + REFERENCE = "REFERENCE" + + +class FixedPivotBinding(HashBoundCanonicalModel): + """A hash-bound frozen subject or named reference object pivot.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/fixed-pivot-binding/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "fixed_pivot_binding_sha256" + + subject_id: CanonicalId + pivot_mode: PivotMode + pivot_entity_id: CanonicalId + pivot_state_sha256: Sha256Digest + fixed_pivot_binding_sha256: Sha256Digest + + @classmethod + def seal(cls, **values) -> Self: + """Seal the canonical JSON pivot-mode symbol as its closed enum value.""" + + pivot_mode = values.get("pivot_mode") + if isinstance(pivot_mode, str): + values = {**values, "pivot_mode": PivotMode(pivot_mode)} + return super().seal(**values) + + @model_validator(mode="after") + def _validate_pivot_identity(self) -> Self: + if self.pivot_mode is PivotMode.OWN and self.pivot_entity_id != self.subject_id: + raise ValueError("own pivot must be the exact subject entity") + if ( + self.pivot_mode is PivotMode.REFERENCE + and self.pivot_entity_id == self.subject_id + ): + raise ValueError("reference pivot must not name the subject") + return self + + +class ExplicitPoseYawBinding(HashBoundCanonicalModel): + """The explicit primary yaw fact and its hash-bound derived base-pose view.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/explicit-pose-yaw-binding/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "pose_yaw_binding_sha256" + + entity_id: CanonicalId + base_pose_sha256: Sha256Digest + explicit_yaw: CanonicalSO2Angle + yaw_to_pose_rule_ref: DefinitionRef + pose_yaw_binding_sha256: Sha256Digest + + +class CardinalYawAuthorization(HashBoundCanonicalModel): + """One exact cardinal-yaw authorization without a hidden reference pivot.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/cardinal-yaw-authorization/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "cardinal_yaw_authorization_sha256" + + subject_id: CanonicalId + operator_ref: DefinitionRef + pivot_binding: FixedPivotBinding + yaw: CardinalYaw + cardinal_yaw_authorization_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_cardinal_authorization(self) -> Self: + if self.subject_id != self.pivot_binding.subject_id: + raise ValueError("authorization subject must match fixed pivot subject") + if self.operator_ref == UPRIGHT_SE2_CARDINAL_OWN_PIVOT_OPERATOR_REF: + if self.pivot_binding.pivot_mode is not PivotMode.OWN: + raise ValueError("own-pivot operator requires an own pivot") + elif self.operator_ref == UPRIGHT_SE2_CARDINAL_REFERENCE_PIVOT_OPERATOR_REF: + if self.pivot_binding.pivot_mode is not PivotMode.REFERENCE: + raise ValueError("reference-pivot operator requires a reference pivot") + if self.yaw.q == 0: + raise ValueError("reference-pivot zero yaw is not canonical") + else: + raise ValueError( + "cardinal authorization requires a registered cardinal operator" + ) + return self + + +class ContinuousYawAuthorization(HashBoundCanonicalModel): + """One continuous-yaw authorization with full-circle and zero rules closed.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/continuous-yaw-authorization/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "continuous_yaw_authorization_sha256" + + subject_id: CanonicalId + operator_ref: DefinitionRef + pivot_binding: FixedPivotBinding + yaw_domain: ContinuousYawDomain + continuous_yaw_authorization_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_continuous_authorization(self) -> Self: + if self.subject_id != self.pivot_binding.subject_id: + raise ValueError("authorization subject must match fixed pivot subject") + if self.operator_ref == UPRIGHT_SE2_CONTINUOUS_OWN_PIVOT_OPERATOR_REF: + if self.pivot_binding.pivot_mode is not PivotMode.OWN: + raise ValueError("own-pivot operator requires an own pivot") + elif self.operator_ref == UPRIGHT_SE2_CONTINUOUS_REFERENCE_PIVOT_OPERATOR_REF: + if self.pivot_binding.pivot_mode is not PivotMode.REFERENCE: + raise ValueError("reference-pivot operator requires a reference pivot") + if isinstance(self.yaw_domain, ContinuousYawFullCircle): + raise ValueError( + "full circle is valid only for continuous own-pivot authorization" + ) + if self.yaw_domain.contains_canonical_zero: + raise ValueError("reference-pivot arc must exclude canonical zero") + else: + raise ValueError( + "continuous authorization requires a registered continuous operator" + ) + return self + + +class UprightSE2ProfileRegistration(HashBoundCanonicalModel): + """One immutable semantic/action profile plus staged static capabilities.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/profile-registration/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "profile_registration_sha256" + + semantics_profile: SemanticsProfile + action_space_profile: ActionSpaceProfile + backend_owner_ref: OwnerRef + checker_owner_ref: OwnerRef + profile_capability_ref: CapabilityRef + cardinal_compiler_capability_ref: CapabilityRef + cardinal_backend_capability_ref: CapabilityRef + cardinal_checker_capability_ref: CapabilityRef + continuous_compiler_capability_ref: CapabilityRef + continuous_backend_capability_ref: CapabilityRef + continuous_checker_capability_ref: CapabilityRef + profile_registration_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_immutable_profile(self) -> Self: + if ( + self.semantics_profile.semantics_profile_ref + != UPRIGHT_SE2_SEMANTICS_PROFILE_REF + ): + raise ValueError("semantics profile reference must be fixed") + if ( + self.action_space_profile.action_space_profile_ref + != UPRIGHT_SE2_PROFILE_REF + ): + raise ValueError("action-space profile reference must be fixed") + if ( + self.semantics_profile.transition_semantics_refs + != UPRIGHT_SE2_OPERATOR_REFS + ): + raise ValueError( + "semantics profile must name exactly four upright operators" + ) + if self.action_space_profile.allowed_operator_refs != UPRIGHT_SE2_OPERATOR_REFS: + raise ValueError( + "action-space profile must name exactly four upright operators" + ) + if ( + self.action_space_profile.backend_capability_requirements + != UPRIGHT_SE2_STAGED_CAPABILITY_REFS + ): + raise ValueError( + "action-space profile must retain both staged capability sets" + ) + if self.backend_owner_ref != UPRIGHT_SE2_BACKEND_OWNER_REF: + raise ValueError("backend owner reference must be fixed") + if self.checker_owner_ref != UPRIGHT_SE2_CHECKER_OWNER_REF: + raise ValueError("checker owner reference must be fixed") + if self.backend_owner_ref == self.checker_owner_ref: + raise ValueError("backend and checker owners must be distinct") + expected_capabilities = ( + UPRIGHT_SE2_PROFILE_CAPABILITY_REF, + UPRIGHT_SE2_CARDINAL_COMPILER_CAPABILITY_REF, + UPRIGHT_SE2_CARDINAL_BACKEND_CAPABILITY_REF, + UPRIGHT_SE2_CARDINAL_CHECKER_CAPABILITY_REF, + UPRIGHT_SE2_CONTINUOUS_COMPILER_CAPABILITY_REF, + UPRIGHT_SE2_CONTINUOUS_BACKEND_CAPABILITY_REF, + UPRIGHT_SE2_CONTINUOUS_CHECKER_CAPABILITY_REF, + ) + actual_capabilities = ( + self.profile_capability_ref, + self.cardinal_compiler_capability_ref, + self.cardinal_backend_capability_ref, + self.cardinal_checker_capability_ref, + self.continuous_compiler_capability_ref, + self.continuous_backend_capability_ref, + self.continuous_checker_capability_ref, + ) + if actual_capabilities != expected_capabilities: + raise ValueError("profile capability references must be fixed") + return self + + +class UprightSE2SemanticOwnerBinding(CanonicalModel): + """The static evaluator/verifier ownership for one semantic definition.""" + + definition_ref: DefinitionRef + evaluator_capability_ref: CapabilityRef + verifier_capability_ref: CapabilityRef + evaluator_owner_ref: OwnerRef + verifier_owner_ref: OwnerRef + evaluator_build_sha256: Sha256Digest + verifier_build_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_static_owner_binding(self) -> Self: + if self.evaluator_owner_ref != UPRIGHT_SE2_BACKEND_OWNER_REF: + raise ValueError("semantic evaluator owner must be the upright backend") + if self.verifier_owner_ref != UPRIGHT_SE2_CHECKER_OWNER_REF: + raise ValueError("semantic verifier owner must be the upright checker") + if self.evaluator_build_sha256 != "b" * 64: + raise ValueError("semantic evaluator build must be fixed") + if self.verifier_build_sha256 != "a" * 64: + raise ValueError("semantic verifier build must be fixed") + expected_capabilities = _semantic_capabilities_for_definition( + self.definition_ref + ) + if ( + self.evaluator_capability_ref, + self.verifier_capability_ref, + ) != expected_capabilities: + raise ValueError("semantic evaluator/verifier capabilities must be fixed") + return self + + +class UprightSE2SemanticDefinition(HashBoundCanonicalModel): + """One typed, versioned predicate body with its executable ownership.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/semantic-definition/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "semantic_definition_sha256" + + definition_ref: DefinitionRef + definition_kind: Literal[ + "COLLISION", + "SUPPORT", + "TARGET_RELATION", + "PRESERVATION", + "VISIBILITY", + ] + payload_schema_ref: CanonicalId + semantic_body: TypedValue + owner_binding: UprightSE2SemanticOwnerBinding + semantic_definition_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_concrete_semantic_body(self) -> Self: + expected_ref = _semantic_definition_ref_for_kind(self.definition_kind) + if self.definition_ref != expected_ref: + raise ValueError( + "semantic definition reference does not match its body kind" + ) + expected_schema = _semantic_definition_schema_for_kind(self.definition_kind) + if self.payload_schema_ref != expected_schema: + raise ValueError("semantic definition schema does not match its body kind") + if self.owner_binding.definition_ref != self.definition_ref: + raise ValueError("semantic owner binding must bind its semantic definition") + if canonical_json_bytes(self.owner_binding) != canonical_json_bytes( + _semantic_owner_binding(self.definition_ref) + ): + raise ValueError( + "semantic definition owner binding does not match the frozen policy" + ) + expected_body = _semantic_definition_body(self.definition_kind) + if canonical_json_bytes(self.semantic_body) != canonical_json_bytes( + expected_body + ): + raise ValueError( + "semantic definition body does not match the frozen upright policy" + ) + return self + + +class UprightSE2ObjectiveTermPolicy(CanonicalModel): + """One concrete term of the fixed five-term M3 objective.""" + + term_id: Literal["T", "A", "R", "V", "S"] + objective_definition_ref: DefinitionRef + metric_definition_ref: DefinitionRef + input_selector_definition_ref: DefinitionRef + unit_ref: DefinitionRef + normalization_definition_ref: DefinitionRef + normalizer_unit_ref: DefinitionRef + weight: FiniteFloat + normalizer: FiniteFloat + + @model_validator(mode="after") + def _validate_term_policy(self) -> Self: + expected = _objective_term_policy_values(self.term_id) + for name, expected_value in expected.items(): + if getattr(self, name) != expected_value: + raise ValueError( + "objective term policy does not match the frozen upright policy" + ) + if not math.isfinite(self.weight) or self.weight < 0.0: + raise ValueError("objective weights must be finite and non-negative") + if self.term_id in ("T", "A") and self.weight <= 0.0: + raise ValueError( + "translation and angular objective weights must be positive" + ) + if not math.isfinite(self.normalizer) or self.normalizer <= 0.0: + raise ValueError( + "objective normalizers must be finite and strictly positive" + ) + if self.normalizer_unit_ref != self.unit_ref: + raise ValueError("objective normalizer units must match term units") + return self + + +class UprightSE2FiveTermObjectivePolicy(HashBoundCanonicalModel): + """The complete non-ambient objective, comparison, tie, and prune policy.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/five-term-objective-policy/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "five_term_objective_policy_sha256" + + objective_definition_ref: DefinitionRef + aggregation_definition_ref: DefinitionRef + safety_penalty_definition_ref: DefinitionRef + strict_interval_comparator_ref: DefinitionRef + exact_equality_definition_ref: DefinitionRef + directed_gap_subtraction_definition_ref: DefinitionRef + exact_prune_comparator_ref: DefinitionRef + gap_prune_comparator_ref: DefinitionRef + interval_boundary_policy_ref: DefinitionRef + deterministic_tie_break_definition_ref: DefinitionRef + terms: tuple[UprightSE2ObjectiveTermPolicy, ...] + owner_binding: UprightSE2SemanticOwnerBinding + five_term_objective_policy_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_complete_objective_policy(self) -> Self: + expected_refs = _objective_policy_refs() + for name, expected_value in expected_refs.items(): + if getattr(self, name) != expected_value: + raise ValueError( + "objective policy reference does not match the frozen upright policy" + ) + if tuple(term.term_id for term in self.terms) != ("T", "A", "R", "V", "S"): + raise ValueError("objective policy must contain ordered T/A/R/V/S terms") + if self.owner_binding.definition_ref != self.objective_definition_ref: + raise ValueError( + "objective owner binding must bind the objective definition" + ) + if canonical_json_bytes(self.owner_binding) != canonical_json_bytes( + _semantic_owner_binding(self.objective_definition_ref) + ): + raise ValueError("objective owner binding does not match the frozen policy") + return self + + @property + def weights_by_term(self) -> dict[str, float]: + """Return the explicit weight mapping for later pure evaluators.""" + + return {term.term_id: term.weight for term in self.terms} + + +class UprightSE2ExecutablePolicyValue(HashBoundCanonicalModel): + """One request-owned executable policy value for a registered M3 family. + + The profile owns the permitted families, schemas, and evaluator bindings. + This model deliberately owns none of the numeric or selector values: those + are carried by the request-specific ``payload`` and sealed with it. + """ + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/executable-policy-value/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "executable_policy_value_sha256" + + policy_key: CanonicalId + policy_family_ref: DefinitionRef + definition_ref: DefinitionRef + payload_schema_ref: CanonicalId + owner_binding: UprightSE2SemanticOwnerBinding + payload: TypedValue + executable_policy_value_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_registered_request_policy(self) -> Self: + expected = _executable_policy_spec(self.policy_key) + if ( + self.policy_family_ref, + self.definition_ref, + self.payload_schema_ref, + ) != ( + expected["policy_family_ref"], + expected["definition_ref"], + expected["payload_schema_ref"], + ): + raise ValueError("executable policy does not match a registered family") + if canonical_json_bytes(self.owner_binding) != canonical_json_bytes( + _semantic_owner_binding(self.definition_ref) + ): + raise ValueError("executable policy has an unowned evaluator binding") + _validate_executable_policy_payload(self.policy_key, self.payload) + return self + + +class UprightSE2ExecutablePolicyBundle(HashBoundCanonicalModel): + """The complete non-ambient executable-policy bundle bound by one request.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/executable-policy-bundle/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "policy_bundle_sha256" + + profile_registration_sha256: Sha256Digest + policies: tuple[UprightSE2ExecutablePolicyValue, ...] + policy_bundle_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_complete_request_policy_bundle(self) -> Self: + keys = tuple(policy.policy_key for policy in self.policies) + if keys != _EXECUTABLE_POLICY_KEYS: + raise ValueError( + "executable policy bundle must contain every policy exactly once" + ) + if len(set(keys)) != len(keys): + raise ValueError("executable policy bundle must not duplicate one policy") + return self + + def policy_for(self, policy_key: str) -> UprightSE2ExecutablePolicyValue: + """Return one policy only after the complete roster has been sealed.""" + + for policy in self.policies: + if policy.policy_key == policy_key: + return policy + raise ValueError("executable policy bundle is missing the requested policy") + + +class UprightSE2SemanticClosure(HashBoundCanonicalModel): + """The typed, hash-bound input closure consumed by every future M3 evaluator.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/semantic-closure/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "semantic_closure_sha256" + + profile_registration_sha256: Sha256Digest + profile_registration: UprightSE2ProfileRegistration + semantic_problem: CounterfactualProblemIR + definition_bundle: DefinitionBundle + predicate_definitions: tuple[UprightSE2SemanticDefinition, ...] + objective_policy: UprightSE2FiveTermObjectivePolicy + evaluator_bindings: tuple[UprightSE2SemanticOwnerBinding, ...] + grounded_obligations: GroundedObligationSet + objective_expression: ObjectiveExpression + executable_policy_bundle: UprightSE2ExecutablePolicyBundle + resource_policy: ResourcePolicy + semantic_closure_sha256: Sha256Digest + + @property + def definition_bundle_sha256(self) -> Sha256Digest: + """Expose the nested definition-bundle digest as a typed closure field.""" + + return self.definition_bundle.definition_bundle_sha256 + + @property + def grounded_obligation_set_sha256(self) -> Sha256Digest: + """Expose the nested grounded-obligation digest for evaluator replay.""" + + return self.grounded_obligations.grounded_obligation_set_sha256 + + @property + def objective_expression_sha256(self) -> Sha256Digest: + """Expose the nested objective-expression digest for evaluator replay.""" + + return self.objective_expression.objective_expression_sha256 + + @model_validator(mode="after") + def _validate_closed_semantics(self) -> Self: + if ( + self.profile_registration.profile_registration_sha256 + != self.profile_registration_sha256 + ): + raise ValueError( + "semantic closure profile digest does not bind its profile" + ) + _validate_task2_semantic_profile(self.profile_registration) + if ( + self.semantic_problem.semantics_profile_ref + != self.profile_registration.semantics_profile.semantics_profile_ref + or self.semantic_problem.action_space_profile_ref + != self.profile_registration.action_space_profile.action_space_profile_ref + ): + raise ValueError( + "semantic closure source problem does not bind its profile" + ) + expected_definitions = _upright_semantic_definitions() + if tuple( + canonical_json_bytes(value) for value in self.predicate_definitions + ) != tuple(canonical_json_bytes(value) for value in expected_definitions): + raise ValueError( + "semantic closure must contain every exact predicate definition" + ) + expected_policy = decode_upright_se2_objective_policy( + self.executable_policy_bundle + ) + if canonical_json_bytes(self.objective_policy) != canonical_json_bytes( + expected_policy + ): + raise ValueError( + "semantic closure must contain the exact five-term objective policy" + ) + expected_bindings = _upright_semantic_owner_bindings() + if self.evaluator_bindings != expected_bindings: + raise ValueError( + "semantic closure evaluator/verifier bindings do not close" + ) + expected_bundle = _semantic_definition_bundle_for_digest( + self.profile_registration_sha256, expected_policy + ) + if canonical_json_bytes(self.definition_bundle) != canonical_json_bytes( + expected_bundle + ): + raise ValueError("semantic closure definition envelopes do not close") + if canonical_json_bytes(self.objective_expression) != canonical_json_bytes( + build_upright_se2_objective_expression() + ): + raise ValueError("semantic closure objective expression does not close") + if canonical_json_bytes(self.definition_bundle) != canonical_json_bytes( + self.semantic_problem.definition_bundle + ): + raise ValueError( + "semantic closure must bind the source problem definition bundle" + ) + if canonical_json_bytes(self.objective_expression) != canonical_json_bytes( + self.semantic_problem.objective_expression + ): + raise ValueError( + "semantic closure must bind the source problem objective expression" + ) + expected_obligations = _bound_grounded_obligations(self.semantic_problem) + if canonical_json_bytes(self.grounded_obligations) != canonical_json_bytes( + expected_obligations + ): + raise ValueError( + "semantic closure grounded obligations must bind the complete source roster" + ) + if ( + self.executable_policy_bundle.profile_registration_sha256 + != self.profile_registration_sha256 + ): + raise ValueError( + "executable policy bundle does not bind the semantic profile" + ) + _validate_executable_policy_resource_binding( + self.executable_policy_bundle, + self.resource_policy, + ) + validate_upright_se2_executable_policy_visibility_binding( + self.executable_policy_bundle, + self.semantic_problem, + ) + return self + + @property + def policy_bundle_sha256(self) -> Sha256Digest: + """Expose the request-bound executable root to compiler consumers.""" + + return self.executable_policy_bundle.policy_bundle_sha256 + + +class UprightSE2ContinuousSemanticClosure(HashBoundCanonicalModel): + """The additive continuous-yaw semantic closure. + + It intentionally has a distinct hash domain and validation entrypoint. In + particular, the frozen cardinal closure is never widened to accept an ARC + or FULL_CIRCLE compiler-input wire. + """ + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/continuous-semantic-closure/1.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "semantic_closure_sha256" + + profile_registration_sha256: Sha256Digest + profile_registration: UprightSE2ProfileRegistration + semantic_problem: CounterfactualProblemIR + definition_bundle: DefinitionBundle + predicate_definitions: tuple[UprightSE2SemanticDefinition, ...] + objective_policy: UprightSE2FiveTermObjectivePolicy + evaluator_bindings: tuple[UprightSE2SemanticOwnerBinding, ...] + grounded_obligations: GroundedObligationSet + objective_expression: ObjectiveExpression + executable_policy_bundle: UprightSE2ExecutablePolicyBundle + resource_policy: ResourcePolicy + semantic_closure_sha256: Sha256Digest + + @property + def definition_bundle_sha256(self) -> Sha256Digest: + return self.definition_bundle.definition_bundle_sha256 + + @property + def grounded_obligation_set_sha256(self) -> Sha256Digest: + return self.grounded_obligations.grounded_obligation_set_sha256 + + @property + def objective_expression_sha256(self) -> Sha256Digest: + return self.objective_expression.objective_expression_sha256 + + @model_validator(mode="after") + def _validate_closed_semantics(self) -> Self: + if ( + self.profile_registration.profile_registration_sha256 + != self.profile_registration_sha256 + ): + raise ValueError( + "continuous semantic closure profile digest does not bind its profile" + ) + _validate_task2_semantic_profile(self.profile_registration) + if ( + self.semantic_problem.semantics_profile_ref + != self.profile_registration.semantics_profile.semantics_profile_ref + or self.semantic_problem.action_space_profile_ref + != self.profile_registration.action_space_profile.action_space_profile_ref + ): + raise ValueError( + "continuous semantic closure source problem does not bind its profile" + ) + expected_definitions = _upright_semantic_definitions() + if tuple( + canonical_json_bytes(value) for value in self.predicate_definitions + ) != tuple(canonical_json_bytes(value) for value in expected_definitions): + raise ValueError( + "continuous semantic closure must contain every exact predicate definition" + ) + expected_policy = decode_upright_se2_objective_policy( + self.executable_policy_bundle + ) + if canonical_json_bytes(self.objective_policy) != canonical_json_bytes( + expected_policy + ): + raise ValueError( + "continuous semantic closure must contain the exact five-term objective policy" + ) + if self.evaluator_bindings != _upright_semantic_owner_bindings(): + raise ValueError( + "continuous semantic closure evaluator/verifier bindings do not close" + ) + expected_bundle = _semantic_definition_bundle_for_digest( + self.profile_registration_sha256, expected_policy + ) + if canonical_json_bytes(self.definition_bundle) != canonical_json_bytes( + expected_bundle + ): + raise ValueError( + "continuous semantic closure definition envelopes do not close" + ) + if canonical_json_bytes(self.objective_expression) != canonical_json_bytes( + build_upright_se2_objective_expression() + ): + raise ValueError( + "continuous semantic closure objective expression does not close" + ) + if canonical_json_bytes(self.definition_bundle) != canonical_json_bytes( + self.semantic_problem.definition_bundle + ) or canonical_json_bytes(self.objective_expression) != canonical_json_bytes( + self.semantic_problem.objective_expression + ): + raise ValueError( + "continuous semantic closure must bind the source semantic roots" + ) + expected_obligations = _bound_continuous_grounded_obligations( + self.semantic_problem + ) + if canonical_json_bytes(self.grounded_obligations) != canonical_json_bytes( + expected_obligations + ): + raise ValueError( + "continuous semantic closure grounded obligations must bind the complete source roster" + ) + if ( + self.executable_policy_bundle.profile_registration_sha256 + != self.profile_registration_sha256 + ): + raise ValueError( + "continuous executable policy bundle does not bind the semantic profile" + ) + _validate_executable_policy_resource_binding( + self.executable_policy_bundle, + self.resource_policy, + ) + validate_upright_se2_executable_policy_visibility_binding( + self.executable_policy_bundle, + self.semantic_problem, + ) + return self + + @property + def policy_bundle_sha256(self) -> Sha256Digest: + return self.executable_policy_bundle.policy_bundle_sha256 + + +_SEMANTIC_ID_SCHEMA_REF = "schema:spatialcf/upright-se2/semantic-id/1.0" +_SEMANTIC_SYMBOL_SCHEMA_REF = "schema:spatialcf/upright-se2/semantic-symbol/1.0" +_SEMANTIC_REAL_SCHEMA_REF = "schema:spatialcf/upright-se2/semantic-real/1.0" +_SEMANTIC_TUPLE_SCHEMA_REF = "schema:spatialcf/upright-se2/semantic-tuple/1.0" +UPRIGHT_SE2_SEMANTIC_CLOSURE_DEFINITION_REF = ( + "definition:spatialcf/upright-se2/semantic-definition-closure/1.0" +) +_SEMANTIC_DEFINITION_KIND_REF = ( + "definition:spatialcf/upright-se2/semantic-definition-body/1.0" +) +_OBJECTIVE_DEFINITION_KIND_REF = ( + "definition:spatialcf/upright-se2/five-term-objective-body/1.0" +) +_SEMANTIC_CLOSURE_KIND_REF = ( + "definition:spatialcf/upright-se2/semantic-closure-body/1.0" +) +_SEMANTIC_CLOSURE_SCHEMA_REF = ( + "schema:spatialcf/upright-se2/semantic-definition-closure/1.0" +) +_OBJECTIVE_POLICY_SCHEMA_REF = ( + "schema:spatialcf/upright-se2/five-term-objective-policy/1.0" +) + +_EXECUTABLE_POLICY_FAMILY_REF = "definition:spatialcf/upright-se2/executable-policy/1.0" +_EXECUTABLE_POLICY_WIRE_SCHEMA_REF = ( + "schema:spatialcf/upright-se2/executable-policy-wire/1.0" +) +_EXECUTABLE_POLICY_BUNDLE_SCHEMA_REF = ( + "schema:spatialcf/upright-se2/executable-policy-bundle/1.0" +) +_EXECUTABLE_POLICY_BUNDLE_FACT_KEY = ( + "fact-key:spatialcf/upright-se2/executable-policy-bundle" +) +_EXECUTABLE_POLICY_KEYS = ( + "collision", + "numeric", + "objective", + "preservation", + "relation:BEHIND", + "relation:FAR", + "relation:FRONT", + "relation:LEFT", + "relation:NEAR", + "relation:RIGHT", + "resource", + "safety", + "support", + "visibility", +) +_EXECUTABLE_POLICY_SCHEMA_BY_KEY = { + key: f"schema:spatialcf/upright-se2/executable-{key.replace(':', '-').lower()}-policy/1.0" + for key in _EXECUTABLE_POLICY_KEYS +} + +_SEMANTIC_KIND_TO_REF = { + "COLLISION": UPRIGHT_SE2_COLLISION_PREDICATE_REF, + "SUPPORT": UPRIGHT_SE2_SUPPORT_PREDICATE_REF, + "TARGET_RELATION": UPRIGHT_SE2_TARGET_RELATION_PREDICATE_REF, + "PRESERVATION": UPRIGHT_SE2_PRESERVATION_PREDICATE_REF, + "VISIBILITY": UPRIGHT_SE2_VISIBILITY_PREDICATE_REF, +} +_SEMANTIC_KIND_TO_SCHEMA = { + kind: f"schema:spatialcf/upright-se2/{kind.lower()}-semantics/1.0" + for kind in _SEMANTIC_KIND_TO_REF +} +_SEMANTIC_BODY_VALUES = { + "COLLISION": ( + ( + "clearance_measurement_ref", + "definition:spatialcf/upright-se2/collision-clearance/1.0", + ), + ( + "contact_comparator_ref", + "definition:spatialcf/upright-se2/collision-contact-comparator/1.0", + ), + ( + "contact_boundary_policy_ref", + "definition:spatialcf/upright-se2/collision-closed-contact-boundary/1.0", + ), + ( + "evaluation_frame_ref", + "definition:spatialcf/upright-se2/world-xy/1.0", + ), + ( + "evaluation_input_schema_ref", + "schema:spatialcf/upright-se2/collision-evaluation-input/1.0", + ), + ( + "collision_evaluator_ref", + "definition:spatialcf/upright-se2/collision-evaluator/1.0", + ), + ( + "obstacle_roster_selector_ref", + "definition:spatialcf/upright-se2/collision-complete-obstacle-roster/1.0", + ), + ( + "source_fact_completeness_ref", + "definition:spatialcf/upright-se2/collision-source-exactness/1.0", + ), + ( + "subject_geometry_selector_ref", + "definition:spatialcf/upright-se2/collision-subject-geometry/1.0", + ), + ), + "SUPPORT": ( + ( + "clearance_policy_ref", + "definition:spatialcf/upright-se2/support-clearance/1.0", + ), + ( + "evaluation_input_schema_ref", + "schema:spatialcf/upright-se2/support-evaluation-input/1.0", + ), + ( + "containment_boundary_policy_ref", + "definition:spatialcf/upright-se2/support-contained-closed-boundary/1.0", + ), + ( + "height_contact_comparator_ref", + "definition:spatialcf/upright-se2/support-height-contact-comparator/1.0", + ), + ( + "same_surface_comparator_ref", + "definition:spatialcf/upright-se2/support-same-surface-comparator/1.0", + ), + ( + "source_fact_completeness_ref", + "definition:spatialcf/upright-se2/support-source-exactness/1.0", + ), + ( + "support_surface_selector_ref", + "definition:spatialcf/upright-se2/support-required-surface/1.0", + ), + ( + "surface_frame_requirement_ref", + "definition:spatialcf/upright-se2/support-world-plus-z-frame/1.0", + ), + ( + "surface_normal_requirement_ref", + "definition:spatialcf/upright-se2/support-world-plus-z-normal/1.0", + ), + ), + "TARGET_RELATION": ( + ( + "after_state_geometry_selector_ref", + "definition:spatialcf/upright-se2/relation-after-state-geometry/1.0", + ), + ( + "evaluation_input_schema_ref", + "schema:spatialcf/upright-se2/target-relation-evaluation-input/1.0", + ), + ( + "relation_boundary_policy_ref", + "definition:spatialcf/upright-se2/relation-closed-boundary/1.0", + ), + ( + "relation_comparator_ref", + "definition:spatialcf/upright-se2/relation-comparator/1.0", + ), + ( + "relation_frame_ref", + "definition:spatialcf/upright-se2/relation-fixed-camera-frame/1.0", + ), + ( + "relation_tolerance_policy_ref", + "definition:spatialcf/upright-se2/relation-tolerance/1.0", + ), + ( + "target_relation_selector_ref", + "definition:spatialcf/upright-se2/relation-target-selector/1.0", + ), + ( + "source_fact_completeness_ref", + "definition:spatialcf/upright-se2/relation-source-exactness/1.0", + ), + ), + "PRESERVATION": ( + ( + "after_state_selector_ref", + "definition:spatialcf/upright-se2/preservation-after-state/1.0", + ), + ( + "grounded_operand_policy_ref", + "definition:spatialcf/upright-se2/preservation-grounded-operands/1.0", + ), + ( + "before_state_selector_ref", + "definition:spatialcf/upright-se2/preservation-before-state/1.0", + ), + ( + "complete_state_policy_ref", + "definition:spatialcf/upright-se2/complete-state-delta/1.0", + ), + ( + "frozen_leaf_policy_ref", + "definition:spatialcf/upright-se2/preservation-frozen-leaves/1.0", + ), + ( + "preservation_evaluator_ref", + "definition:spatialcf/upright-se2/preservation-evaluator/1.0", + ), + ( + "transition_comparator_ref", + "definition:spatialcf/upright-se2/preservation-transition-comparator/1.0", + ), + ), + "VISIBILITY": ( + ( + "camera_selector_ref", + "definition:spatialcf/upright-se2/visibility-fixed-camera-selector/1.0", + ), + ( + "evaluation_input_schema_ref", + "schema:spatialcf/upright-se2/visibility-evaluation-input/1.0", + ), + ( + "fixed_camera_policy_ref", + "definition:spatialcf/upright-se2/visibility-fixed-camera-policy/1.0", + ), + ( + "metric_definition_ref", + "definition:spatialcf/upright-se2/visibility-metric/1.0", + ), + ( + "occluder_roster_completeness_ref", + "definition:spatialcf/upright-se2/visibility-complete-occluder-roster/1.0", + ), + ( + "threshold_comparator_ref", + "definition:spatialcf/upright-se2/visibility-threshold-comparator/1.0", + ), + ( + "threshold_definition_ref", + "definition:spatialcf/upright-se2/visibility-threshold/1.0", + ), + ( + "visibility_boundary_policy_ref", + "definition:spatialcf/upright-se2/visibility-closed-boundary/1.0", + ), + ( + "visibility_evaluator_ref", + "definition:spatialcf/upright-se2/visibility-evaluator/1.0", + ), + ), +} + + +def _semantic_definition_ref_for_kind(kind: str) -> DefinitionRef: + return _SEMANTIC_KIND_TO_REF[kind] + + +def _semantic_definition_schema_for_kind(kind: str) -> CanonicalId: + return _SEMANTIC_KIND_TO_SCHEMA[kind] + + +def _semantic_capabilities_for_definition( + definition_ref: DefinitionRef, +) -> tuple[CapabilityRef, CapabilityRef]: + if definition_ref == UPRIGHT_SE2_OBJECTIVE_DEFINITION_REF: + return ( + UPRIGHT_SE2_OBJECTIVE_EVALUATOR_CAPABILITY_REF, + UPRIGHT_SE2_OBJECTIVE_VERIFIER_CAPABILITY_REF, + ) + if definition_ref in UPRIGHT_SE2_PREDICATE_DEFINITION_REFS: + return ( + UPRIGHT_SE2_PREDICATE_EVALUATOR_CAPABILITY_REF, + UPRIGHT_SE2_PREDICATE_VERIFIER_CAPABILITY_REF, + ) + raise ValueError( + "semantic owner binding must name a registered semantic definition" + ) + + +def _semantic_typed_id(value: str) -> TypedValue: + return TypedValue( + value_schema_ref=_SEMANTIC_ID_SCHEMA_REF, + payload=CanonicalIdValue(value=value), + ) + + +def _semantic_typed_symbol(value: str) -> TypedValue: + return TypedValue( + value_schema_ref=_SEMANTIC_SYMBOL_SCHEMA_REF, + payload=EnumSymbolValue(symbol=value), + ) + + +def _semantic_typed_real(value: float) -> TypedValue: + return TypedValue( + value_schema_ref=_SEMANTIC_REAL_SCHEMA_REF, + payload=FiniteRealValue(value=value), + ) + + +def _semantic_typed_digest(value: str) -> TypedValue: + return TypedValue( + value_schema_ref="schema:spatialcf/upright-se2/digest/1.0", + payload=DigestValue(value=value), + ) + + +def _semantic_typed_tuple(values: tuple[str, ...]) -> TypedValue: + return TypedValue( + value_schema_ref=_SEMANTIC_TUPLE_SCHEMA_REF, + payload=FiniteOrderedTupleValue( + element_schema_ref=_SEMANTIC_ID_SCHEMA_REF, + items=tuple(_semantic_typed_id(value) for value in values), + ), + ) + + +def _semantic_typed_digest_tuple(values: tuple[str, ...]) -> TypedValue: + return TypedValue( + value_schema_ref="schema:spatialcf/upright-se2/semantic-digest-tuple/1.0", + payload=FiniteOrderedTupleValue( + element_schema_ref="schema:spatialcf/upright-se2/digest/1.0", + items=tuple(_semantic_typed_digest(value) for value in values), + ), + ) + + +def _semantic_typed_record( + schema_ref: str, + fields: tuple[tuple[str, TypedValue], ...], +) -> TypedValue: + return TypedValue( + value_schema_ref=schema_ref, + payload=RecordValue( + fields=tuple( + sorted( + (NamedTypedValue(name=name, value=value) for name, value in fields), + key=canonical_json_bytes, + ) + ) + ), + ) + + +def _semantic_definition_body(kind: str) -> TypedValue: + schema_ref = _semantic_definition_schema_for_kind(kind) + return _semantic_typed_record( + schema_ref, + ( + ("definition_kind", _semantic_typed_symbol(kind)), + ( + "operand_schema_refs", + _semantic_typed_tuple(_semantic_operand_schemas(kind)), + ), + *( + (name, _semantic_typed_id(value)) + for name, value in _SEMANTIC_BODY_VALUES[kind] + ), + ), + ) + + +def _semantic_operand_schemas(kind: str) -> tuple[str, ...]: + if kind == "TARGET_RELATION": + return ( + "schema:spatialcf/upright-se2/object-ref/1.0", + "schema:spatialcf/upright-se2/object-ref/1.0", + "schema:spatialcf/upright-se2/relation-symbol/1.0", + "schema:spatialcf/upright-se2/phase-symbol/1.0", + ) + if kind == "PRESERVATION": + return ( + "schema:spatialcf/upright-se2/entity-ref/1.0", + "schema:spatialcf/upright-se2/preservation-selector/1.0", + "schema:spatialcf/upright-se2/phase-symbol/1.0", + ) + if kind == "VISIBILITY": + return ( + "schema:spatialcf/upright-se2/camera-ref/1.0", + "schema:spatialcf/upright-se2/object-ref/1.0", + "schema:spatialcf/upright-se2/visibility-metric-symbol/1.0", + "schema:spatialcf/upright-se2/observation-ref/1.0", + "schema:spatialcf/upright-se2/phase-symbol/1.0", + ) + return () + + +def _semantic_owner_binding( + definition_ref: DefinitionRef, +) -> UprightSE2SemanticOwnerBinding: + evaluator_capability_ref, verifier_capability_ref = ( + _semantic_capabilities_for_definition(definition_ref) + ) + return UprightSE2SemanticOwnerBinding( + definition_ref=definition_ref, + evaluator_capability_ref=evaluator_capability_ref, + verifier_capability_ref=verifier_capability_ref, + evaluator_owner_ref=UPRIGHT_SE2_BACKEND_OWNER_REF, + verifier_owner_ref=UPRIGHT_SE2_CHECKER_OWNER_REF, + evaluator_build_sha256="b" * 64, + verifier_build_sha256="a" * 64, + ) + + +def _executable_policy_spec(policy_key: str) -> dict[str, str]: + """Return profile-owned family metadata for one request-owned policy key.""" + + if policy_key not in _EXECUTABLE_POLICY_SCHEMA_BY_KEY: + raise ValueError("executable policy key is unknown") + if policy_key == "collision": + definition_ref = UPRIGHT_SE2_COLLISION_PREDICATE_REF + elif policy_key == "support": + definition_ref = UPRIGHT_SE2_SUPPORT_PREDICATE_REF + elif policy_key.startswith("relation:"): + definition_ref = UPRIGHT_SE2_TARGET_RELATION_PREDICATE_REF + elif policy_key == "visibility": + definition_ref = UPRIGHT_SE2_VISIBILITY_PREDICATE_REF + elif policy_key == "preservation": + definition_ref = UPRIGHT_SE2_PRESERVATION_PREDICATE_REF + else: + # Numeric, resource, and safety determine objective/search accounting; + # their evaluator owner is the registered objective authority. + definition_ref = UPRIGHT_SE2_OBJECTIVE_DEFINITION_REF + return { + "policy_family_ref": _EXECUTABLE_POLICY_FAMILY_REF, + "definition_ref": definition_ref, + "payload_schema_ref": _EXECUTABLE_POLICY_SCHEMA_BY_KEY[policy_key], + } + + +def _policy_payload_fields( + policy_key: str, + payload: TypedValue, +) -> dict[str, TypedValue]: + expected_schema = _executable_policy_spec(policy_key)["payload_schema_ref"] + if ( + payload.value_schema_ref != expected_schema + or type(payload.payload) is not RecordValue + ): + raise ValueError( + "executable policy payload must use its registered record schema" + ) + fields = {field.name: field.value for field in payload.payload.fields} + if len(fields) != len(payload.payload.fields): + raise ValueError("executable policy payload contains duplicate fields") + return fields + + +def _require_policy_fields( + policy_key: str, + fields: dict[str, TypedValue], + expected: tuple[str, ...], +) -> None: + if set(fields) != set(expected): + raise ValueError( + f"executable policy {policy_key!r} fields are incomplete or unknown" + ) + + +def _policy_real(fields: dict[str, TypedValue], name: str) -> float: + value = fields[name] + if ( + value.value_schema_ref + not in (_SEMANTIC_REAL_SCHEMA_REF, _UPRIGHT_SE2_REAL_SCHEMA_REF) + or type(value.payload) is not FiniteRealValue + ): + raise ValueError(f"executable policy field {name!r} must be a finite real") + return value.payload.value + + +def _policy_integer(fields: dict[str, TypedValue], name: str) -> int: + value = fields[name] + if ( + value.value_schema_ref != "schema:spatialcf/upright-se2/integer/1.0" + or type(value.payload) is not IntegerValue + ): + raise ValueError(f"executable policy field {name!r} must be an integer") + return value.payload.value + + +def _policy_symbol(fields: dict[str, TypedValue], name: str) -> str: + value = fields[name] + if ( + value.value_schema_ref + not in ( + _SEMANTIC_SYMBOL_SCHEMA_REF, + "schema:spatialcf/upright-se2/enum-symbol/1.0", + ) + or type(value.payload) is not EnumSymbolValue + ): + raise ValueError(f"executable policy field {name!r} must be a symbol") + return value.payload.symbol + + +def _policy_id(fields: dict[str, TypedValue], name: str) -> str: + value = fields[name] + if ( + value.value_schema_ref + not in (_SEMANTIC_ID_SCHEMA_REF, _UPRIGHT_SE2_ID_SCHEMA_REF) + or type(value.payload) is not CanonicalIdValue + ): + raise ValueError(f"executable policy field {name!r} must be an identifier") + return value.payload.value + + +def _policy_digest(fields: dict[str, TypedValue], name: str) -> str: + value = fields[name] + if ( + value.value_schema_ref != "schema:spatialcf/upright-se2/digest/1.0" + or type(value.payload) is not DigestValue + ): + raise ValueError(f"executable policy field {name!r} must be a digest") + return value.payload.value + + +def _policy_tuple(fields: dict[str, TypedValue], name: str) -> FiniteOrderedTupleValue: + value = fields[name] + if type(value.payload) is not FiniteOrderedTupleValue: + raise ValueError(f"executable policy field {name!r} must be an ordered tuple") + return value.payload + + +def _policy_id_tuple(fields: dict[str, TypedValue], name: str) -> tuple[str, ...]: + value = fields[name] + if ( + value.value_schema_ref + != "schema:spatialcf/upright-se2/resource-limit-values/1.0" + or type(value.payload) is not FiniteOrderedTupleValue + or value.payload.element_schema_ref + != "schema:spatialcf/upright-se2/policy-item/1.0" + ): + raise ValueError( + f"executable policy field {name!r} must be an exact identifier tuple" + ) + identifiers: list[str] = [] + for item in value.payload.items: + if ( + item.value_schema_ref + not in (_SEMANTIC_ID_SCHEMA_REF, _UPRIGHT_SE2_ID_SCHEMA_REF) + or type(item.payload) is not CanonicalIdValue + ): + raise ValueError( + f"executable policy field {name!r} must be an exact identifier tuple" + ) + identifiers.append(item.payload.value) + return tuple(identifiers) + + +def _visibility_observation_bound_fields(value: TypedValue) -> dict[str, TypedValue]: + if ( + value.value_schema_ref + != "schema:spatialcf/upright-se2/visibility-observation-bound/1.0" + or type(value.payload) is not RecordValue + ): + raise ValueError( + "executable visibility observation bounds must use typed bound records" + ) + fields = {field.name: field.value for field in value.payload.fields} + if len(fields) != len(value.payload.fields) or set(fields) != { + "observation_id", + "camera_id", + "object_id", + "metric_definition_id", + "metric_definition_version", + "comparator", + "boundary_policy", + "threshold", + "tolerance", + }: + raise ValueError( + "executable visibility observation bound fields are incomplete or unknown" + ) + return fields + + +def _validate_visibility_observation_bounds(fields: dict[str, TypedValue]) -> None: + value = fields["observation_bounds"] + if ( + value.value_schema_ref + != "schema:spatialcf/upright-se2/visibility-observation-bounds/1.0" + or type(value.payload) is not FiniteOrderedTupleValue + or value.payload.element_schema_ref + != "schema:spatialcf/upright-se2/visibility-observation-bound/1.0" + ): + raise ValueError( + "executable visibility policy must bind an exact typed observation roster" + ) + observation_ids: list[str] = [] + for item in value.payload.items: + bound = _visibility_observation_bound_fields(item) + observation_ids.append(_policy_id(bound, "observation_id")) + _policy_id(bound, "camera_id") + _policy_id(bound, "object_id") + metric_pair = ( + _policy_symbol(bound, "metric_definition_id"), + _policy_id(bound, "metric_definition_version"), + ) + if metric_pair not in { + ("visibility:image-area-fraction", "definition:1"), + ("visibility:truncated-fraction", "definition:1"), + ("visibility:visible-surface-fraction", "definition:1"), + }: + raise ValueError("executable visibility observation metric is unsupported") + for name, expected in ( + ("comparator", _policy_symbol(fields, "comparator")), + ("boundary_policy", _policy_symbol(fields, "boundary_policy")), + ): + if _policy_symbol(bound, name) != expected: + raise ValueError( + f"executable visibility observation bound {name!r} is inconsistent" + ) + for name in ("threshold", "tolerance"): + bound_value = _policy_real(bound, name) + if not 0.0 <= bound_value <= 1.0: + raise ValueError( + f"executable visibility observation bound {name} must be in [0, 1]" + ) + if not observation_ids or len(set(observation_ids)) != len(observation_ids): + raise ValueError( + "executable visibility observation bounds must be nonempty and unique" + ) + if tuple(observation_ids) != tuple( + sorted(observation_ids, key=canonical_json_bytes) + ): + raise ValueError("executable visibility observation bounds must be ordered") + + +def _decode_upright_se2_objective_policy_payload( + payload: TypedValue, +) -> UprightSE2FiveTermObjectivePolicy: + fields = _policy_payload_fields("objective", payload) + _require_policy_fields( + "objective", + fields, + ( + "aggregation_rule", + "comparison_rule", + "objective_policy_sha256", + "terms", + "tie_break_rule", + ), + ) + _require_policy_values( + fields, + { + "aggregation_rule": "WEIGHTED_NORMALIZED_SUM", + "comparison_rule": "INTERVAL_LEXICOGRAPHIC", + "tie_break_rule": "T_R_V_S_A", + }, + ) + terms_value = fields["terms"] + if ( + terms_value.value_schema_ref + != "schema:spatialcf/upright-se2/objective-term-values/1.0" + or type(terms_value.payload) is not FiniteOrderedTupleValue + or terms_value.payload.element_schema_ref + != "schema:spatialcf/upright-se2/objective-term-value/1.0" + ): + raise ValueError( + "executable objective policy terms must use the exact typed roster" + ) + terms: list[UprightSE2ObjectiveTermPolicy] = [] + for item in terms_value.payload.items: + if ( + item.value_schema_ref + != "schema:spatialcf/upright-se2/objective-term-value/1.0" + or type(item.payload) is not RecordValue + ): + raise ValueError( + "executable objective policy term must use its typed record" + ) + term_fields = {field.name: field.value for field in item.payload.fields} + if len(term_fields) != len(item.payload.fields) or set(term_fields) != { + "term_id", + "objective_definition_ref", + "metric_definition_ref", + "input_selector_definition_ref", + "unit_ref", + "normalization_definition_ref", + "normalizer_unit_ref", + "weight", + "normalizer", + }: + raise ValueError( + "executable objective policy term fields are incomplete or unknown" + ) + terms.append( + UprightSE2ObjectiveTermPolicy( + term_id=_policy_symbol(term_fields, "term_id"), + objective_definition_ref=_policy_id( + term_fields, "objective_definition_ref" + ), + metric_definition_ref=_policy_id(term_fields, "metric_definition_ref"), + input_selector_definition_ref=_policy_id( + term_fields, "input_selector_definition_ref" + ), + unit_ref=_policy_id(term_fields, "unit_ref"), + normalization_definition_ref=_policy_id( + term_fields, "normalization_definition_ref" + ), + normalizer_unit_ref=_policy_id(term_fields, "normalizer_unit_ref"), + weight=_policy_real(term_fields, "weight"), + normalizer=_policy_real(term_fields, "normalizer"), + ) + ) + policy = UprightSE2FiveTermObjectivePolicy.seal( + **_objective_policy_refs(), + terms=tuple(terms), + owner_binding=_semantic_owner_binding(UPRIGHT_SE2_OBJECTIVE_DEFINITION_REF), + ) + if _policy_digest(fields, "objective_policy_sha256") != ( + policy.five_term_objective_policy_sha256 + ): + raise ValueError("executable objective policy self digest does not match") + return policy + + +def _validate_executable_policy_payload(policy_key: str, payload: TypedValue) -> None: + """Validate every executable family without substituting a value or default.""" + + fields = _policy_payload_fields(policy_key, payload) + if policy_key == "collision": + _require_policy_fields( + policy_key, + fields, + ( + "boundary_policy", + "clearance_m", + "contact_comparator", + "obstacle_selector", + "subject_geometry_selector", + ), + ) + if _policy_real(fields, "clearance_m") < 0.0: + raise ValueError( + "executable collision policy clearance must be non-negative" + ) + _require_policy_values( + fields, + { + "contact_comparator": "ALLOW_EQUALITY", + "boundary_policy": "CLOSED", + }, + ) + _require_policy_ids( + fields, + { + "obstacle_selector": "selector:complete-obstacle-roster", + "subject_geometry_selector": "selector:compound-subject-geometry", + }, + ) + return + if policy_key == "support": + _require_policy_fields( + policy_key, + fields, + ( + "contact_gap_lower_m", + "contact_gap_upper_m", + "containment_boundary_policy", + "containment_comparator", + "normal_selector", + "stability_margin_m", + "support_frame_selector", + "support_surface_selector", + ), + ) + if _policy_real(fields, "contact_gap_lower_m") > _policy_real( + fields, "contact_gap_upper_m" + ): + raise ValueError( + "executable support policy contact-gap interval is reversed" + ) + if _policy_real(fields, "stability_margin_m") < 0.0: + raise ValueError( + "executable support policy stability margin must be non-negative" + ) + _require_policy_ids( + fields, + { + "normal_selector": "selector:world-positive-z", + "support_frame_selector": "selector:world-xy", + "support_surface_selector": "selector:assigned-support-surface", + }, + ) + _require_policy_values( + fields, + { + "containment_boundary_policy": "CLOSED", + "containment_comparator": "CONTAINS", + }, + ) + return + if policy_key.startswith("relation:"): + _require_policy_fields( + policy_key, + fields, + ( + "boundary_policy", + "comparator", + "fixed_camera_selector", + "frame_selector", + "measurement", + "operand_order", + "relation_symbol", + "representative_geometry", + "threshold", + "tolerance", + "visibility_gate", + ), + ) + if _policy_symbol(fields, "relation_symbol") != policy_key.removeprefix( + "relation:" + ): + raise ValueError("executable relation policy has the wrong relation symbol") + relation = policy_key.removeprefix("relation:") + _require_policy_values( + fields, + { + "boundary_policy": "CLOSED", + "comparator": "LE" if relation in {"LEFT", "FRONT", "NEAR"} else "GE", + "measurement": ( + "EXTENT_EUCLIDEAN_SEPARATION" + if relation in {"NEAR", "FAR"} + else "EXTENT_SIGNED_AXIS_GAP" + ), + "operand_order": "SUBJECT_THEN_REFERENCE", + "representative_geometry": "COMPOUND_BODY", + "visibility_gate": "NONE", + }, + ) + _require_policy_ids( + fields, + { + "fixed_camera_selector": "selector:fixed-camera", + "frame_selector": "selector:world-xy", + }, + ) + if _policy_real(fields, "threshold") < 0.0: + raise ValueError( + "executable relation policy threshold must be non-negative" + ) + if _policy_real(fields, "tolerance") < 0.0: + raise ValueError( + "executable relation policy tolerance must be non-negative" + ) + return + if policy_key == "visibility": + _require_policy_fields( + policy_key, + fields, + ( + "boundary_policy", + "camera_projection_convention", + "comparator", + "depth_policy", + "mask_policy", + "observation_bounds", + "occluder_policy", + "projected_area_metric", + "subject_as_occluder", + "threshold", + "tolerance", + ), + ) + expected_symbols = { + "boundary_policy": "CLOSED", + "camera_projection_convention": "UPRIGHT_CAMERA_V2_9", + "comparator": "GEQ", + "depth_policy": "NEAR_CLIPPED", + "mask_policy": "COMPLETE_MASK", + "occluder_policy": "COMPLETE_ROSTER", + "projected_area_metric": "PROJECTED_AREA", + "subject_as_occluder": "INCLUDED", + } + for name, expected in expected_symbols.items(): + if _policy_symbol(fields, name) != expected: + raise ValueError( + f"executable visibility policy field {name!r} is unsupported" + ) + _validate_visibility_observation_bounds(fields) + if not _policy_tuple(fields, "observation_bounds").items: + raise ValueError( + "executable visibility policy must bind observation bounds" + ) + for name in ("threshold", "tolerance"): + value = _policy_real(fields, name) + if not 0.0 <= value <= 1.0: + raise ValueError( + f"executable visibility policy {name} must be in [0, 1]" + ) + return + if policy_key == "numeric": + _require_policy_fields( + policy_key, + fields, + ( + "dyadic_refinement_policy", + "exact_number_representation", + "numeric_semantics_ref", + "tolerance_m", + ), + ) + _require_policy_ids( + fields, + { + "numeric_semantics_ref": "definition:spatialcf/upright-se2/numeric-semantics/1.0" + }, + ) + if _policy_real(fields, "tolerance_m") < 0.0: + raise ValueError("executable numeric policy tolerance must be non-negative") + _require_policy_values( + fields, + { + "exact_number_representation": "BINARY64_BITS", + "dyadic_refinement_policy": "EXACT_DYADIC", + }, + ) + return + if policy_key == "objective": + _require_policy_fields( + policy_key, + fields, + ( + "aggregation_rule", + "comparison_rule", + "objective_policy_sha256", + "terms", + "tie_break_rule", + ), + ) + _decode_upright_se2_objective_policy_payload(payload) + return + if policy_key == "safety": + _require_policy_fields( + policy_key, + fields, + ( + "constraint_slack_target", + "hard_constraint_selector", + "safety_penalty_rule", + ), + ) + if _policy_real(fields, "constraint_slack_target") < 0.0: + raise ValueError( + "executable safety policy slack target must be non-negative" + ) + _require_policy_ids( + fields, + {"hard_constraint_selector": "selector:all-hard-constraints"}, + ) + _require_policy_values( + fields, + {"safety_penalty_rule": "FROM_CONSTRAINT_SLACK"}, + ) + return + if policy_key == "resource": + _require_policy_fields( + policy_key, + fields, + ( + "atomic_step_limit", + "deterministic_order", + "limits", + "resource_policy_sha256", + "shared_ledger_policy_ref", + ), + ) + atomic_step_limit = _policy_integer(fields, "atomic_step_limit") + if atomic_step_limit < 0: + raise ValueError( + "executable resource policy atomic step limit must be non-negative" + ) + if not _policy_id_tuple(fields, "limits"): + raise ValueError("executable resource policy must bind every request limit") + _policy_digest(fields, "resource_policy_sha256") + _policy_id(fields, "shared_ledger_policy_ref") + _require_policy_values( + fields, + {"deterministic_order": "LOWER_OWNED_XY"}, + ) + return + if policy_key == "preservation": + _require_policy_fields( + policy_key, + fields, + ( + "frozen_observation_policy", + "grounded_invariant_selector", + "state_selector", + ), + ) + _require_policy_values( + fields, + {"frozen_observation_policy": "COMPLETE_GROUNDED"}, + ) + _require_policy_ids( + fields, + { + "grounded_invariant_selector": "selector:grounded-invariants", + "state_selector": "selector:frozen-nonprimary-state", + }, + ) + return + raise ValueError("executable policy key is unknown") + + +def _require_policy_values( + fields: dict[str, TypedValue], expected: dict[str, str] +) -> None: + for name, expected_value in expected.items(): + if _policy_symbol(fields, name) != expected_value: + raise ValueError(f"executable policy field {name!r} is unsupported") + + +def _require_policy_ids( + fields: dict[str, TypedValue], expected: dict[str, str] +) -> None: + for name, expected_value in expected.items(): + if _policy_id(fields, name) != expected_value: + raise ValueError(f"executable policy field {name!r} is unsupported") + + +def _executable_policy_wire(policy: UprightSE2ExecutablePolicyValue) -> TypedValue: + return _semantic_typed_record( + _EXECUTABLE_POLICY_WIRE_SCHEMA_REF, + ( + ("definition_ref", _semantic_typed_id(policy.definition_ref)), + ("payload", policy.payload), + ("payload_schema_ref", _semantic_typed_id(policy.payload_schema_ref)), + ("policy_family_ref", _semantic_typed_id(policy.policy_family_ref)), + ("policy_key", _semantic_typed_id(policy.policy_key)), + ), + ) + + +def executable_policy_bundle_to_typed_value( + bundle: UprightSE2ExecutablePolicyBundle, +) -> TypedValue: + """Encode a sealed bundle into the semantic-problem extension-fact wire.""" + + return _semantic_typed_record( + _EXECUTABLE_POLICY_BUNDLE_SCHEMA_REF, + ( + ( + "policies", + TypedValue( + value_schema_ref=( + "schema:spatialcf/upright-se2/executable-policy-wire-tuple/1.0" + ), + payload=FiniteOrderedTupleValue( + element_schema_ref=_EXECUTABLE_POLICY_WIRE_SCHEMA_REF, + items=tuple( + _executable_policy_wire(policy) + for policy in bundle.policies + ), + ), + ), + ), + ( + "profile_registration_sha256", + _semantic_typed_digest(bundle.profile_registration_sha256), + ), + ), + ) + + +def _record_field_map(value: TypedValue, schema_ref: str) -> dict[str, TypedValue]: + if value.value_schema_ref != schema_ref or type(value.payload) is not RecordValue: + raise ValueError("executable policy wire has the wrong record schema") + return {field.name: field.value for field in value.payload.fields} + + +def _wire_policy_value(value: TypedValue) -> UprightSE2ExecutablePolicyValue: + fields = _record_field_map(value, _EXECUTABLE_POLICY_WIRE_SCHEMA_REF) + if set(fields) != { + "definition_ref", + "payload", + "payload_schema_ref", + "policy_family_ref", + "policy_key", + }: + raise ValueError("executable policy wire fields are incomplete or unknown") + policy_key = _policy_id(fields, "policy_key") + definition_ref = _policy_id(fields, "definition_ref") + return UprightSE2ExecutablePolicyValue.seal( + policy_key=policy_key, + policy_family_ref=_policy_id(fields, "policy_family_ref"), + definition_ref=definition_ref, + payload_schema_ref=_policy_id(fields, "payload_schema_ref"), + owner_binding=_semantic_owner_binding(definition_ref), + payload=fields["payload"], + ) + + +def request_bound_executable_policy_bundle_from_problem( + semantic_problem: CounterfactualProblemIR, +) -> UprightSE2ExecutablePolicyBundle: + """Recover the request-owned bundle from the only permitted semantic wire.""" + + facts = tuple( + fact + for bundle in semantic_problem.scene_state.extension_fact_bundles + for fact in bundle.facts + if fact.fact_family_ref == _EXECUTABLE_POLICY_FAMILY_REF + ) + if len(facts) != 1: + raise ValueError("executable policy bundle must be present exactly once") + fact = facts[0] + if ( + fact.fact_key != _EXECUTABLE_POLICY_BUNDLE_FACT_KEY + or fact.subject_entity_id != "entity:upright-se2-policy" + ): + raise ValueError("executable policy bundle has an unbound extension-fact owner") + fields = _record_field_map(fact.value, _EXECUTABLE_POLICY_BUNDLE_SCHEMA_REF) + if set(fields) != {"policies", "profile_registration_sha256"}: + raise ValueError( + "executable policy bundle wire fields are incomplete or unknown" + ) + profile_digest = _policy_digest(fields, "profile_registration_sha256") + policies_value = fields["policies"] + if ( + policies_value.value_schema_ref + != "schema:spatialcf/upright-se2/executable-policy-wire-tuple/1.0" + or type(policies_value.payload) is not FiniteOrderedTupleValue + or policies_value.payload.element_schema_ref + != _EXECUTABLE_POLICY_WIRE_SCHEMA_REF + ): + raise ValueError( + "executable policy bundle must carry the registered policy tuple" + ) + return UprightSE2ExecutablePolicyBundle.seal( + profile_registration_sha256=profile_digest, + policies=tuple( + _wire_policy_value(value) for value in policies_value.payload.items + ), + ) + + +def build_upright_se2_executable_policy_bundle( + *, + profile_registration_sha256: Sha256Digest, + policies: tuple[UprightSE2ExecutablePolicyValue, ...], +) -> UprightSE2ExecutablePolicyBundle: + """Seal supplied policy values without selecting any ambient values.""" + + return UprightSE2ExecutablePolicyBundle.seal( + profile_registration_sha256=profile_registration_sha256, + policies=policies, + ) + + +def decode_upright_se2_objective_policy( + bundle: UprightSE2ExecutablePolicyBundle, +) -> UprightSE2FiveTermObjectivePolicy: + """Decode the one request-bound T/A/R/V/S evaluator policy from its wire.""" + + return _decode_upright_se2_objective_policy_payload( + bundle.policy_for("objective").payload + ) + + +def _validate_executable_policy_resource_binding( + bundle: UprightSE2ExecutablePolicyBundle, + resource_policy: ResourcePolicy, +) -> None: + resource_fields = _policy_payload_fields( + "resource", bundle.policy_for("resource").payload + ) + if len(resource_policy.limits) != 1: + raise ValueError("resource policy must bind one registered limit") + limit = resource_policy.limits[0] + if ( + not math.isfinite(limit.finite_limit) + or limit.finite_limit < 0.0 + or not limit.finite_limit.is_integer() + ): + raise ValueError("resource policy finite limit must be a non-negative integer") + if _policy_id_tuple(resource_fields, "limits") != (limit.definition_ref,): + raise ValueError( + "executable resource policy limits do not bind the source request" + ) + if _policy_integer(resource_fields, "atomic_step_limit") != int(limit.finite_limit): + raise ValueError( + "executable resource policy atomic step limit does not bind the source request" + ) + if ( + _policy_digest(resource_fields, "resource_policy_sha256") + != resource_policy.resource_policy_sha256 + ): + raise ValueError( + "executable policy resource root does not bind the source request" + ) + if ( + _policy_id(resource_fields, "shared_ledger_policy_ref") + != resource_policy.shared_ledger_policy_ref + ): + raise ValueError( + "executable policy shared ledger does not bind the source request" + ) + + +def validate_upright_se2_executable_policy_visibility_binding( + bundle: UprightSE2ExecutablePolicyBundle, + problem: CounterfactualProblemIR, +) -> None: + """Bind every request visibility bound to one exact grounded observation.""" + + fields = _policy_payload_fields( + "visibility", bundle.policy_for("visibility").payload + ) + _validate_visibility_observation_bounds(fields) + value = fields["observation_bounds"] + assert type(value.payload) is FiniteOrderedTupleValue + scene = problem.scene_state.base_scene_payload + observations = _exact_source_values( + scene.baseline_observations, "baseline observations" + ) + expected = {observation.observation_id: observation for observation in observations} + if len(problem.explicit_observation_obligations) != len(expected): + raise ValueError( + "semantic visibility obligations must cover every exact observation" + ) + obligation_ids: set[str] = set() + for obligation in problem.explicit_observation_obligations: + if type(obligation) is not ObservationObligation: + raise ValueError( + "semantic visibility obligations must be exact observations" + ) + obligation_id = _bound_visibility_observation_id(obligation, scene) + if obligation_id in obligation_ids: + raise ValueError( + "semantic visibility obligations must not duplicate observations" + ) + obligation_ids.add(obligation_id) + if obligation_ids != set(expected): + raise ValueError("semantic visibility obligations must match the exact roster") + bound_ids: list[str] = [] + for item in value.payload.items: + bound = _visibility_observation_bound_fields(item) + observation_id = _policy_id(bound, "observation_id") + source = expected.get(observation_id) + if source is None: + raise ValueError("executable visibility observation bound is unknown") + if ( + _policy_id(bound, "camera_id") != source.camera_id + or _policy_id(bound, "object_id") != source.object_id + or _policy_symbol(bound, "metric_definition_id") + != source.metric_definition_id + or _policy_id(bound, "metric_definition_version") + != source.metric_definition_version + ): + raise ValueError( + "executable visibility observation bound does not match its obligation" + ) + if ( + _policy_symbol(bound, "comparator") != _policy_symbol(fields, "comparator") + or _policy_symbol(bound, "boundary_policy") + != _policy_symbol(fields, "boundary_policy") + or _policy_real(bound, "threshold") != _policy_real(fields, "threshold") + or _policy_real(bound, "tolerance") != _policy_real(fields, "tolerance") + ): + raise ValueError( + "executable visibility observation bound does not match its policy" + ) + bound_ids.append(observation_id) + if tuple(bound_ids) != tuple( + observation.observation_id for observation in observations + ): + raise ValueError( + "executable visibility observation bounds must match the exact ordered roster" + ) + + +def _upright_semantic_definitions() -> tuple[UprightSE2SemanticDefinition, ...]: + kinds = tuple( + sorted( + _SEMANTIC_KIND_TO_REF, + key=lambda kind: canonical_json_bytes(_SEMANTIC_KIND_TO_REF[kind]), + ) + ) + return tuple( + UprightSE2SemanticDefinition.seal( + definition_ref=_semantic_definition_ref_for_kind(kind), + definition_kind=kind, + payload_schema_ref=_semantic_definition_schema_for_kind(kind), + semantic_body=_semantic_definition_body(kind), + owner_binding=_semantic_owner_binding( + _semantic_definition_ref_for_kind(kind) + ), + ) + for kind in kinds + ) + + +def _objective_term_policy_values(term_id: str) -> dict[str, object]: + values = { + "T": ( + "definition:spatialcf/upright-se2/objective-subject-pivot-displacement/1.0", + "definition:spatialcf/upright-se2/objective-selector-subject-pivot-displacement/1.0", + "definition:spatialcf/upright-se2/metre/1.0", + "definition:spatialcf/upright-se2/normalizer-subject-pivot-displacement/1.0", + ), + "A": ( + "definition:spatialcf/upright-se2/objective-angular-geodesic/1.0", + "definition:spatialcf/upright-se2/objective-selector-angular-geodesic/1.0", + "definition:spatialcf/upright-se2/turn/1.0", + "definition:spatialcf/upright-se2/normalizer-angular-geodesic/1.0", + ), + "R": ( + "definition:spatialcf/upright-se2/objective-nontarget-relation-damage/1.0", + "definition:spatialcf/upright-se2/objective-selector-nontarget-relation-damage/1.0", + "definition:spatialcf/upright-se2/dimensionless/1.0", + "definition:spatialcf/upright-se2/normalizer-nontarget-relation-damage/1.0", + ), + "V": ( + "definition:spatialcf/upright-se2/objective-visibility-change/1.0", + "definition:spatialcf/upright-se2/objective-selector-visibility-change/1.0", + "definition:spatialcf/upright-se2/dimensionless/1.0", + "definition:spatialcf/upright-se2/normalizer-visibility-change/1.0", + ), + "S": ( + "definition:spatialcf/upright-se2/objective-safety-margin-penalty/1.0", + "definition:spatialcf/upright-se2/objective-selector-safety-margin-penalty/1.0", + "definition:spatialcf/upright-se2/dimensionless/1.0", + "definition:spatialcf/upright-se2/normalizer-safety-margin-penalty/1.0", + ), + }[term_id] + return { + "objective_definition_ref": UPRIGHT_SE2_OBJECTIVE_DEFINITION_REF, + "metric_definition_ref": values[0], + "input_selector_definition_ref": values[1], + "unit_ref": values[2], + "normalization_definition_ref": values[3], + "normalizer_unit_ref": values[2], + } + + +def _objective_policy_refs() -> dict[str, str]: + return { + "objective_definition_ref": UPRIGHT_SE2_OBJECTIVE_DEFINITION_REF, + "aggregation_definition_ref": "definition:spatialcf/upright-se2/objective-weighted-normalized-sum/1.0", + "safety_penalty_definition_ref": "definition:spatialcf/upright-se2/objective-safety-penalty-from-hard-constraints/1.0", + "strict_interval_comparator_ref": "definition:spatialcf/upright-se2/objective-strict-upper-lower-comparator/1.0", + "exact_equality_definition_ref": "definition:spatialcf/upright-se2/objective-degenerate-exact-equality/1.0", + "directed_gap_subtraction_definition_ref": "definition:spatialcf/upright-se2/objective-directed-gap-subtraction/1.0", + "exact_prune_comparator_ref": "definition:spatialcf/upright-se2/objective-exact-strict-prune/1.0", + "gap_prune_comparator_ref": "definition:spatialcf/upright-se2/objective-gap-closed-prune/1.0", + "interval_boundary_policy_ref": "definition:spatialcf/upright-se2/objective-interval-boundary-policy/1.0", + "deterministic_tie_break_definition_ref": "definition:spatialcf/upright-se2/objective-tie-break-T-R-V-S-A/1.0", + } + + +def build_upright_se2_q0_target_objective_policy() -> UprightSE2FiveTermObjectivePolicy: + """Build the explicit, source-independent unit target policy used by q=0.""" + + return UprightSE2FiveTermObjectivePolicy.seal( + **_objective_policy_refs(), + terms=tuple( + UprightSE2ObjectiveTermPolicy( + term_id=term_id, + **_objective_term_policy_values(term_id), + weight=1.0, + normalizer=1.0, + ) + for term_id in ("T", "A", "R", "V", "S") + ), + owner_binding=_semantic_owner_binding(UPRIGHT_SE2_OBJECTIVE_DEFINITION_REF), + ) + + +def build_upright_se2_objective_expression() -> ObjectiveExpression: + """Return the fixed structural expression, without a numeric policy fallback.""" + + return ObjectiveExpression.seal( + aggregation_definition_ref=_objective_policy_refs()[ + "aggregation_definition_ref" + ], + terms=tuple( + ObjectiveTerm( + term_id=term_id, + objective_definition_ref=UPRIGHT_SE2_OBJECTIVE_DEFINITION_REF, + input_selector_definition_ref=_objective_term_policy_values(term_id)[ + "input_selector_definition_ref" + ], + unit_ref=_objective_term_policy_values(term_id)["unit_ref"], + normalization_definition_ref=_objective_term_policy_values(term_id)[ + "normalization_definition_ref" + ], + ) + for term_id in ("T", "A", "R", "V", "S") + ), + deterministic_tie_break_definition_ref=( + _objective_policy_refs()["deterministic_tie_break_definition_ref"] + ), + ) + + +def _semantic_owner_binding_payload( + binding: UprightSE2SemanticOwnerBinding, +) -> TypedValue: + return _semantic_typed_record( + "schema:spatialcf/upright-se2/semantic-owner-binding/1.0", + ( + ("definition_ref", _semantic_typed_id(binding.definition_ref)), + ( + "evaluator_build_sha256", + _semantic_typed_digest(binding.evaluator_build_sha256), + ), + ( + "evaluator_capability_ref", + _semantic_typed_id(binding.evaluator_capability_ref), + ), + ("evaluator_owner_ref", _semantic_typed_id(binding.evaluator_owner_ref)), + ( + "verifier_build_sha256", + _semantic_typed_digest(binding.verifier_build_sha256), + ), + ( + "verifier_capability_ref", + _semantic_typed_id(binding.verifier_capability_ref), + ), + ("verifier_owner_ref", _semantic_typed_id(binding.verifier_owner_ref)), + ), + ) + + +def _semantic_definition_envelope( + definition: UprightSE2SemanticDefinition, +) -> CanonicalDefinitionEnvelope: + return CanonicalDefinitionEnvelope.seal( + definition_ref=definition.definition_ref, + definition_kind_ref=_SEMANTIC_DEFINITION_KIND_REF, + payload_schema_ref=definition.payload_schema_ref, + payload=_semantic_typed_record( + definition.payload_schema_ref, + ( + ("definition_kind", _semantic_typed_symbol(definition.definition_kind)), + ("definition_ref", _semantic_typed_id(definition.definition_ref)), + ( + "owner_binding", + _semantic_owner_binding_payload(definition.owner_binding), + ), + ("semantic_body", definition.semantic_body), + ( + "semantic_definition_sha256", + _semantic_typed_digest(definition.semantic_definition_sha256), + ), + ), + ), + ) + + +def _objective_term_payload(term: UprightSE2ObjectiveTermPolicy) -> TypedValue: + return _semantic_typed_record( + "schema:spatialcf/upright-se2/objective-term-policy/1.0", + ( + ( + "input_selector_definition_ref", + _semantic_typed_id(term.input_selector_definition_ref), + ), + ("metric_definition_ref", _semantic_typed_id(term.metric_definition_ref)), + ( + "normalization_definition_ref", + _semantic_typed_id(term.normalization_definition_ref), + ), + ("normalizer", _semantic_typed_real(term.normalizer)), + ("normalizer_unit_ref", _semantic_typed_id(term.normalizer_unit_ref)), + ( + "objective_definition_ref", + _semantic_typed_id(term.objective_definition_ref), + ), + ("term_id", _semantic_typed_symbol(term.term_id)), + ("unit_ref", _semantic_typed_id(term.unit_ref)), + ("weight", _semantic_typed_real(term.weight)), + ), + ) + + +def _objective_policy_envelope( + policy: UprightSE2FiveTermObjectivePolicy, +) -> CanonicalDefinitionEnvelope: + return CanonicalDefinitionEnvelope.seal( + definition_ref=policy.objective_definition_ref, + definition_kind_ref=_OBJECTIVE_DEFINITION_KIND_REF, + payload_schema_ref=_OBJECTIVE_POLICY_SCHEMA_REF, + payload=_semantic_typed_record( + _OBJECTIVE_POLICY_SCHEMA_REF, + ( + ( + "aggregation_definition_ref", + _semantic_typed_id(policy.aggregation_definition_ref), + ), + ( + "deterministic_tie_break_definition_ref", + _semantic_typed_id(policy.deterministic_tie_break_definition_ref), + ), + ( + "directed_gap_subtraction_definition_ref", + _semantic_typed_id(policy.directed_gap_subtraction_definition_ref), + ), + ( + "exact_equality_definition_ref", + _semantic_typed_id(policy.exact_equality_definition_ref), + ), + ( + "exact_prune_comparator_ref", + _semantic_typed_id(policy.exact_prune_comparator_ref), + ), + ( + "five_term_objective_policy_sha256", + _semantic_typed_digest(policy.five_term_objective_policy_sha256), + ), + ( + "gap_prune_comparator_ref", + _semantic_typed_id(policy.gap_prune_comparator_ref), + ), + ( + "interval_boundary_policy_ref", + _semantic_typed_id(policy.interval_boundary_policy_ref), + ), + ( + "objective_definition_ref", + _semantic_typed_id(policy.objective_definition_ref), + ), + ( + "owner_binding", + _semantic_owner_binding_payload(policy.owner_binding), + ), + ( + "safety_penalty_definition_ref", + _semantic_typed_id(policy.safety_penalty_definition_ref), + ), + ( + "strict_interval_comparator_ref", + _semantic_typed_id(policy.strict_interval_comparator_ref), + ), + ( + "terms", + TypedValue( + value_schema_ref="schema:spatialcf/upright-se2/objective-term-policy-list/1.0", + payload=FiniteOrderedTupleValue( + element_schema_ref="schema:spatialcf/upright-se2/objective-term-policy/1.0", + items=tuple( + _objective_term_payload(term) for term in policy.terms + ), + ), + ), + ), + ), + ), + ) + + +def _semantic_definition_bundle_for_digest( + profile_registration_sha256: Sha256Digest, + objective_policy: UprightSE2FiveTermObjectivePolicy, +) -> DefinitionBundle: + definitions = _upright_semantic_definitions() + policy = objective_policy + predicate_envelopes = tuple( + _semantic_definition_envelope(definition) for definition in definitions + ) + objective_envelope = _objective_policy_envelope(policy) + closure_envelope = CanonicalDefinitionEnvelope.seal( + definition_ref=UPRIGHT_SE2_SEMANTIC_CLOSURE_DEFINITION_REF, + definition_kind_ref=_SEMANTIC_CLOSURE_KIND_REF, + payload_schema_ref=_SEMANTIC_CLOSURE_SCHEMA_REF, + payload=_semantic_typed_record( + _SEMANTIC_CLOSURE_SCHEMA_REF, + ( + ( + "definition_content_sha256s", + _semantic_typed_digest_tuple( + tuple( + definition.semantic_definition_sha256 + for definition in definitions + ) + + (policy.five_term_objective_policy_sha256,) + ), + ), + ( + "objective_definition_ref", + _semantic_typed_id(policy.objective_definition_ref), + ), + ( + "profile_registration_sha256", + _semantic_typed_digest(profile_registration_sha256), + ), + ( + "semantic_definition_refs", + _semantic_typed_tuple( + tuple(definition.definition_ref for definition in definitions) + + (policy.objective_definition_ref,) + ), + ), + ), + ), + ) + return DefinitionBundle.seal( + definitions=tuple( + sorted( + (*predicate_envelopes, objective_envelope, closure_envelope), + key=lambda definition: canonical_json_bytes(definition.definition_ref), + ) + ) + ) + + +def _validate_task2_semantic_profile( + registration: UprightSE2ProfileRegistration, +) -> None: + """Require the complete Task 2 closure only where semantic evaluation begins. + + The Task 1 profile record remains a structural, capability-staged wire so + its frozen profile tests can still construct the pre-evaluation form. The + compiler cannot reach a semantic bundle without this stricter M3 gate. + """ + + if ( + registration.semantics_profile.predicate_definition_refs + != UPRIGHT_SE2_PREDICATE_DEFINITION_REFS + ): + raise ValueError( + "semantics profile must name the complete upright predicate closure" + ) + if registration.semantics_profile.objective_definition_refs != ( + UPRIGHT_SE2_OBJECTIVE_DEFINITION_REF, + ): + raise ValueError("semantics profile must name the five-term objective policy") + if ( + registration.action_space_profile.predicate_capability_refs + != UPRIGHT_SE2_PREDICATE_CAPABILITY_REFS + ): + raise ValueError( + "action-space profile must name the semantic predicate capabilities" + ) + if ( + registration.action_space_profile.objective_capability_refs + != UPRIGHT_SE2_OBJECTIVE_CAPABILITY_REFS + ): + raise ValueError( + "action-space profile must name the semantic objective capabilities" + ) + + +def build_upright_se2_semantic_definition_bundle( + registration: UprightSE2ProfileRegistration, + *, + objective_policy: UprightSE2FiveTermObjectivePolicy, +) -> DefinitionBundle: + """Build the only profile/definition bundle accepted by the M3 compiler.""" + + _validate_task2_semantic_profile(registration) + return _semantic_definition_bundle_for_digest( + registration.profile_registration_sha256, objective_policy + ) + + +def _upright_semantic_owner_bindings() -> tuple[UprightSE2SemanticOwnerBinding, ...]: + return tuple( + sorted( + ( + *( + _semantic_owner_binding(definition_ref) + for definition_ref in UPRIGHT_SE2_PREDICATE_DEFINITION_REFS + ), + _semantic_owner_binding(UPRIGHT_SE2_OBJECTIVE_DEFINITION_REF), + ), + key=canonical_json_bytes, + ) + ) + + +def _predicate_atom_from_context(context: object) -> PredicateAtom: + if isinstance(context, (BeforePrecondition, AfterGoal, ObservationObligation)): + formula = context.formula + if type(formula) is PredicateAtom: + return formula + raise ValueError("semantic obligations must use direct grounded predicate atoms") + + +def _validate_grounded_semantic_shape( + grounded_obligations: GroundedObligationSet, +) -> None: + if len(grounded_obligations.before_preconditions) != 1: + raise ValueError( + "semantic closure requires one grounded target before-precondition" + ) + if len(grounded_obligations.after_goals) != 1: + raise ValueError("semantic closure requires one grounded target after-goal") + if len(grounded_obligations.preservation_invariants) != 1: + raise ValueError( + "semantic closure requires one grounded preservation invariant" + ) + if not grounded_obligations.observation_obligations: + raise ValueError("semantic closure requires grounded visibility obligations") + before = _predicate_atom_from_context( + grounded_obligations.before_preconditions[0].context + ) + after = _predicate_atom_from_context(grounded_obligations.after_goals[0].context) + if ( + before.predicate_ref != UPRIGHT_SE2_TARGET_RELATION_PREDICATE_REF + or after.predicate_ref != UPRIGHT_SE2_TARGET_RELATION_PREDICATE_REF + or not before.operands + or not after.operands + ): + raise ValueError( + "semantic closure target relation obligations must be grounded" + ) + preservation_context = grounded_obligations.preservation_invariants[0].context + if type(preservation_context) is not PreservationInvariant: + raise ValueError( + "semantic closure preservation must use a transition invariant" + ) + if ( + type(preservation_context.before_formula) is not PredicateAtom + or type(preservation_context.after_formula) is not PredicateAtom + or preservation_context.before_formula.predicate_ref + != UPRIGHT_SE2_PRESERVATION_PREDICATE_REF + or preservation_context.after_formula.predicate_ref + != UPRIGHT_SE2_PRESERVATION_PREDICATE_REF + or not preservation_context.before_formula.operands + or not preservation_context.after_formula.operands + or preservation_context.transition_comparator_ref + != "definition:spatialcf/upright-se2/preservation-transition-comparator/1.0" + ): + raise ValueError("semantic closure preservation invariant must be grounded") + for obligation in grounded_obligations.observation_obligations: + context = obligation.context + if type(context) is not ObservationObligation: + raise ValueError( + "semantic closure observations must use observation obligations" + ) + atom = _predicate_atom_from_context(context) + if ( + atom.predicate_ref != UPRIGHT_SE2_VISIBILITY_PREDICATE_REF + or not atom.operands + or context.evidence_policy_ref + != "definition:spatialcf/upright-se2/visibility-evidence-policy/1.0" + ): + raise ValueError("semantic closure visibility obligations must be grounded") + + +def _bound_source_input(problem: CounterfactualProblemIR) -> _UprightSE2SourceInput: + """Decode the sole cardinal compiler-input fact from a bound semantic root.""" + + bundles = problem.scene_state.extension_fact_bundles + facts = tuple(fact for bundle in bundles for fact in bundle.facts) + compiler_facts = tuple( + fact + for fact in facts + if ( + fact.fact_family_ref == _UPRIGHT_SE2_COMPILER_INPUT_FAMILY_REF + and fact.fact_key == _UPRIGHT_SE2_COMPILER_INPUT_FACT_KEY + ) + ) + construction_facts = tuple( + fact + for fact in facts + if fact.fact_family_ref + == "definition:spatialcf/upright-se2/m2-q0-construction/1.0" + ) + if ( + len(compiler_facts) != 1 + or len(construction_facts) > 1 + or len(facts) != 2 + len(construction_facts) + ): + raise ValueError( + "semantic closure source problem must contain one compiler input, one executable policy, and at most one construction root" + ) + request_bound_executable_policy_bundle_from_problem(problem) + fact = compiler_facts[0] + if ( + fact.fact_family_ref != _UPRIGHT_SE2_COMPILER_INPUT_FAMILY_REF + or fact.fact_key != _UPRIGHT_SE2_COMPILER_INPUT_FACT_KEY + or fact.value.value_schema_ref != _UPRIGHT_SE2_COMPILER_INPUT_SCHEMA_REF + or type(fact.value.payload) is not RecordValue + ): + raise ValueError( + "semantic closure source compiler input has the wrong identity" + ) + fields = fact.value.payload.fields + expected_names = tuple( + sorted( + ( + "operation_kind", + "operator_ref", + "reference_id", + "subject_id", + "subject_yaw_turns", + "yaw_argument", + ), + key=canonical_json_bytes, + ) + ) + if tuple(field.name for field in fields) != expected_names: + raise ValueError( + "semantic closure source compiler input fields are not canonical" + ) + values = {field.name: field.value for field in fields} + if ( + _bound_symbol(values["operation_kind"], _UPRIGHT_SE2_ENUM_SCHEMA_REF) + != "CARDINAL" + ): + raise ValueError("semantic closure source input must be a cardinal operation") + subject_id = _bound_id(values["subject_id"], _UPRIGHT_SE2_ID_SCHEMA_REF) + if fact.subject_entity_id != f"entity:{subject_id}": + raise ValueError( + "semantic closure source compiler input subject is not scene-owned" + ) + yaw_argument = values["yaw_argument"] + if ( + yaw_argument.value_schema_ref != _UPRIGHT_SE2_YAW_ARGUMENT_SCHEMA_REF + or type(yaw_argument.payload) is not RecordValue + ): + raise ValueError("semantic closure source yaw argument has the wrong schema") + yaw_fields = yaw_argument.payload.fields + if tuple(field.name for field in yaw_fields) != tuple( + sorted(("kind", "quarter_turns_ccw"), key=canonical_json_bytes) + ): + raise ValueError( + "semantic closure source cardinal yaw fields are not canonical" + ) + yaw_values = {field.name: field.value for field in yaw_fields} + if _bound_symbol(yaw_values["kind"], _UPRIGHT_SE2_ENUM_SCHEMA_REF) != "CARDINAL": + raise ValueError("semantic closure source yaw must be cardinal") + return _UprightSE2SourceInput( + operator_ref=_bound_id(values["operator_ref"], _UPRIGHT_SE2_ID_SCHEMA_REF), + subject_id=subject_id, + reference_id=_bound_id(values["reference_id"], _UPRIGHT_SE2_ID_SCHEMA_REF), + subject_yaw_turns=CanonicalSO2Angle( + turns=_bound_real(values["subject_yaw_turns"]) + ), + quarter_turns_ccw=CardinalYaw( + q=_bound_integer(yaw_values["quarter_turns_ccw"]) + ).q, + ) + + +def _bound_continuous_source_input( + problem: CounterfactualProblemIR, +) -> _UprightSE2ContinuousSourceInput: + """Decode the continuous wire without changing the cardinal decoder.""" + + bundles = problem.scene_state.extension_fact_bundles + facts = tuple(fact for bundle in bundles for fact in bundle.facts) + compiler_facts = tuple( + fact + for fact in facts + if ( + fact.fact_family_ref == _UPRIGHT_SE2_COMPILER_INPUT_FAMILY_REF + and fact.fact_key == _UPRIGHT_SE2_COMPILER_INPUT_FACT_KEY + ) + ) + construction_facts = tuple( + fact + for fact in facts + if fact.fact_family_ref + == "definition:spatialcf/upright-se2/m2-q0-construction/1.0" + ) + if ( + len(compiler_facts) != 1 + or len(construction_facts) > 1 + or len(facts) != 2 + len(construction_facts) + ): + raise ValueError( + "continuous semantic closure source problem must contain one compiler input, one executable policy, and at most one construction root" + ) + request_bound_executable_policy_bundle_from_problem(problem) + fact = compiler_facts[0] + if ( + fact.fact_family_ref != _UPRIGHT_SE2_COMPILER_INPUT_FAMILY_REF + or fact.fact_key != _UPRIGHT_SE2_COMPILER_INPUT_FACT_KEY + or fact.value.value_schema_ref != _UPRIGHT_SE2_COMPILER_INPUT_SCHEMA_REF + or type(fact.value.payload) is not RecordValue + ): + raise ValueError( + "continuous semantic closure source compiler input has the wrong identity" + ) + fields = fact.value.payload.fields + expected_names = tuple( + sorted( + ( + "operation_kind", + "operator_ref", + "reference_id", + "subject_id", + "subject_yaw_turns", + "yaw_argument", + ), + key=canonical_json_bytes, + ) + ) + if tuple(field.name for field in fields) != expected_names: + raise ValueError( + "continuous semantic closure source compiler input fields are not canonical" + ) + values = {field.name: field.value for field in fields} + if ( + _bound_symbol(values["operation_kind"], _UPRIGHT_SE2_ENUM_SCHEMA_REF) + != "CONTINUOUS" + ): + raise ValueError( + "continuous semantic closure source input must be a continuous operation" + ) + subject_id = _bound_id(values["subject_id"], _UPRIGHT_SE2_ID_SCHEMA_REF) + if fact.subject_entity_id != f"entity:{subject_id}": + raise ValueError( + "continuous semantic closure source compiler input subject is not scene-owned" + ) + yaw_argument = values["yaw_argument"] + if ( + yaw_argument.value_schema_ref != _UPRIGHT_SE2_YAW_ARGUMENT_SCHEMA_REF + or type(yaw_argument.payload) is not RecordValue + ): + raise ValueError( + "continuous semantic closure source yaw argument has the wrong schema" + ) + yaw_fields = yaw_argument.payload.fields + yaw_values = {field.name: field.value for field in yaw_fields} + if "kind" not in yaw_values: + raise ValueError("continuous semantic closure source yaw must declare a kind") + kind = _bound_symbol(yaw_values["kind"], _UPRIGHT_SE2_ENUM_SCHEMA_REF) + if kind == "ARC": + if tuple(field.name for field in yaw_fields) != tuple( + sorted(("ccw_sweep_turns", "kind", "start_turns"), key=canonical_json_bytes) + ): + raise ValueError( + "continuous semantic closure arc yaw fields are not canonical" + ) + yaw_domain: ContinuousYawDomain = ContinuousYawArc( + start_angle=CanonicalSO2Angle(turns=_bound_real(yaw_values["start_turns"])), + ccw_sweep_turns=_bound_real(yaw_values["ccw_sweep_turns"]), + ) + elif kind == "FULL_CIRCLE": + if tuple(field.name for field in yaw_fields) != ("kind",): + raise ValueError( + "continuous semantic closure full-circle yaw must not carry arc fields" + ) + yaw_domain = ContinuousYawFullCircle() + else: + raise ValueError("continuous semantic closure yaw must be ARC or FULL_CIRCLE") + return _UprightSE2ContinuousSourceInput( + operator_ref=_bound_id(values["operator_ref"], _UPRIGHT_SE2_ID_SCHEMA_REF), + subject_id=subject_id, + reference_id=_bound_id(values["reference_id"], _UPRIGHT_SE2_ID_SCHEMA_REF), + subject_yaw_turns=CanonicalSO2Angle( + turns=_bound_real(values["subject_yaw_turns"]) + ), + yaw_domain=yaw_domain, + ) + + +def _bound_real(value: TypedValue) -> float: + if ( + value.value_schema_ref != _UPRIGHT_SE2_REAL_SCHEMA_REF + or type(value.payload) is not FiniteRealValue + ): + raise ValueError("semantic closure source input must use finite real operands") + return value.payload.value + + +def _bound_integer(value: TypedValue) -> int: + if ( + value.value_schema_ref != _UPRIGHT_SE2_INTEGER_SCHEMA_REF + or type(value.payload) is not IntegerValue + ): + raise ValueError("semantic closure source input must use integer operands") + return value.payload.value + + +def _bound_id(value: TypedValue, schema_ref: str) -> CanonicalId: + if ( + value.value_schema_ref != schema_ref + or type(value.payload) is not CanonicalIdValue + ): + raise ValueError("semantic closure source input must use canonical ID operands") + return value.payload.value + + +def _bound_symbol(value: TypedValue, schema_ref: str) -> str: + if ( + value.value_schema_ref != schema_ref + or type(value.payload) is not EnumSymbolValue + ): + raise ValueError("semantic closure source input must use symbol operands") + return value.payload.symbol + + +def _bound_scene_object(scene: CanonicalScene, object_id: CanonicalId) -> object: + objects = _exact_source_values(scene.objects, "objects") + matching = tuple(object_ for object_ in objects if object_.object_id == object_id) + if len(matching) != 1: + raise ValueError( + "semantic closure source input must name one exact scene object" + ) + return matching[0] + + +def _bound_predicate_atom(formula: object) -> PredicateAtom: + if type(formula) is not PredicateAtom or not formula.operands: + raise ValueError( + "semantic closure source obligations must use direct predicate atoms" + ) + return formula + + +def _bound_reference( + value: TypedValue, + *, + schema_ref: str, + kind: ValueKind, + expected: str | None = None, +) -> str: + if ( + value.value_schema_ref != schema_ref + or type(value.payload) is not ReferenceValue + or value.payload.kind is not kind + ): + raise ValueError( + "semantic closure source obligation has the wrong reference operand" + ) + if expected is not None and value.payload.reference != expected: + raise ValueError( + "semantic closure source obligation does not bind request authority" + ) + return value.payload.reference + + +def _validate_bound_target_atom( + atom: PredicateAtom, + *, + subject_id: CanonicalId, + reference_id: CanonicalId, + phase: str, +) -> str: + if ( + atom.predicate_ref != UPRIGHT_SE2_TARGET_RELATION_PREDICATE_REF + or len(atom.operands) != 4 + ): + raise ValueError( + "semantic closure source target relation has the wrong predicate" + ) + _bound_reference( + atom.operands[0], + schema_ref="schema:spatialcf/upright-se2/object-ref/1.0", + kind=ValueKind.OBJECT_REF, + expected=subject_id, + ) + _bound_reference( + atom.operands[1], + schema_ref="schema:spatialcf/upright-se2/object-ref/1.0", + kind=ValueKind.OBJECT_REF, + expected=reference_id, + ) + relation = _bound_symbol( + atom.operands[2], + "schema:spatialcf/upright-se2/relation-symbol/1.0", + ) + if relation not in { + "relation:LEFT", + "relation:RIGHT", + "relation:FRONT", + "relation:BEHIND", + "relation:NEAR", + "relation:FAR", + }: + raise ValueError("semantic closure source target relation is not registered") + if ( + _bound_symbol( + atom.operands[3], + "schema:spatialcf/upright-se2/phase-symbol/1.0", + ) + != f"phase:{phase}" + ): + raise ValueError("semantic closure source target relation has the wrong phase") + return relation + + +def _validate_bound_preservation_atom( + atom: PredicateAtom, + *, + subject_id: CanonicalId, + phase: str, +) -> None: + if ( + atom.predicate_ref != UPRIGHT_SE2_PRESERVATION_PREDICATE_REF + or len(atom.operands) != 3 + ): + raise ValueError("semantic closure source preservation has the wrong predicate") + _bound_reference( + atom.operands[0], + schema_ref="schema:spatialcf/upright-se2/entity-ref/1.0", + kind=ValueKind.ENTITY_REF, + expected=f"entity:{subject_id}", + ) + if ( + _bound_symbol( + atom.operands[1], + "schema:spatialcf/upright-se2/preservation-selector/1.0", + ) + != "preservation:FROZEN_NONPRIMARY_LEAVES" + ): + raise ValueError( + "semantic closure source preservation selector is not frozen-state" + ) + if ( + _bound_symbol( + atom.operands[2], + "schema:spatialcf/upright-se2/phase-symbol/1.0", + ) + != f"phase:{phase}" + ): + raise ValueError("semantic closure source preservation has the wrong phase") + + +def _bound_visibility_observation_id( + obligation: ObservationObligation, + scene: CanonicalScene, +) -> CanonicalId: + if obligation.phase != "AFTER" or obligation.evidence_policy_ref != ( + "definition:spatialcf/upright-se2/visibility-evidence-policy/1.0" + ): + raise ValueError( + "semantic closure source visibility policy is not fixed-camera AFTER" + ) + atom = _bound_predicate_atom(obligation.formula) + if ( + atom.predicate_ref != UPRIGHT_SE2_VISIBILITY_PREDICATE_REF + or len(atom.operands) != 5 + ): + raise ValueError("semantic closure source visibility has the wrong predicate") + camera_id = _bound_reference( + atom.operands[0], + schema_ref="schema:spatialcf/upright-se2/camera-ref/1.0", + kind=ValueKind.CAMERA_REF, + ) + object_id = _bound_reference( + atom.operands[1], + schema_ref="schema:spatialcf/upright-se2/object-ref/1.0", + kind=ValueKind.OBJECT_REF, + ) + metric_definition_id = _bound_symbol( + atom.operands[2], + "schema:spatialcf/upright-se2/visibility-metric-symbol/1.0", + ) + observation_id = _bound_id( + atom.operands[3], + "schema:spatialcf/upright-se2/observation-ref/1.0", + ) + if ( + _bound_symbol( + atom.operands[4], + "schema:spatialcf/upright-se2/phase-symbol/1.0", + ) + != "phase:AFTER" + ): + raise ValueError("semantic closure source visibility has the wrong phase") + observations = _exact_source_values( + scene.baseline_observations, "baseline observations" + ) + matching = tuple( + observation + for observation in observations + if observation.observation_id == observation_id + ) + if len(matching) != 1: + raise ValueError( + "semantic closure source visibility names no exact observation" + ) + observation = matching[0] + if ( + camera_id != observation.camera_id + or object_id != observation.object_id + or metric_definition_id != observation.metric_definition_id + ): + raise ValueError( + "semantic closure source visibility does not bind its exact observation" + ) + return observation_id + + +def _validate_bound_semantic_problem(problem: CounterfactualProblemIR) -> None: + """Close target, preservation, and fixed-camera obligations over the source scene.""" + + source_input = _bound_source_input(problem) + scene = problem.scene_state.base_scene_payload + subject = _bound_scene_object(scene, source_input.subject_id) + reference = _bound_scene_object(scene, source_input.reference_id) + if subject.object_id == reference.object_id or not subject.movable: + raise ValueError( + "semantic closure source input must name one movable subject and reference" + ) + if ( + subject.pose.anchor_kind != "OBJECT_PIVOT" + or reference.pose.anchor_kind != "OBJECT_PIVOT" + ): + raise ValueError("semantic closure source pivots must be exact object pivots") + if ( + len(problem.before_preconditions) != 1 + or type(problem.before_preconditions[0]) is not BeforePrecondition + ): + raise ValueError( + "semantic closure source problem requires one target before-precondition" + ) + before_relation = _validate_bound_target_atom( + _bound_predicate_atom(problem.before_preconditions[0].formula), + subject_id=source_input.subject_id, + reference_id=source_input.reference_id, + phase="BEFORE", + ) + if type(problem.after_goal) is not AfterGoal: + raise ValueError( + "semantic closure source problem requires one target after-goal" + ) + after_relation = _validate_bound_target_atom( + _bound_predicate_atom(problem.after_goal.formula), + subject_id=source_input.subject_id, + reference_id=source_input.reference_id, + phase="AFTER", + ) + if before_relation == after_relation: + raise ValueError("semantic closure source target relations must differ") + if ( + len(problem.preservation_invariants) != 1 + or type(problem.preservation_invariants[0]) is not PreservationInvariant + ): + raise ValueError( + "semantic closure source problem requires one preservation invariant" + ) + preservation = problem.preservation_invariants[0] + if preservation.transition_comparator_ref != ( + "definition:spatialcf/upright-se2/preservation-transition-comparator/1.0" + ): + raise ValueError("semantic closure source preservation comparator is not fixed") + _validate_bound_preservation_atom( + _bound_predicate_atom(preservation.before_formula), + subject_id=source_input.subject_id, + phase="BEFORE", + ) + _validate_bound_preservation_atom( + _bound_predicate_atom(preservation.after_formula), + subject_id=source_input.subject_id, + phase="AFTER", + ) + expected_observations = _exact_source_values( + scene.baseline_observations, "baseline observations" + ) + if len(problem.explicit_observation_obligations) != len(expected_observations): + raise ValueError( + "semantic closure source visibility must cover every exact observation" + ) + actual_observation_ids: set[CanonicalId] = set() + for obligation in problem.explicit_observation_obligations: + if type(obligation) is not ObservationObligation: + raise ValueError( + "semantic closure source visibility must use observation obligations" + ) + observation_id = _bound_visibility_observation_id(obligation, scene) + if observation_id in actual_observation_ids: + raise ValueError( + "semantic closure source visibility must not duplicate observations" + ) + actual_observation_ids.add(observation_id) + expected_observation_ids = { + observation.observation_id for observation in expected_observations + } + if actual_observation_ids != expected_observation_ids: + raise ValueError( + "semantic closure source visibility roster must match the exact scene" + ) + + +def _bound_grounded_obligations( + problem: CounterfactualProblemIR, +) -> GroundedObligationSet: + """Derive the only grounded obligation roster from a sealed source problem.""" + + _validate_bound_semantic_problem(problem) + source_definition_refs = tuple( + definition.definition_ref + for definition in problem.definition_bundle.definitions + ) + return GroundedObligationSet.seal( + before_preconditions=tuple( + GroundedObligation( + context=context, + source_definition_refs=source_definition_refs, + ) + for context in problem.before_preconditions + ), + after_goals=( + GroundedObligation( + context=problem.after_goal, + source_definition_refs=source_definition_refs, + ), + ), + preservation_invariants=tuple( + GroundedObligation( + context=context, + source_definition_refs=source_definition_refs, + ) + for context in problem.preservation_invariants + ), + observation_obligations=tuple( + GroundedObligation( + context=context, + source_definition_refs=source_definition_refs, + ) + for context in problem.explicit_observation_obligations + ), + grounding_entity_sets=(), + ) + + +def _validate_bound_continuous_semantic_problem( + problem: CounterfactualProblemIR, +) -> None: + """Close the retained semantic roster over one continuous compiler input.""" + + source_input = _bound_continuous_source_input(problem) + scene = problem.scene_state.base_scene_payload + subject = _bound_scene_object(scene, source_input.subject_id) + reference = _bound_scene_object(scene, source_input.reference_id) + if subject.object_id == reference.object_id or not subject.movable: + raise ValueError( + "continuous semantic closure source input must name one movable subject and reference" + ) + if ( + subject.pose.anchor_kind != "OBJECT_PIVOT" + or reference.pose.anchor_kind != "OBJECT_PIVOT" + ): + raise ValueError( + "continuous semantic closure source pivots must be exact object pivots" + ) + if ( + len(problem.before_preconditions) != 1 + or type(problem.before_preconditions[0]) is not BeforePrecondition + ): + raise ValueError( + "continuous semantic closure source problem requires one target before-precondition" + ) + before_relation = _validate_bound_target_atom( + _bound_predicate_atom(problem.before_preconditions[0].formula), + subject_id=source_input.subject_id, + reference_id=source_input.reference_id, + phase="BEFORE", + ) + if type(problem.after_goal) is not AfterGoal: + raise ValueError( + "continuous semantic closure source problem requires one target after-goal" + ) + after_relation = _validate_bound_target_atom( + _bound_predicate_atom(problem.after_goal.formula), + subject_id=source_input.subject_id, + reference_id=source_input.reference_id, + phase="AFTER", + ) + if before_relation == after_relation: + raise ValueError( + "continuous semantic closure source target relations must differ" + ) + if ( + len(problem.preservation_invariants) != 1 + or type(problem.preservation_invariants[0]) is not PreservationInvariant + ): + raise ValueError( + "continuous semantic closure source problem requires one preservation invariant" + ) + preservation = problem.preservation_invariants[0] + if preservation.transition_comparator_ref != ( + "definition:spatialcf/upright-se2/preservation-transition-comparator/1.0" + ): + raise ValueError( + "continuous semantic closure source preservation comparator is not fixed" + ) + _validate_bound_preservation_atom( + _bound_predicate_atom(preservation.before_formula), + subject_id=source_input.subject_id, + phase="BEFORE", + ) + _validate_bound_preservation_atom( + _bound_predicate_atom(preservation.after_formula), + subject_id=source_input.subject_id, + phase="AFTER", + ) + expected_observations = _exact_source_values( + scene.baseline_observations, "baseline observations" + ) + if len(problem.explicit_observation_obligations) != len(expected_observations): + raise ValueError( + "continuous semantic closure source visibility must cover every exact observation" + ) + actual_observation_ids: set[CanonicalId] = set() + for obligation in problem.explicit_observation_obligations: + if type(obligation) is not ObservationObligation: + raise ValueError( + "continuous semantic closure source visibility must use observation obligations" + ) + observation_id = _bound_visibility_observation_id(obligation, scene) + if observation_id in actual_observation_ids: + raise ValueError( + "continuous semantic closure source visibility must not duplicate observations" + ) + actual_observation_ids.add(observation_id) + expected_observation_ids = { + observation.observation_id for observation in expected_observations + } + if actual_observation_ids != expected_observation_ids: + raise ValueError( + "continuous semantic closure visibility roster must match the exact scene" + ) + + +def _bound_continuous_grounded_obligations( + problem: CounterfactualProblemIR, +) -> GroundedObligationSet: + """Derive the continuous closure's source-bound obligation roster.""" + + _validate_bound_continuous_semantic_problem(problem) + source_definition_refs = tuple( + definition.definition_ref + for definition in problem.definition_bundle.definitions + ) + return GroundedObligationSet.seal( + before_preconditions=tuple( + GroundedObligation( + context=context, + source_definition_refs=source_definition_refs, + ) + for context in problem.before_preconditions + ), + after_goals=( + GroundedObligation( + context=problem.after_goal, + source_definition_refs=source_definition_refs, + ), + ), + preservation_invariants=tuple( + GroundedObligation( + context=context, + source_definition_refs=source_definition_refs, + ) + for context in problem.preservation_invariants + ), + observation_obligations=tuple( + GroundedObligation( + context=context, + source_definition_refs=source_definition_refs, + ) + for context in problem.explicit_observation_obligations + ), + grounding_entity_sets=(), + ) + + +def build_upright_se2_semantic_closure( + *, + profile_registration: UprightSE2ProfileRegistration, + semantic_problem: CounterfactualProblemIR, + definition_bundle: DefinitionBundle, + grounded_obligations: GroundedObligationSet, + objective_expression: ObjectiveExpression, + executable_policy_bundle: UprightSE2ExecutablePolicyBundle, + resource_policy: ResourcePolicy, +) -> UprightSE2SemanticClosure: + """Seal all definitions, operands, objective policy, and owner builds together.""" + + objective_policy = decode_upright_se2_objective_policy(executable_policy_bundle) + return UprightSE2SemanticClosure.seal( + profile_registration_sha256=profile_registration.profile_registration_sha256, + profile_registration=profile_registration, + semantic_problem=semantic_problem, + definition_bundle=definition_bundle, + predicate_definitions=_upright_semantic_definitions(), + objective_policy=objective_policy, + evaluator_bindings=_upright_semantic_owner_bindings(), + grounded_obligations=grounded_obligations, + objective_expression=objective_expression, + executable_policy_bundle=executable_policy_bundle, + resource_policy=resource_policy, + ) + + +def build_upright_se2_continuous_semantic_closure( + *, + profile_registration: UprightSE2ProfileRegistration, + semantic_problem: CounterfactualProblemIR, + definition_bundle: DefinitionBundle, + grounded_obligations: GroundedObligationSet, + objective_expression: ObjectiveExpression, + executable_policy_bundle: UprightSE2ExecutablePolicyBundle, + resource_policy: ResourcePolicy, +) -> UprightSE2ContinuousSemanticClosure: + """Seal continuous-yaw semantics without widening the cardinal closure.""" + + objective_policy = decode_upright_se2_objective_policy(executable_policy_bundle) + return UprightSE2ContinuousSemanticClosure.seal( + profile_registration_sha256=profile_registration.profile_registration_sha256, + profile_registration=profile_registration, + semantic_problem=semantic_problem, + definition_bundle=definition_bundle, + predicate_definitions=_upright_semantic_definitions(), + objective_policy=objective_policy, + evaluator_bindings=_upright_semantic_owner_bindings(), + grounded_obligations=grounded_obligations, + objective_expression=objective_expression, + executable_policy_bundle=executable_policy_bundle, + resource_policy=resource_policy, + ) + + +class UprightSE2BackendAvailability(HashBoundCanonicalModel): + """Operational availability is separate from the immutable profile hash.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/backend-availability/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "backend_availability_sha256" + + profile_registration_sha256: Sha256Digest + available_capability_refs: tuple[CapabilityRef, ...] + backend_availability_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_staged_availability(self) -> Self: + _require_sorted_unique_by_bytes( + self.available_capability_refs, + "available staged capabilities", + ) + if self.available_capability_refs not in ( + UPRIGHT_SE2_CARDINAL_CAPABILITY_REFS, + UPRIGHT_SE2_STAGED_CAPABILITY_REFS, + ): + raise ValueError( + "availability must be cardinal-only or cardinal-plus-continuous" + ) + return self + + +class UprightSE2CompiledCell(HashBoundCanonicalModel): + """A declarative exact-dyadic M3 cell, not a compiled solver implementation.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/compiled-cell/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "compiled_cell_sha256" + + cell_id: CanonicalId + authorization_sha256: Sha256Digest + x_lower: ExactDyadic + x_upper: ExactDyadic + y_lower: ExactDyadic + y_upper: ExactDyadic + yaw_interval: LiftedYawInterval + compiled_cell_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_cell_bounds(self) -> Self: + if not self.cell_id.startswith("cell:"): + raise ValueError("compiled cell IDs must use the cell namespace") + if self.x_lower.as_fraction > self.x_upper.as_fraction: + raise ValueError("compiled cell x bounds must be ordered") + if self.y_lower.as_fraction > self.y_upper.as_fraction: + raise ValueError("compiled cell y bounds must be ordered") + return self + + +def _lifted_cell_order_key( + cell: UprightSE2CompiledCell, +) -> tuple[Fraction, Fraction, bytes]: + """Return the frozen lifted-yaw order with a canonical cell-ID tie-break.""" + + return ( + cell.yaw_interval.lower.as_fraction, + cell.yaw_interval.upper.as_fraction, + canonical_json_bytes(cell.cell_id), + ) + + +class UprightSE2CoverageArtifact(HashBoundCanonicalModel): + """Canonical roster/coverage data that remains untrusted until checker replay.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/coverage-artifact/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "coverage_artifact_sha256" + + authorization_sha256: Sha256Digest + cells: tuple[UprightSE2CompiledCell, ...] + unresolved_cell_sha256s: tuple[Sha256Digest, ...] + coverage_artifact_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_coverage_roster(self) -> Self: + if not self.cells: + raise ValueError("coverage artifact must name at least one compiled cell") + if self.cells != tuple(sorted(self.cells, key=_lifted_cell_order_key)): + raise ValueError("coverage cells must use ascending exact lifted yaw order") + cell_ids = tuple(cell.cell_id for cell in self.cells) + if len(set(cell_ids)) != len(cell_ids): + raise ValueError("coverage artifact must not duplicate a cell ID") + if self.unresolved_cell_sha256s != tuple( + sorted(set(self.unresolved_cell_sha256s)) + ): + raise ValueError("unresolved cell digests must be sorted and unique") + if any( + cell.authorization_sha256 != self.authorization_sha256 + for cell in self.cells + ): + raise ValueError("coverage cells must bind the same authorization") + return self + + +class UprightSE2ProofLeafDisposition(StrEnum): + """The closed leaf status a later checker must replay without relabelling.""" + + INWARD_FEASIBLE = "INWARD_FEASIBLE" + OUTWARD_INFEASIBLE = "OUTWARD_INFEASIBLE" + PRUNED = "PRUNED" + UNRESOLVED = "UNRESOLVED" + + +class UprightSE2RetainedOwnerOutcomeKind(StrEnum): + """The finite retained-owner result alphabet carried by one proof row.""" + + EXACT = "EXACT" + NUMERIC_GAP = "NUMERIC_GAP" + RESOURCE_LIMIT = "RESOURCE_LIMIT" + UNSUPPORTED = "UNSUPPORTED" + INCOMPLETE = "INCOMPLETE" + FINITE_MISS = "FINITE_MISS" + + +def _proof_usage_total(usages: tuple[ResourceUsage, ...]) -> ResourceUsage: + """Derive one canonical shared-ledger total from non-reset stage usages.""" + + if not usages: + raise ValueError("proof resource aggregation requires at least one usage") + accounting_refs = {usage.accounting_claim_definition_ref for usage in usages} + if len(accounting_refs) != 1: + raise ValueError("proof resource usages must use one accounting claim") + totals: dict[str, float] = {} + for usage in usages: + for entry in usage.entries: + totals[entry.resource_definition_ref] = ( + totals.get(entry.resource_definition_ref, 0.0) + entry.used + ) + entry_values = tuple( + { + "resource_definition_ref": resource_definition_ref, + "used": used, + } + for resource_definition_ref, used in sorted( + totals.items(), key=lambda item: canonical_json_bytes(item[0]) + ) + ) + return ResourceUsage.model_validate( + { + "accounting_claim_definition_ref": next(iter(accounting_refs)), + "entries": entry_values, + "exhausted": any(usage.exhausted for usage in usages), + } + ) + + +class UprightSE2CardinalProofTuple(HashBoundCanonicalModel): + """The one request-authorized operator/reference/pivot/q proof tuple.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/cardinal-proof-tuple/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "cardinal_proof_tuple_sha256" + + authorization: CardinalYawAuthorization + reference_id: CanonicalId + translation_domain: UprightSE2TranslationDomain + compiled_cells: tuple[UprightSE2CompiledCell, ...] + cardinal_proof_tuple_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_tuple_cells(self) -> Self: + if not self.compiled_cells: + raise ValueError("proof tuple must retain at least one compiled cell") + if tuple(cell.authorization_sha256 for cell in self.compiled_cells) != ( + self.authorization.cardinal_yaw_authorization_sha256, + ) * len(self.compiled_cells): + raise ValueError("proof tuple cells must bind its authorization") + if self.compiled_cells != tuple( + sorted(self.compiled_cells, key=_lifted_cell_order_key) + ): + raise ValueError("proof tuple cells must use canonical lifted-yaw order") + if len({cell.cell_id for cell in self.compiled_cells}) != len( + self.compiled_cells + ): + raise ValueError("proof tuple must not duplicate a compiled cell") + return self + + +class UprightSE2RetainedOwnerEvaluation(HashBoundCanonicalModel): + """One exact retained-owner outcome, bounds, findings, proof rows, and delta.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/retained-owner-evaluation/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "retained_owner_evaluation_sha256" + + compiled_cell: UprightSE2CompiledCell + owner_ref: OwnerRef + evaluator_capability_ref: CapabilityRef + outcome_kind: UprightSE2RetainedOwnerOutcomeKind + exact_bounds: tuple[TypedValue, ...] + finding_codes: tuple[CanonicalId, ...] + proof_rows: tuple[CanonicalId, ...] + resource_delta: ResourceUsage + retained_owner_evaluation_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_retained_owner_outcome(self) -> Self: + _require_sorted_unique_by_bytes(self.exact_bounds, "proof exact bounds") + _require_sorted_unique_by_bytes(self.finding_codes, "proof finding codes") + _require_sorted_unique_by_bytes(self.proof_rows, "proof rows") + if not self.proof_rows: + raise ValueError("retained owner evaluation must retain proof rows") + if any(type(bound.payload) is DigestValue for bound in self.exact_bounds): + raise ValueError("proof exact bounds must not be digest-only") + if self.outcome_kind is UprightSE2RetainedOwnerOutcomeKind.EXACT: + if not self.exact_bounds or self.finding_codes: + raise ValueError( + "exact retained owner evaluations require bounds and no finding" + ) + return self + if self.exact_bounds or not self.finding_codes: + raise ValueError( + "nonexact retained owner evaluations require findings and no exact bounds" + ) + expected_prefix = f"finding:spatialcf/upright-se2/proof-transport/{self.outcome_kind.value.lower()}" + if any(not code.startswith(expected_prefix) for code in self.finding_codes): + raise ValueError("retained owner finding must match its typed outcome") + return self + + +class UprightSE2ProofCellEvaluation(HashBoundCanonicalModel): + """Every retained-owner result for one exact cell and optional leaf status.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/proof-cell-evaluation/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "proof_cell_evaluation_sha256" + + compiled_cell: UprightSE2CompiledCell + owner_evaluations: tuple[UprightSE2RetainedOwnerEvaluation, ...] + leaf_disposition: UprightSE2ProofLeafDisposition | None + complete_domain_empty: StrictBool | None + proof_cell_evaluation_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_leaf_disposition(self) -> Self: + _require_sorted_unique_by_bytes( + self.owner_evaluations, + "proof cell retained owner evaluations", + ) + if not self.owner_evaluations: + raise ValueError("proof cell must retain owner evaluations") + if any( + canonical_json_bytes(evaluation.compiled_cell) + != canonical_json_bytes(self.compiled_cell) + for evaluation in self.owner_evaluations + ): + raise ValueError("proof owner evaluation must bind its exact common cell") + all_exact = all( + evaluation.outcome_kind is UprightSE2RetainedOwnerOutcomeKind.EXACT + for evaluation in self.owner_evaluations + ) + any_nonexact = not all_exact + if self.leaf_disposition is None: + if self.complete_domain_empty is not None: + raise ValueError( + "internal proof cells must not claim complete-domain empty" + ) + return self + if self.complete_domain_empty is None: + raise ValueError("proof leaf disposition requires an explicit empty claim") + if self.leaf_disposition is UprightSE2ProofLeafDisposition.INWARD_FEASIBLE: + if not all_exact or self.complete_domain_empty: + raise ValueError( + "inward-feasible proof cells require exact nonempty evidence" + ) + return self + if self.leaf_disposition is UprightSE2ProofLeafDisposition.OUTWARD_INFEASIBLE: + if not all_exact or not self.complete_domain_empty: + raise ValueError( + "complete-domain empty proof cells require exact outward-infeasible evidence" + ) + return self + if self.leaf_disposition is UprightSE2ProofLeafDisposition.PRUNED: + if self.complete_domain_empty: + raise ValueError( + "pruned proof cells cannot claim complete-domain empty" + ) + return self + if self.complete_domain_empty or not any_nonexact: + raise ValueError( + "unresolved proof cells require nonexact evidence and no complete-domain empty claim" + ) + return self + + +class UprightSE2ExactRational(CanonicalModel): + """One normalized exact rational retained in profile proof transport.""" + + numerator: StrictInt + denominator: Annotated[StrictInt, Field(ge=1)] + + @model_validator(mode="after") + def _validate_normalized_rational(self) -> Self: + if math.gcd(abs(self.numerator), self.denominator) != 1: + raise ValueError("exact rational values must be normalized") + return self + + @property + def as_fraction(self) -> Fraction: + """Return the sole exact arithmetic representation for profile checks.""" + + return Fraction(self.numerator, self.denominator) + + +class UprightSE2SolvePolicyDefinitionPayload(CanonicalModel): + """Request-owned finite-gap and claim-strength policy for M3 replay.""" + + profile_registration_sha256: Sha256Digest + requested_gap: UprightSE2ExactRational + objective_bound_policy_ref: DefinitionRef + exact_global_claim_definition_ref: DefinitionRef + finite_gap_claim_definition_ref: DefinitionRef + + @model_validator(mode="after") + def _validate_requested_gap_and_claims(self) -> Self: + if self.requested_gap.as_fraction < 0: + raise ValueError("requested gap must be non-negative") + if ( + self.exact_global_claim_definition_ref + == self.finite_gap_claim_definition_ref + ): + raise ValueError("exact and finite-gap claims must be distinct") + return self + + +def _solve_policy_rational_value(value: UprightSE2ExactRational) -> TypedValue: + return _materialized_state_record( + _UPRIGHT_SE2_EXACT_RATIONAL_SCHEMA_REF, + ( + ( + "denominator", + TypedValue( + value_schema_ref=_UPRIGHT_SE2_INTEGER_SCHEMA_REF, + payload=IntegerValue(value=value.denominator), + ), + ), + ( + "numerator", + TypedValue( + value_schema_ref=_UPRIGHT_SE2_INTEGER_SCHEMA_REF, + payload=IntegerValue(value=value.numerator), + ), + ), + ), + ) + + +def build_upright_se2_solve_policy_definition_bundle( + registration: UprightSE2ProfileRegistration, + *, + requested_gap: UprightSE2ExactRational, + objective_bound_policy_ref: DefinitionRef, + exact_global_claim_definition_ref: DefinitionRef = UPRIGHT_SE2_EXACT_GLOBAL_CLAIM_DEFINITION_REF, + finite_gap_claim_definition_ref: DefinitionRef = UPRIGHT_SE2_FINITE_GAP_CLAIM_DEFINITION_REF, +) -> DefinitionBundle: + """Seal the request's exact gap and claim policy into its definition closure.""" + + if type(registration) is not UprightSE2ProfileRegistration: + raise TypeError("solve policy requires an exact upright se2 registration") + if type(requested_gap) is not UprightSE2ExactRational: + raise TypeError("solve policy requested gap requires an exact rational") + payload = UprightSE2SolvePolicyDefinitionPayload( + profile_registration_sha256=registration.profile_registration_sha256, + requested_gap=requested_gap, + objective_bound_policy_ref=objective_bound_policy_ref, + exact_global_claim_definition_ref=exact_global_claim_definition_ref, + finite_gap_claim_definition_ref=finite_gap_claim_definition_ref, + ) + fields = ( + ( + "exact_global_claim_definition_ref", + TypedValue( + value_schema_ref=_UPRIGHT_SE2_ID_SCHEMA_REF, + payload=CanonicalIdValue( + value=payload.exact_global_claim_definition_ref + ), + ), + ), + ( + "finite_gap_claim_definition_ref", + TypedValue( + value_schema_ref=_UPRIGHT_SE2_ID_SCHEMA_REF, + payload=CanonicalIdValue(value=payload.finite_gap_claim_definition_ref), + ), + ), + ( + "objective_bound_policy_ref", + TypedValue( + value_schema_ref=_UPRIGHT_SE2_ID_SCHEMA_REF, + payload=CanonicalIdValue(value=payload.objective_bound_policy_ref), + ), + ), + ( + "profile_registration_sha256", + TypedValue( + value_schema_ref=_UPRIGHT_SE2_DIGEST_SCHEMA_REF, + payload=DigestValue(value=payload.profile_registration_sha256), + ), + ), + ("requested_gap", _solve_policy_rational_value(payload.requested_gap)), + ) + return DefinitionBundle.seal( + definitions=( + CanonicalDefinitionEnvelope.seal( + definition_ref=UPRIGHT_SE2_SOLVE_POLICY_DEFINITION_REF, + definition_kind_ref="definition:spatialcf/upright-se2/definition-kind/1.0", + payload_schema_ref=UPRIGHT_SE2_SOLVE_POLICY_PAYLOAD_SCHEMA_REF, + payload=_materialized_state_record( + UPRIGHT_SE2_SOLVE_POLICY_PAYLOAD_SCHEMA_REF, fields + ), + ), + ) + ) + + +def decode_upright_se2_solve_policy_definition_payload( + bundle: DefinitionBundle, +) -> UprightSE2SolvePolicyDefinitionPayload: + """Decode only the registered canonical request-owned solve-policy payload.""" + + if type(bundle) is not DefinitionBundle or len(bundle.definitions) != 1: + raise ValueError("solve policy bundle must contain one registered definition") + definition = bundle.definitions[0] + if ( + definition.definition_ref != UPRIGHT_SE2_SOLVE_POLICY_DEFINITION_REF + or definition.payload_schema_ref != UPRIGHT_SE2_SOLVE_POLICY_PAYLOAD_SCHEMA_REF + or definition.payload.value_schema_ref + != UPRIGHT_SE2_SOLVE_POLICY_PAYLOAD_SCHEMA_REF + or type(definition.payload.payload) is not RecordValue + ): + raise ValueError("solve policy bundle has the wrong registered payload") + fields = {field.name: field.value for field in definition.payload.payload.fields} + if set(fields) != { + "exact_global_claim_definition_ref", + "finite_gap_claim_definition_ref", + "objective_bound_policy_ref", + "profile_registration_sha256", + "requested_gap", + }: + raise ValueError("solve policy payload fields are not closed") + profile = fields["profile_registration_sha256"] + requested_gap = fields["requested_gap"] + if ( + type(profile.payload) is not DigestValue + or type(requested_gap.payload) is not RecordValue + ): + raise ValueError("solve policy payload has invalid scalar forms") + gap_fields = {field.name: field.value for field in requested_gap.payload.fields} + if set(gap_fields) != {"numerator", "denominator"} or any( + type(gap_fields[name].payload) is not IntegerValue + for name in ("numerator", "denominator") + ): + raise ValueError("solve policy requested gap is not an exact rational") + + def canonical_id(name: str) -> str: + value = fields[name] + if type(value.payload) is not CanonicalIdValue: + raise ValueError("solve policy reference is not canonical") + return value.payload.value + + return UprightSE2SolvePolicyDefinitionPayload( + profile_registration_sha256=profile.payload.value, + requested_gap=UprightSE2ExactRational( + numerator=gap_fields["numerator"].payload.value, + denominator=gap_fields["denominator"].payload.value, + ), + objective_bound_policy_ref=canonical_id("objective_bound_policy_ref"), + exact_global_claim_definition_ref=canonical_id( + "exact_global_claim_definition_ref" + ), + finite_gap_claim_definition_ref=canonical_id("finite_gap_claim_definition_ref"), + ) + + +class UprightSE2ProposalPointTerm(CanonicalModel): + """One ordered exact retained semantic T/A/R/V/S point-term interval.""" + + term_id: Literal["T", "A", "R", "V", "S"] + lower: UprightSE2ExactRational + upper: UprightSE2ExactRational + + @model_validator(mode="after") + def _validate_ordered_interval(self) -> Self: + if self.lower.as_fraction > self.upper.as_fraction: + raise ValueError("point term interval bounds must be ordered") + return self + + +class UprightSE2ProposalPointObjective(CanonicalModel): + """The complete interval point objective copied from retained owner output.""" + + terms: tuple[UprightSE2ProposalPointTerm, ...] + total_lower: UprightSE2ExactRational + total_upper: UprightSE2ExactRational + + @model_validator(mode="after") + def _validate_complete_interval_point_objective(self) -> Self: + if tuple(term.term_id for term in self.terms) != ("T", "A", "R", "V", "S"): + raise ValueError("point objective must contain ordered T/A/R/V/S terms") + if self.total_lower.as_fraction > self.total_upper.as_fraction: + raise ValueError("point objective total interval bounds must be ordered") + return self + + +_UPRIGHT_SE2_RETAINED_POINT_OBJECTIVE_SCHEMA_REF = ( + "schema:spatialcf/upright-se2/retained-point-objective/2.0" +) +_UPRIGHT_SE2_RETAINED_POINT_TERM_SCHEMA_REF = ( + "schema:spatialcf/upright-se2/retained-point-term/2.0" +) +_UPRIGHT_SE2_RETAINED_POINT_TERM_ROSTER_SCHEMA_REF = ( + "schema:spatialcf/upright-se2/retained-point-term-roster/2.0" +) +_UPRIGHT_SE2_EXACT_RATIONAL_SCHEMA_REF = ( + "schema:spatialcf/upright-se2/exact-rational/1.0" +) + + +def _retained_point_objective_value( + objective: UprightSE2ProposalPointObjective, +) -> TypedValue: + """Encode the exact owner output transported into a proposal point record.""" + + def rational(value: UprightSE2ExactRational) -> TypedValue: + return _materialized_state_record( + _UPRIGHT_SE2_EXACT_RATIONAL_SCHEMA_REF, + ( + ( + "numerator", + TypedValue( + value_schema_ref=_UPRIGHT_SE2_INTEGER_SCHEMA_REF, + payload=IntegerValue(value=value.numerator), + ), + ), + ( + "denominator", + TypedValue( + value_schema_ref=_UPRIGHT_SE2_INTEGER_SCHEMA_REF, + payload=IntegerValue(value=value.denominator), + ), + ), + ), + ) + + terms = tuple( + _materialized_state_record( + _UPRIGHT_SE2_RETAINED_POINT_TERM_SCHEMA_REF, + ( + ( + "term_id", + TypedValue( + value_schema_ref=_UPRIGHT_SE2_ENUM_SCHEMA_REF, + payload=EnumSymbolValue(symbol=term.term_id), + ), + ), + ("lower", rational(term.lower)), + ("upper", rational(term.upper)), + ), + ) + for term in objective.terms + ) + return _materialized_state_record( + _UPRIGHT_SE2_RETAINED_POINT_OBJECTIVE_SCHEMA_REF, + ( + ( + "terms", + TypedValue( + value_schema_ref=( + _UPRIGHT_SE2_RETAINED_POINT_TERM_ROSTER_SCHEMA_REF + ), + payload=FiniteOrderedTupleValue( + element_schema_ref=_UPRIGHT_SE2_RETAINED_POINT_TERM_SCHEMA_REF, + items=terms, + ), + ), + ), + ("total_lower", rational(objective.total_lower)), + ("total_upper", rational(objective.total_upper)), + ), + ) + + +def _retained_point_owner_objective( + owner_evaluations: tuple[UprightSE2RetainedOwnerEvaluation, ...], +) -> TypedValue: + """Require exactly one retained exact-bound payload for the point objective.""" + + matches = tuple( + bound + for evaluation in owner_evaluations + for bound in evaluation.exact_bounds + if bound.value_schema_ref == _UPRIGHT_SE2_RETAINED_POINT_OBJECTIVE_SCHEMA_REF + ) + if len(matches) != 1: + raise ValueError( + "proposal point evaluation requires one retained point owner objective" + ) + return matches[0] + + +class UprightSE2ProposalPointEvaluation(HashBoundCanonicalModel): + """Exact retained point-cell output transported without invoking a kernel.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/proposal-point-evaluation/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "proposal_point_evaluation_sha256" + + point_cell_evaluation: UprightSE2ProofCellEvaluation + point_objective: UprightSE2ProposalPointObjective + proposal_point_evaluation_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_exact_point_transport(self) -> Self: + cell_evaluation = self.point_cell_evaluation + cell = cell_evaluation.compiled_cell + if ( + cell_evaluation.leaf_disposition is None + and cell_evaluation.complete_domain_empty is not None + ): + raise ValueError("proposal point evaluation must retain an internal cell") + if cell_evaluation.leaf_disposition is not None and ( + cell_evaluation.leaf_disposition + is not UprightSE2ProofLeafDisposition.INWARD_FEASIBLE + or cell_evaluation.complete_domain_empty is not False + ): + raise ValueError( + "proposal point evaluation may reuse only an inward-feasible final cell" + ) + if cell.x_lower != cell.x_upper or cell.y_lower != cell.y_upper: + raise ValueError( + "proposal point evaluation requires a degenerate point cell" + ) + if any( + evaluation.outcome_kind is not UprightSE2RetainedOwnerOutcomeKind.EXACT + for evaluation in cell_evaluation.owner_evaluations + ): + raise ValueError( + "proposal point evaluation requires exact retained owner evidence" + ) + retained_objective = _retained_point_owner_objective( + cell_evaluation.owner_evaluations + ) + if canonical_json_bytes(retained_objective) != canonical_json_bytes( + _retained_point_objective_value(self.point_objective) + ): + raise ValueError( + "proposal point objective must byte-bind retained point owner output" + ) + return self + + +class UprightSE2ProofPruneDecision(HashBoundCanonicalModel): + """One inspectable deterministic prune decision for a retained cell row.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/proof-prune-decision/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "proof_prune_decision_sha256" + + cell_evaluation: UprightSE2ProofCellEvaluation + prune_reason_codes: tuple[CanonicalId, ...] + proof_prune_decision_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_prune_decision(self) -> Self: + _require_sorted_unique_by_bytes( + self.prune_reason_codes, + "proof prune reason codes", + ) + if ( + self.cell_evaluation.leaf_disposition + is not UprightSE2ProofLeafDisposition.PRUNED + or not self.prune_reason_codes + ): + raise ValueError("proof prune decision must retain one pruned cell reason") + return self + + +class UprightSE2ProofFrontierRow(HashBoundCanonicalModel): + """One final unresolved frontier row with the full nonexact cell evidence.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/proof-frontier-row/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "proof_frontier_row_sha256" + + cell_evaluation: UprightSE2ProofCellEvaluation + frontier_reason_codes: tuple[CanonicalId, ...] + proof_frontier_row_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_frontier_row(self) -> Self: + _require_sorted_unique_by_bytes( + self.frontier_reason_codes, + "proof frontier reason codes", + ) + if ( + self.cell_evaluation.leaf_disposition + is not UprightSE2ProofLeafDisposition.UNRESOLVED + or not self.frontier_reason_codes + ): + raise ValueError( + "proof frontier row must retain one unresolved cell reason" + ) + return self + + +class UprightSE2ProofStageDelta(HashBoundCanonicalModel): + """One shared-ledger stage that retains its full owner rows and resource delta.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/proof-stage-delta/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "proof_stage_delta_sha256" + + stage_ref: CanonicalId + owner_evaluations: tuple[UprightSE2RetainedOwnerEvaluation, ...] + resource_delta: ResourceUsage + proof_stage_delta_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_stage_delta(self) -> Self: + if not self.stage_ref.startswith("stage:"): + raise ValueError("proof stage references must use the stage namespace") + _require_sorted_unique_by_bytes( + self.owner_evaluations, + "proof stage owner evaluations", + ) + if not self.owner_evaluations: + raise ValueError("proof stage must retain at least one owner evaluation") + expected = _proof_usage_total( + tuple(evaluation.resource_delta for evaluation in self.owner_evaluations) + ) + if canonical_json_bytes(self.resource_delta) != canonical_json_bytes(expected): + raise ValueError("proof stage resource delta does not match owner rows") + return self + + +class UprightSE2ProofResourceLedger(HashBoundCanonicalModel): + """The single canonical total resource ledger carried by the proof payload.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/proof-resource-ledger/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "proof_resource_ledger_sha256" + + stage_deltas: tuple[UprightSE2ProofStageDelta, ...] + canonical_total_resource_usage: ResourceUsage + proof_resource_ledger_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_shared_ledger(self) -> Self: + _require_sorted_unique_by_bytes(self.stage_deltas, "proof stage deltas") + if not self.stage_deltas: + raise ValueError("proof resource ledger must retain stage deltas") + if len({stage.stage_ref for stage in self.stage_deltas}) != len( + self.stage_deltas + ): + raise ValueError("proof stage references must be unique") + expected = _proof_usage_total( + tuple(stage.resource_delta for stage in self.stage_deltas) + ) + if canonical_json_bytes( + self.canonical_total_resource_usage + ) != canonical_json_bytes(expected): + raise ValueError( + "proof resource ledger canonical total does not match stages" + ) + return self + + +def _proof_cell_is_within_root( + cell: UprightSE2CompiledCell, + root: UprightSE2CompiledCell, +) -> bool: + """Return whether one proof row is a yaw-bound exact-dyadic root descendant.""" + + return ( + cell.authorization_sha256 == root.authorization_sha256 + and cell.yaw_interval == root.yaw_interval + and root.x_lower.as_fraction <= cell.x_lower.as_fraction + and cell.x_upper.as_fraction <= root.x_upper.as_fraction + and root.y_lower.as_fraction <= cell.y_lower.as_fraction + and cell.y_upper.as_fraction <= root.y_upper.as_fraction + ) + + +def _proof_root_for_cell( + cell: UprightSE2CompiledCell, + roots: tuple[UprightSE2CompiledCell, ...], +) -> UprightSE2CompiledCell: + """Resolve exactly one compilation root for a retained proof cell.""" + + matches = tuple(root for root in roots if _proof_cell_is_within_root(cell, root)) + if len(matches) != 1: + raise ValueError( + "proof cells must bind one authorization/yaw compilation root domain" + ) + return matches[0] + + +def _proof_validate_leaf_partition( + roots: tuple[UprightSE2CompiledCell, ...], + leaves: tuple[UprightSE2CompiledCell, ...], +) -> None: + """Require final leaves to partition roots with lower-owned split seams. + + Retained-owner geometry remains closed. Proof coverage instead assigns an + internal X/Y seam to its lower child, while retaining the root's outer + upper boundary. A degenerate axis has one owned coordinate and cannot be + widened or subdivided into synthetic area. + """ + + leaves_by_root: dict[bytes, list[UprightSE2CompiledCell]] = { + canonical_json_bytes(root): [] for root in roots + } + for leaf in leaves: + root = _proof_root_for_cell(leaf, roots) + if ( + root.x_lower.as_fraction < root.x_upper.as_fraction + and leaf.x_lower.as_fraction >= leaf.x_upper.as_fraction + ) or ( + root.y_lower.as_fraction < root.y_upper.as_fraction + and leaf.y_lower.as_fraction >= leaf.y_upper.as_fraction + ): + raise ValueError( + "proof final leaves must not collapse a nondegenerate root axis" + ) + leaves_by_root[canonical_json_bytes(root)].append(leaf) + + for root in roots: + root_key = canonical_json_bytes(root) + root_leaves = tuple(leaves_by_root[root_key]) + if not root_leaves: + raise ValueError("proof coverage leaves must cover every compilation root") + x_coordinates = tuple( + sorted( + { + root.x_lower.as_fraction, + root.x_upper.as_fraction, + *( + coordinate + for leaf in root_leaves + for coordinate in ( + leaf.x_lower.as_fraction, + leaf.x_upper.as_fraction, + ) + ), + } + ) + ) + y_coordinates = tuple( + sorted( + { + root.y_lower.as_fraction, + root.y_upper.as_fraction, + *( + coordinate + for leaf in root_leaves + for coordinate in ( + leaf.y_lower.as_fraction, + leaf.y_upper.as_fraction, + ) + ), + } + ) + ) + x_segments = ( + ((root.x_lower.as_fraction, root.x_upper.as_fraction),) + if root.x_lower.as_fraction == root.x_upper.as_fraction + else tuple(pairwise(x_coordinates)) + ) + y_segments = ( + ((root.y_lower.as_fraction, root.y_upper.as_fraction),) + if root.y_lower.as_fraction == root.y_upper.as_fraction + else tuple(pairwise(y_coordinates)) + ) + for x_lower, x_upper in x_segments: + for y_lower, y_upper in y_segments: + covering = tuple( + leaf + for leaf in root_leaves + if ( + leaf.x_lower.as_fraction <= x_lower + and x_upper <= leaf.x_upper.as_fraction + and leaf.y_lower.as_fraction <= y_lower + and y_upper <= leaf.y_upper.as_fraction + ) + ) + if len(covering) != 1: + raise ValueError( + "proof coverage leaves must form one exact-dyadic partition" + ) + for x_coordinate in x_coordinates: + for y_coordinate in y_coordinates: + owning = tuple( + leaf + for leaf in root_leaves + if _proof_leaf_owns_coordinate( + lower=leaf.x_lower.as_fraction, + upper=leaf.x_upper.as_fraction, + coordinate=x_coordinate, + root_lower=root.x_lower.as_fraction, + ) + and _proof_leaf_owns_coordinate( + lower=leaf.y_lower.as_fraction, + upper=leaf.y_upper.as_fraction, + coordinate=y_coordinate, + root_lower=root.y_lower.as_fraction, + ) + ) + if len(owning) != 1: + raise ValueError( + "proof coverage leaves must assign each split seam to one lower owner" + ) + + +def _proof_leaf_owns_coordinate( + *, + lower: Fraction, + upper: Fraction, + coordinate: Fraction, + root_lower: Fraction, +) -> bool: + """Apply proof-only lower ownership while retaining the root outer lower.""" + + return (coordinate == root_lower and lower == root_lower) or ( + lower < coordinate <= upper + ) + + +class UprightSE2ProofMaterial(HashBoundCanonicalModel): + """Full hash-bound M3 cardinal proof material prior to independent checking.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/proof-material/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "proof_material_sha256" + + solve_request_sha256: Sha256Digest + semantic_closure_sha256: Sha256Digest + upright_se2_compilation_sha256: Sha256Digest + compilation: UprightSE2Compilation + cardinal_tuple_roster: tuple[UprightSE2CardinalProofTuple, ...] + coverage_artifact: UprightSE2CoverageArtifact + compiled_cell_sha256s: tuple[Sha256Digest, ...] + evaluated_cells: tuple[UprightSE2ProofCellEvaluation, ...] + proposal_order: tuple[UprightSE2ProofCellEvaluation, ...] + proposal_candidates: tuple[UprightSE2ProposalCandidate, ...] = () + prune_decisions: tuple[UprightSE2ProofPruneDecision, ...] + unresolved_frontier: tuple[UprightSE2ProofFrontierRow, ...] + resource_ledger: UprightSE2ProofResourceLedger + proof_material_sha256: Sha256Digest + + @property + def total_resource_usage(self) -> ResourceUsage: + """Expose the exact inner total callers must copy into their outer record.""" + + return self.resource_ledger.canonical_total_resource_usage + + @model_validator(mode="after") + def _validate_proof_material(self) -> Self: + compilation = self.compilation + if ( + self.solve_request_sha256 != compilation.solve_request_sha256 + or self.semantic_closure_sha256 + != compilation.semantic_closure.semantic_closure_sha256 + or self.upright_se2_compilation_sha256 + != compilation.upright_se2_compilation_sha256 + ): + raise ValueError("proof material roots do not bind its compilation") + source_input = _bound_source_input( + compilation.source_solve_request.semantic_problem + ) + if len(self.cardinal_tuple_roster) != 1: + raise ValueError( + "proof tuple roster must contain one request-authorized tuple" + ) + proof_tuple = self.cardinal_tuple_roster[0] + if ( + canonical_json_bytes(proof_tuple.authorization) + != canonical_json_bytes(compilation.operation.authorization) + or proof_tuple.reference_id != source_input.reference_id + or proof_tuple.translation_domain + != compilation.operation.translation_domain + or tuple(canonical_json_bytes(cell) for cell in proof_tuple.compiled_cells) + != tuple(canonical_json_bytes(cell) for cell in compilation.compiled_cells) + or proof_tuple.authorization.operator_ref != source_input.operator_ref + or proof_tuple.authorization.yaw.q != source_input.quarter_turns_ccw + ): + raise ValueError( + "proof tuple roster does not bind the source authorization" + ) + root_cells = proof_tuple.compiled_cells + if any( + ( + root.x_lower, + root.x_upper, + root.y_lower, + root.y_upper, + ) + != ( + proof_tuple.translation_domain.x_lower, + proof_tuple.translation_domain.x_upper, + proof_tuple.translation_domain.y_lower, + proof_tuple.translation_domain.y_upper, + ) + for root in root_cells + ): + raise ValueError( + "proof tuple roots must bind the complete world-XY translation domain" + ) + if ( + self.coverage_artifact.authorization_sha256 + != compilation.operation.authorization_sha256 + ): + raise ValueError( + "proof coverage does not bind the compilation authorization" + ) + _proof_validate_leaf_partition(root_cells, self.coverage_artifact.cells) + expected_cell_sha256s = tuple( + cell.compiled_cell_sha256 for cell in self.coverage_artifact.cells + ) + if self.compiled_cell_sha256s != expected_cell_sha256s: + raise ValueError( + "proof material must bind the complete canonical cell roster" + ) + if self.evaluated_cells != tuple( + sorted( + self.evaluated_cells, + key=lambda row: _lifted_cell_order_key(row.compiled_cell), + ) + ): + raise ValueError( + "proof material evaluated cell roster must use deterministic order" + ) + evaluated_cell_ids = tuple( + row.compiled_cell.cell_id for row in self.evaluated_cells + ) + if len(set(evaluated_cell_ids)) != len(evaluated_cell_ids): + raise ValueError( + "proof material evaluated cells must not duplicate a cell ID" + ) + if len( + {canonical_json_bytes(row.compiled_cell) for row in self.evaluated_cells} + ) != len(self.evaluated_cells): + raise ValueError("proof material evaluated cells must not duplicate a cell") + for row in self.evaluated_cells: + _proof_root_for_cell(row.compiled_cell, root_cells) + final_leaf_rows = tuple( + row for row in self.evaluated_cells if row.leaf_disposition is not None + ) + if tuple( + canonical_json_bytes(row.compiled_cell) for row in final_leaf_rows + ) != tuple(canonical_json_bytes(cell) for cell in self.coverage_artifact.cells): + raise ValueError( + "proof evaluated cell roster has an incomplete, duplicated, or unordered final leaf roster" + ) + + expected_proposals = tuple( + row + for row in final_leaf_rows + if row.leaf_disposition is UprightSE2ProofLeafDisposition.INWARD_FEASIBLE + ) + if tuple(canonical_json_bytes(row) for row in self.proposal_order) != tuple( + canonical_json_bytes(row) for row in expected_proposals + ): + raise ValueError("proof proposal order must be complete and deterministic") + expected_proposal_cells = tuple( + canonical_json_bytes(row) for row in expected_proposals + ) + candidate_cells = tuple( + canonical_json_bytes(candidate.final_inward_cell) + for candidate in self.proposal_candidates + ) + if len(set(candidate_cells)) != len(candidate_cells) or set( + candidate_cells + ) != set(expected_proposal_cells): + raise ValueError("proof proposal candidates must cover final inward cells") + if self.proposal_candidates != tuple( + sorted( + self.proposal_candidates, + key=lambda candidate: candidate.canonical_order_key, + ) + ): + raise ValueError( + "proof proposal candidates must use canonical witness order" + ) + for candidate in self.proposal_candidates: + point_evaluation = candidate.point_evaluation.point_cell_evaluation + point_cell = point_evaluation.compiled_cell + if not any( + canonical_json_bytes(row) == canonical_json_bytes(point_evaluation) + for row in self.evaluated_cells + ): + raise ValueError( + "proposal point evaluation must be present in evaluated cells" + ) + point_root = _proof_root_for_cell(point_cell, root_cells) + reuses_final_row = canonical_json_bytes( + point_evaluation + ) == canonical_json_bytes(candidate.final_inward_cell) + if reuses_final_row: + if ( + canonical_json_bytes(point_cell) != canonical_json_bytes(point_root) + or point_cell.x_lower != point_cell.x_upper + or point_cell.y_lower != point_cell.y_upper + ): + raise ValueError( + "proposal point may reuse only its byte-identical fully degenerate root final row" + ) + else: + if point_evaluation.leaf_disposition is not None: + raise ValueError( + "nondegenerate proposal points require a distinct internal exact point row" + ) + final_cell = candidate.final_inward_cell.compiled_cell + for lower, upper, coordinate in ( + ( + final_cell.x_lower.as_fraction, + final_cell.x_upper.as_fraction, + point_cell.x_lower.as_fraction, + ), + ( + final_cell.y_lower.as_fraction, + final_cell.y_upper.as_fraction, + point_cell.y_lower.as_fraction, + ), + ): + if (lower < upper and not lower < coordinate < upper) or ( + lower == upper and coordinate != lower + ): + raise ValueError( + "nondegenerate proposal points require a strict-interior exact descendant" + ) + owning_leaves = tuple( + row + for row in final_leaf_rows + if _proof_cell_is_within_root(point_cell, row.compiled_cell) + and _proof_leaf_owns_coordinate( + lower=row.compiled_cell.x_lower.as_fraction, + upper=row.compiled_cell.x_upper.as_fraction, + coordinate=point_cell.x_lower.as_fraction, + root_lower=point_root.x_lower.as_fraction, + ) + and _proof_leaf_owns_coordinate( + lower=row.compiled_cell.y_lower.as_fraction, + upper=row.compiled_cell.y_upper.as_fraction, + coordinate=point_cell.y_lower.as_fraction, + root_lower=point_root.y_lower.as_fraction, + ) + ) + if len(owning_leaves) != 1 or canonical_json_bytes( + owning_leaves[0] + ) != canonical_json_bytes(candidate.final_inward_cell): + raise ValueError( + "proposal point cell must be uniquely owned by its final inward leaf" + ) + policy_terms = compilation.semantic_closure.objective_policy.terms + if tuple(term.term_id for term in candidate.point_objective.terms) != tuple( + term.term_id for term in policy_terms + ): + raise ValueError( + "proposal point objective terms do not bind the request policy" + ) + expected_lower = sum( + ( + point_term.lower.as_fraction + * Fraction.from_float(policy_term.weight) + / Fraction.from_float(policy_term.normalizer) + for point_term, policy_term in zip( + candidate.point_objective.terms, + policy_terms, + strict=True, + ) + ), + start=Fraction(0), + ) + expected_upper = sum( + ( + point_term.upper.as_fraction + * Fraction.from_float(policy_term.weight) + / Fraction.from_float(policy_term.normalizer) + for point_term, policy_term in zip( + candidate.point_objective.terms, + policy_terms, + strict=True, + ) + ), + start=Fraction(0), + ) + if ( + candidate.point_objective.total_lower.as_fraction != expected_lower + or candidate.point_objective.total_upper.as_fraction != expected_upper + ): + raise ValueError( + "proposal point objective total does not bind the request policy" + ) + if ( + candidate.materialized_endpoint.upright_se2_compilation_sha256 + != compilation.upright_se2_compilation_sha256 + or candidate.program.semantic_problem_sha256 + != compilation.source_solve_request.semantic_problem_sha256 + or candidate.program.action_space_profile_sha256 + != compilation.semantic_closure.profile_registration.action_space_profile.action_space_profile_sha256 + or candidate.program.before_state_sha256 + != compilation.source_solve_request.semantic_problem.scene_state.scene_state_sha256 + or candidate.program.grounded_obligation_set_sha256 + != compilation.grounded_obligations.grounded_obligation_set_sha256 + or candidate.program.state_delta_manifest + != compilation.state_footprint.state_delta_manifest + ): + raise ValueError( + "proposal candidate program does not bind compilation roots" + ) + expected_pruned = tuple( + row + for row in final_leaf_rows + if row.leaf_disposition is UprightSE2ProofLeafDisposition.PRUNED + ) + if len(self.prune_decisions) != len(expected_pruned) or any( + canonical_json_bytes(decision.cell_evaluation) + != canonical_json_bytes(expected) + for decision, expected in zip( + self.prune_decisions, expected_pruned, strict=True + ) + ): + raise ValueError("proof prune decisions must be complete and deterministic") + expected_unresolved = tuple( + row + for row in final_leaf_rows + if row.leaf_disposition is UprightSE2ProofLeafDisposition.UNRESOLVED + ) + if len(self.unresolved_frontier) != len(expected_unresolved) or any( + canonical_json_bytes(row.cell_evaluation) != canonical_json_bytes(expected) + for row, expected in zip( + self.unresolved_frontier, + expected_unresolved, + strict=True, + ) + ): + raise ValueError( + "proof unresolved frontier must be complete and deterministic" + ) + if self.coverage_artifact.unresolved_cell_sha256s != tuple( + sorted( + row.compiled_cell.compiled_cell_sha256 for row in expected_unresolved + ) + ): + raise ValueError("proof coverage unresolved roster does not match frontier") + + expected_owner_evaluations = tuple( + sorted( + ( + owner_evaluation + for row in self.evaluated_cells + for owner_evaluation in row.owner_evaluations + ), + key=canonical_json_bytes, + ) + ) + staged_owner_evaluations = tuple( + owner_evaluation + for stage in self.resource_ledger.stage_deltas + for owner_evaluation in stage.owner_evaluations + ) + if tuple(sorted(staged_owner_evaluations, key=canonical_json_bytes)) != ( + expected_owner_evaluations + ): + raise ValueError( + "proof shared ledger must account for every owner evaluation" + ) + return self + + +def _proof_wire_record( + fields: tuple[tuple[CanonicalId, TypedValue], ...], +) -> TypedValue: + """Build one canonical record using the sole public proof payload schema.""" + + return TypedValue( + value_schema_ref=UPRIGHT_SE2_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF, + payload=RecordValue( + fields=tuple( + sorted( + (NamedTypedValue(name=name, value=value) for name, value in fields), + key=lambda field: canonical_json_bytes(field.name), + ) + ) + ), + ) + + +def _proof_wire_encode_node(value: object) -> TypedValue: + """Encode every canonical proof leaf structurally under one schema identity.""" + + if isinstance(value, StrEnum): + return TypedValue( + value_schema_ref=UPRIGHT_SE2_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF, + payload=CanonicalIdValue(value=value.value), + ) + if value is None: + return TypedValue( + value_schema_ref=UPRIGHT_SE2_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF, + payload=EnumSymbolValue(symbol="NULL"), + ) + if type(value) is bool: + return TypedValue( + value_schema_ref=UPRIGHT_SE2_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF, + payload=BooleanValue(value=value), + ) + if type(value) is int: + return TypedValue( + value_schema_ref=UPRIGHT_SE2_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF, + payload=IntegerValue(value=value), + ) + if type(value) is float: + return TypedValue( + value_schema_ref=UPRIGHT_SE2_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF, + payload=FiniteRealValue(value=value), + ) + if type(value) is str: + return TypedValue( + value_schema_ref=UPRIGHT_SE2_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF, + payload=CanonicalIdValue(value=value), + ) + if type(value) in (tuple, list): + return TypedValue( + value_schema_ref=UPRIGHT_SE2_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF, + payload=FiniteOrderedTupleValue( + element_schema_ref=UPRIGHT_SE2_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF, + items=tuple(_proof_wire_encode_node(item) for item in value), + ), + ) + if type(value) is dict: + if any(type(name) is not str for name in value): + raise TypeError( + "proof payload records require canonical string field names" + ) + return _proof_wire_record( + tuple((name, _proof_wire_encode_node(item)) for name, item in value.items()) + ) + raise TypeError(f"proof payload cannot encode {type(value).__name__}") + + +def _proof_wire_decode_node(value: TypedValue) -> object: + """Decode one structural proof node while rejecting other schema families.""" + + if value.value_schema_ref != UPRIGHT_SE2_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF: + raise ValueError("proof payload node has the wrong schema") + payload = value.payload + if type(payload) is CanonicalIdValue: + return payload.value + if type(payload) is BooleanValue: + return payload.value + if type(payload) is IntegerValue: + return payload.value + if type(payload) is FiniteRealValue: + return payload.value + if type(payload) is EnumSymbolValue: + if payload.symbol != "NULL": + raise ValueError("proof payload has an unknown scalar discriminator") + return None + if type(payload) is FiniteOrderedTupleValue: + if payload.element_schema_ref != UPRIGHT_SE2_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF: + raise ValueError("proof payload tuple has the wrong element schema") + return tuple(_proof_wire_decode_node(item) for item in payload.items) + if type(payload) is RecordValue: + return { + field.name: _proof_wire_decode_node(field.value) for field in payload.fields + } + raise ValueError("proof payload must be structural rather than a digest-only value") + + +def encode_upright_se2_proof_material(material: UprightSE2ProofMaterial) -> TypedValue: + """Encode one sealed full cardinal proof into the sole M3 typed payload wire.""" + + if type(material) is not UprightSE2ProofMaterial: + raise TypeError("proof material codec requires UprightSE2ProofMaterial") + checked = UprightSE2ProofMaterial.model_validate( + material.model_dump(mode="python", round_trip=True), + strict=True, + ) + return _proof_wire_record( + ( + ( + "definition_ref", + TypedValue( + value_schema_ref=UPRIGHT_SE2_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF, + payload=CanonicalIdValue( + value=UPRIGHT_SE2_PROOF_MATERIAL_DEFINITION_REF + ), + ), + ), + ( + "discriminator", + TypedValue( + value_schema_ref=UPRIGHT_SE2_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF, + payload=EnumSymbolValue( + symbol=UPRIGHT_SE2_PROOF_MATERIAL_DISCRIMINATOR + ), + ), + ), + ( + "proof_material", + _proof_wire_encode_node( + checked.model_dump(mode="python", round_trip=True) + ), + ), + ) + ) + + +def decode_upright_se2_proof_material(value: TypedValue) -> UprightSE2ProofMaterial: + """Decode the one full canonical proof payload and reject all substitutions.""" + + if value.value_schema_ref != UPRIGHT_SE2_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF: + raise ValueError("proof payload has the wrong schema") + if type(value.payload) is not RecordValue: + raise ValueError("proof payload must use its canonical record") + fields = {field.name: field.value for field in value.payload.fields} + if set(fields) != {"definition_ref", "discriminator", "proof_material"}: + raise ValueError("proof payload record has missing or unknown fields") + definition = fields["definition_ref"] + if ( + type(definition.payload) is not CanonicalIdValue + or definition.payload.value != UPRIGHT_SE2_PROOF_MATERIAL_DEFINITION_REF + ): + raise ValueError("proof payload has the wrong definition identity") + discriminator = fields["discriminator"] + if ( + type(discriminator.payload) is not EnumSymbolValue + or discriminator.payload.symbol != UPRIGHT_SE2_PROOF_MATERIAL_DISCRIMINATOR + ): + raise ValueError("proof payload has an unknown discriminator") + raw = _proof_wire_decode_node(fields["proof_material"]) + if type(raw) is not dict: + raise ValueError("proof payload material must be a structural record") + try: + # The wire carries enum members as canonical scalar symbols; strict + # reconstruction would incorrectly demand Python enum instances. The + # exact re-encode comparison below still rejects every coercive or + # non-canonical representation. + material = UprightSE2ProofMaterial.model_validate(raw, strict=False) + except Exception as error: + raise ValueError( + "proof payload does not decode to sealed proof material" + ) from error + if canonical_json_bytes( + encode_upright_se2_proof_material(material) + ) != canonical_json_bytes(value): + raise ValueError("proof payload is not the canonical complete representation") + return material + + +def _continuous_proof_wire_record( + fields: tuple[tuple[CanonicalId, TypedValue], ...], +) -> TypedValue: + """Encode an additive continuous payload without touching cardinal codec.""" + + # ``encode_upright_se2_continuous_proof_material`` has already rebuilt its + # source material through the exact sealed model. Constructing this + # recursive, structural wire from those checked scalar/tuple/record values + # must not repeatedly re-run Pydantic's recursive payload-union validation: + # a real proposal embeds its compiler-materialized endpoint and otherwise + # expands that same checked tree at every nested TypedValue boundary. + # + # These constructions retain the schema tags and canonical named-field + # ordering explicitly. They do not accept caller-provided wire nodes, and + # the public decoder still reconstructs and re-encodes through the sealed + # continuous proof model before accepting any payload. + return TypedValue.model_construct( + value_schema_ref=UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF, + payload=RecordValue.model_construct( + fields=tuple( + sorted( + ( + NamedTypedValue.model_construct(name=name, value=value) + for name, value in fields + ), + key=lambda field: canonical_json_bytes(field.name), + ) + ) + ), + ) + + +def _continuous_proof_wire_encode_node(value: object) -> TypedValue: + """Structurally encode only the additive continuous proof schema.""" + + if isinstance(value, StrEnum): + payload = CanonicalIdValue.model_construct(value=value.value) + elif value is None: + payload = EnumSymbolValue.model_construct(symbol="NULL") + elif type(value) is bool: + payload = BooleanValue.model_construct(value=value) + elif type(value) is int: + payload = IntegerValue.model_construct(value=value) + elif type(value) is float: + payload = FiniteRealValue.model_construct(value=value) + elif type(value) is str: + payload = CanonicalIdValue.model_construct(value=value) + elif type(value) in (tuple, list): + return TypedValue.model_construct( + value_schema_ref=UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF, + payload=FiniteOrderedTupleValue.model_construct( + element_schema_ref=UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF, + items=tuple(_continuous_proof_wire_encode_node(item) for item in value), + ), + ) + elif type(value) is dict: + if any(type(name) is not str for name in value): + raise TypeError("continuous proof payload records require string names") + return _continuous_proof_wire_record( + tuple( + (name, _continuous_proof_wire_encode_node(item)) + for name, item in value.items() + ) + ) + else: + raise TypeError( + f"continuous proof payload cannot encode {type(value).__name__}" + ) + return TypedValue.model_construct( + value_schema_ref=UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF, + payload=payload, + ) + + +def _continuous_proof_wire_decode_node(value: TypedValue) -> object: + if ( + value.value_schema_ref + != UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF + ): + raise ValueError("continuous proof payload node has the wrong schema") + payload = value.payload + if type(payload) is CanonicalIdValue: + return payload.value + if type(payload) is BooleanValue: + return payload.value + if type(payload) is IntegerValue: + return payload.value + if type(payload) is FiniteRealValue: + return payload.value + if type(payload) is EnumSymbolValue: + if payload.symbol != "NULL": + raise ValueError("continuous proof payload has an unknown scalar") + return None + if type(payload) is FiniteOrderedTupleValue: + if ( + payload.element_schema_ref + != UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF + ): + raise ValueError("continuous proof payload tuple has wrong schema") + return tuple(_continuous_proof_wire_decode_node(item) for item in payload.items) + if type(payload) is RecordValue: + return { + field.name: _continuous_proof_wire_decode_node(field.value) + for field in payload.fields + } + raise ValueError("continuous proof payload must be structural") + + +def encode_upright_se2_continuous_proof_material( + material: UprightSE2ContinuousProofMaterial, +) -> TypedValue: + """Encode the independent continuous proof payload wire.""" + + if type(material) is not UprightSE2ContinuousProofMaterial: + raise TypeError("continuous proof codec requires continuous proof material") + checked = UprightSE2ContinuousProofMaterial.model_validate( + material.model_dump(mode="python", round_trip=True), strict=True + ) + return _continuous_proof_wire_record( + ( + ( + "definition_ref", + TypedValue( + value_schema_ref=UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF, + payload=CanonicalIdValue( + value=UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_DEFINITION_REF + ), + ), + ), + ( + "discriminator", + TypedValue( + value_schema_ref=UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF, + payload=EnumSymbolValue( + symbol=UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_DISCRIMINATOR + ), + ), + ), + ( + "proof_material", + _continuous_proof_wire_encode_node( + checked.model_dump(mode="python", round_trip=True) + ), + ), + ) + ) + + +def decode_upright_se2_continuous_proof_material( + value: TypedValue, +) -> UprightSE2ContinuousProofMaterial: + """Decode only the additive continuous proof discriminator/schema pair.""" + + if ( + value.value_schema_ref + != UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_PAYLOAD_SCHEMA_REF + ): + raise ValueError("continuous proof payload has the wrong schema") + if type(value.payload) is not RecordValue: + raise ValueError("continuous proof payload must use a record") + fields = {field.name: field.value for field in value.payload.fields} + if set(fields) != {"definition_ref", "discriminator", "proof_material"}: + raise ValueError("continuous proof payload has missing or unknown fields") + if ( + type(fields["definition_ref"].payload) is not CanonicalIdValue + or fields["definition_ref"].payload.value + != UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_DEFINITION_REF + ): + raise ValueError("continuous proof payload has wrong definition identity") + if ( + type(fields["discriminator"].payload) is not EnumSymbolValue + or fields["discriminator"].payload.symbol + != UPRIGHT_SE2_CONTINUOUS_PROOF_MATERIAL_DISCRIMINATOR + ): + raise ValueError("continuous proof payload has wrong discriminator") + raw = _continuous_proof_wire_decode_node(fields["proof_material"]) + if type(raw) is not dict: + raise ValueError("continuous proof material must be a structural record") + try: + material = UprightSE2ContinuousProofMaterial.model_validate(raw, strict=False) + except Exception as error: + raise ValueError( + "continuous proof payload does not decode to sealed material" + ) from error + if canonical_json_bytes( + encode_upright_se2_continuous_proof_material(material) + ) != canonical_json_bytes(value): + raise ValueError("continuous proof payload is not canonical") + return material + + +class UprightSE2CheckerReplayPolicy(HashBoundCanonicalModel): + """The explicit fresh-policy root consumed by the cardinal checker only.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/checker-replay-policy/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "checker_replay_policy_sha256" + + proof_policy_sha256: Sha256Digest + solve_policy_definition_bundle_sha256: Sha256Digest + requested_gap: UprightSE2ExactRational + objective_bound_policy_ref: DefinitionRef + exact_global_claim_definition_ref: DefinitionRef + finite_gap_claim_definition_ref: DefinitionRef + checker_owner_ref: OwnerRef + checker_capability_ref: CapabilityRef + checker_build_sha256: Sha256Digest + checker_replay_policy_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_static_checker_identity(self) -> Self: + if ( + self.checker_owner_ref != UPRIGHT_SE2_CHECKER_OWNER_REF + or self.checker_capability_ref + != UPRIGHT_SE2_CARDINAL_CHECKER_CAPABILITY_REF + or self.checker_build_sha256 != UPRIGHT_SE2_CHECKER_BUILD_SHA256 + ): + raise ValueError("checker replay policy must bind the registered checker") + return self + + +def build_upright_se2_checker_replay_policy( + proof_policy: ProofPolicy, + solve_policy_definition_bundle: DefinitionBundle, +) -> UprightSE2CheckerReplayPolicy: + """Bind one fresh M1 proof policy to the static M3 checker identity.""" + + if type(proof_policy) is not ProofPolicy: + raise TypeError("checker replay policy requires an exact ProofPolicy") + solve_policy = decode_upright_se2_solve_policy_definition_payload( + solve_policy_definition_bundle + ) + return UprightSE2CheckerReplayPolicy.seal( + proof_policy_sha256=proof_policy.proof_policy_sha256, + solve_policy_definition_bundle_sha256=( + solve_policy_definition_bundle.definition_bundle_sha256 + ), + requested_gap=solve_policy.requested_gap, + objective_bound_policy_ref=solve_policy.objective_bound_policy_ref, + exact_global_claim_definition_ref=( + solve_policy.exact_global_claim_definition_ref + ), + finite_gap_claim_definition_ref=solve_policy.finite_gap_claim_definition_ref, + checker_owner_ref=UPRIGHT_SE2_CHECKER_OWNER_REF, + checker_capability_ref=UPRIGHT_SE2_CARDINAL_CHECKER_CAPABILITY_REF, + checker_build_sha256=UPRIGHT_SE2_CHECKER_BUILD_SHA256, + ) + + +class UprightSE2VerificationBundle(HashBoundCanonicalModel): + """A closed profile/availability/pose/proof bundle for later checker replay.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/verification-bundle/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "verification_bundle_sha256" + + profile_registration_sha256: Sha256Digest + backend_availability_sha256: Sha256Digest + pose_yaw_bindings: tuple[ExplicitPoseYawBinding, ...] + proof_material: UprightSE2ProofMaterial + verification_bundle_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_pose_binding_identity(self) -> Self: + _require_sorted_unique_by_bytes(self.pose_yaw_bindings, "pose/yaw bindings") + entity_ids = tuple(binding.entity_id for binding in self.pose_yaw_bindings) + if len(set(entity_ids)) != len(entity_ids): + raise ValueError( + "verification bundle must reject same entity ID with different bytes" + ) + return self + + +class UprightSE2CompilerClosure(HashBoundCanonicalModel): + """The static profile/definition/owner closure consumed by the pure compiler.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/compiler-closure/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "compiler_closure_sha256" + + profile_registration_sha256: Sha256Digest + definition_bundle_sha256: Sha256Digest + solve_policy_definition_bundle_sha256: Sha256Digest + semantic_closure_sha256: Sha256Digest + policy_bundle_sha256: Sha256Digest + resource_policy_sha256: Sha256Digest + compiler_owner_ref: OwnerRef + compiler_build_sha256: Sha256Digest + compiler_closure_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_static_compiler_owner(self) -> Self: + if self.compiler_owner_ref != UPRIGHT_SE2_COMPILER_OWNER_REF: + raise ValueError("compiler owner reference must be fixed") + if self.compiler_build_sha256 != UPRIGHT_SE2_COMPILER_BUILD_SHA256: + raise ValueError("compiler build digest must be fixed") + return self + + +class UprightSE2TranslationDomain(CanonicalModel): + """The complete exact world-XY search domain for one cardinal tuple.""" + + x_lower: ExactDyadic + x_upper: ExactDyadic + y_lower: ExactDyadic + y_upper: ExactDyadic + + @model_validator(mode="after") + def _validate_bounds(self) -> Self: + if self.x_lower.as_fraction > self.x_upper.as_fraction: + raise ValueError("translation x lower bound must not exceed upper") + if self.y_lower.as_fraction > self.y_upper.as_fraction: + raise ValueError("translation y lower bound must not exceed upper") + return self + + +class UprightSE2CardinalOperation(CanonicalModel): + """One resolved cardinal *domain* transition before endpoint selection. + + The operation deliberately carries the complete authorized world-XY + domain, rather than a selected translation. A later, explicit endpoint + materializer is the only place that may combine this operation with a + concrete world-XY value. + """ + + authorization: CardinalYawAuthorization + translation_domain: UprightSE2TranslationDomain + inverse_quarter_turns_ccw: Annotated[StrictInt, Field(ge=0, le=3)] + maximum_program_steps: Annotated[StrictInt, Field(gt=0)] + maximum_edited_entities: Annotated[StrictInt, Field(gt=0)] + + @property + def subject_id(self) -> CanonicalId: + return self.authorization.subject_id + + @property + def pivot_binding(self) -> FixedPivotBinding: + return self.authorization.pivot_binding + + @property + def quarter_turns_ccw(self) -> int: + return self.authorization.yaw.q + + @property + def authorization_sha256(self) -> Sha256Digest: + return self.authorization.cardinal_yaw_authorization_sha256 + + @model_validator(mode="after") + def _validate_inverse(self) -> Self: + if self.inverse_quarter_turns_ccw != (-self.authorization.yaw.q) % 4: + raise ValueError( + "cardinal inverse must match the exact quarter-turn inverse" + ) + if self.maximum_program_steps != 1: + raise ValueError("upright se2 compilation permits exactly one program step") + if self.maximum_edited_entities != 1: + raise ValueError( + "upright se2 compilation permits exactly one edited entity" + ) + return self + + +class UprightSE2ContinuousOperation(CanonicalModel): + """One resolved continuous domain transition before endpoint selection. + + This is intentionally a sibling of ``UprightSE2CardinalOperation``. It + does not widen that frozen cardinal record, so existing cardinal canonical + bytes, discriminators, and hash domains remain unchanged. + """ + + authorization: ContinuousYawAuthorization + translation_domain: UprightSE2TranslationDomain + maximum_program_steps: Annotated[StrictInt, Field(gt=0)] + maximum_edited_entities: Annotated[StrictInt, Field(gt=0)] + + @property + def subject_id(self) -> CanonicalId: + return self.authorization.subject_id + + @property + def pivot_binding(self) -> FixedPivotBinding: + return self.authorization.pivot_binding + + @property + def yaw_domain(self) -> ContinuousYawDomain: + return self.authorization.yaw_domain + + @property + def authorization_sha256(self) -> Sha256Digest: + return self.authorization.continuous_yaw_authorization_sha256 + + @model_validator(mode="after") + def _validate_limits(self) -> Self: + if self.maximum_program_steps != 1: + raise ValueError("continuous upright se2 permits exactly one program step") + if self.maximum_edited_entities != 1: + raise ValueError("continuous upright se2 permits exactly one edited entity") + return self + + +class UprightSE2StateFootprint(HashBoundCanonicalModel): + """The complete primary/derived/frozen state partition for one transition.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/state-footprint/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "state_footprint_sha256" + + state_delta_manifest: StateDeltaManifest + frozen_leaf_refs: tuple[StateVariableRef, ...] + state_footprint_sha256: Sha256Digest + + @property + def complete_before_leaf_index_sha256(self) -> Sha256Digest: + return self.state_delta_manifest.complete_before_leaf_index_sha256 + + @property + def complete_after_leaf_index_sha256(self) -> Sha256Digest: + return self.state_delta_manifest.complete_after_leaf_index_sha256 + + @model_validator(mode="after") + def _validate_frozen_partition(self) -> Self: + _require_sorted_unique_by_bytes(self.frozen_leaf_refs, "frozen state leaves") + primary = set(self.state_delta_manifest.authorized_primary_writes) + derived = set(self.state_delta_manifest.recomputed_derived_writes) + if primary & set(self.frozen_leaf_refs) or derived & set(self.frozen_leaf_refs): + raise ValueError("frozen leaves must not overlap primary or derived writes") + return self + + +_DerivedSourceFact: TypeAlias = ( + CollisionBodyFactV2 | SupportSurfaceFact | GeometryInstanceV2 | BaselineObservation +) + + +def _derived_source_identifier(source_fact: _DerivedSourceFact) -> CanonicalId: + if isinstance(source_fact, CollisionBodyFactV2): + return source_fact.body_id + if isinstance(source_fact, SupportSurfaceFact): + return source_fact.surface_id + if isinstance(source_fact, GeometryInstanceV2): + return source_fact.geometry_id + return source_fact.observation_id + + +def _exact_source_values( + facts: FactSetV2, + label: str, +) -> tuple[CanonicalModel, ...]: + """Return only the profile-authorized complete source-fact branch.""" + + if ( + facts.availability is not FactAvailabilityV2.KNOWN + or facts.completeness is not FactCompletenessV2.EXACT + or facts.values is None + or facts.inner_values is not None + or facts.outer_values is not None + ): + raise ValueError(f"{label} must be a KNOWN EXACT fact set") + return facts.values + + +def validate_required_upright_support_surface( + scene: CanonicalScene, + subject_id: CanonicalId, +) -> SupportSurfaceFact: + """Resolve the subject's frozen support under the upright world-+Z rule.""" + + subject = next( + ( + object_ + for object_ in _exact_source_values(scene.objects, "objects") + if object_.object_id == subject_id + ), + None, + ) + if ( + subject is None + or subject.support_assignment.availability is not FactAvailabilityV2.KNOWN + or subject.support_assignment.surface_id is None + ): + raise ValueError( + "subject support assignment must be KNOWN with a support surface" + ) + sources = tuple( + surface + for surface in _exact_source_values(scene.support_surfaces, "support surfaces") + if surface.surface_id == subject.support_assignment.surface_id + ) + if len(sources) != 1: + raise ValueError( + "subject support assignment must bind one exact support surface" + ) + + surface = sources[0] + normal = surface.normal_in_anchor + if (normal.x, normal.y, normal.z) != (0.0, 0.0, 1.0): + raise ValueError( + "required support surface must have an exact horizontal world +Z normal" + ) + surface_rotation = surface.anchor_from_surface.rotation + if surface_rotation.x != 0.0 or surface_rotation.y != 0.0: + raise ValueError( + "required support surface frame must preserve exact horizontal world +Z" + ) + if surface.owner_object_id is not None: + owner = next( + ( + object_ + for object_ in _exact_source_values(scene.objects, "objects") + if object_.object_id == surface.owner_object_id + ), + None, + ) + if owner is None: + raise ValueError("required support surface owner must be a scene object") + owner_rotation = owner.pose.world_from_object.rotation + if owner_rotation.x != 0.0 or owner_rotation.y != 0.0: + raise ValueError( + "required support surface owner pose must preserve exact horizontal world +Z" + ) + return surface + + +def _expected_derived_sources( + scene: CanonicalScene, + subject_id: CanonicalId, + fact_kind: Literal["COLLISION", "SUPPORT", "RELATION", "VISIBILITY"], +) -> tuple[_DerivedSourceFact, ...]: + if fact_kind == "COLLISION": + return tuple(_exact_source_values(scene.collision_bodies, "collision bodies")) + if fact_kind == "SUPPORT": + return (validate_required_upright_support_surface(scene, subject_id),) + if fact_kind == "RELATION": + return tuple( + geometry + for geometry in _exact_source_values( + scene.geometry_instances, + "geometry instances", + ) + if geometry.role is GeometryRoleV2.RELATION + ) + return tuple( + _exact_source_values(scene.baseline_observations, "baseline observations") + ) + + +class UprightSE2DerivedAfterFact(CanonicalModel): + """One concrete frozen-source evaluation input at the canonical after pose.""" + + fact_kind: Literal["COLLISION", "SUPPORT", "RELATION", "VISIBILITY"] + source_fact_id: CanonicalId + source_fact_sha256: Sha256Digest + source_fact: _DerivedSourceFact + after_subject_pose: RigidTransformV2 + + @model_validator(mode="after") + def _validate_concrete_source_fact(self) -> Self: + source_type = { + "COLLISION": CollisionBodyFactV2, + "SUPPORT": SupportSurfaceFact, + "RELATION": GeometryInstanceV2, + "VISIBILITY": BaselineObservation, + }[self.fact_kind] + if not isinstance(self.source_fact, source_type): + raise TypeError("derived fact kind does not match its concrete source fact") + if self.fact_kind == "RELATION" and ( + self.source_fact.role is not GeometryRoleV2.RELATION + ): + raise ValueError("relation derived fact must use relation-role geometry") + if _derived_source_identifier(self.source_fact) != self.source_fact_id: + raise ValueError( + "derived fact source ID does not match its concrete source fact" + ) + if ( + canonical_sha256( + self.source_fact, + domain=UPRIGHT_SE2_DERIVED_SOURCE_HASH_DOMAIN, + ) + != self.source_fact_sha256 + ): + raise ValueError( + "derived fact source digest does not bind its concrete source fact" + ) + return self + + +class UprightSE2AfterStateTemplate(HashBoundCanonicalModel): + """A complete endpoint pose template without solver or checker conclusions.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/after-state-template/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "after_state_template_sha256" + + subject_id: CanonicalId + subject_before_pose: RigidTransformV2 + evaluation_scene: CanonicalScene + subject_pivot_xy_m: Vec2 + subject_pivot_z_m: FiniteFloat + subject_pose: RigidTransformV2 + subject_yaw_turns: CanonicalSO2Angle + reference_pivot_xy_m: Vec2 + collision_facts: tuple[UprightSE2DerivedAfterFact, ...] + support_facts: tuple[UprightSE2DerivedAfterFact, ...] + relation_facts: tuple[UprightSE2DerivedAfterFact, ...] + visibility_facts: tuple[UprightSE2DerivedAfterFact, ...] + after_state_template_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_complete_derived_views(self) -> Self: + subject = next( + ( + object_ + for object_ in self.evaluation_scene.objects.values or () + if object_.object_id == self.subject_id + ), + None, + ) + if subject is None: + raise ValueError("after-state evaluation scene must contain the subject") + if subject.pose.world_from_object != self.subject_before_pose: + raise ValueError( + "after-state subject before pose must bind the evaluation scene" + ) + translation = self.subject_pose.translation + if (translation.x, translation.y, translation.z) != ( + self.subject_pivot_xy_m.x, + self.subject_pivot_xy_m.y, + self.subject_pivot_z_m, + ): + raise ValueError("canonical subject pose must match the after pivot") + validate_directed_yaw_quaternion_consistency( + self.subject_yaw_turns, + self.subject_pose.rotation, + ) + _exact_source_values(self.evaluation_scene.cameras, "cameras") + for label, facts in ( + ("collision facts", self.collision_facts), + ("support facts", self.support_facts), + ("relation facts", self.relation_facts), + ("visibility facts", self.visibility_facts), + ): + _require_sorted_unique_by_bytes(facts, label) + if any(fact.after_subject_pose != self.subject_pose for fact in facts): + raise ValueError( + "derived fact after poses must match the canonical subject pose" + ) + for fact_kind, facts in ( + ("COLLISION", self.collision_facts), + ("SUPPORT", self.support_facts), + ("RELATION", self.relation_facts), + ("VISIBILITY", self.visibility_facts), + ): + if any(fact.fact_kind != fact_kind for fact in facts): + raise ValueError("after-state derived fact family is inconsistent") + expected_sources = _expected_derived_sources( + self.evaluation_scene, + self.subject_id, + fact_kind, + ) + expected_by_id = { + _derived_source_identifier(source): source + for source in expected_sources + } + actual_by_id = {fact.source_fact_id: fact.source_fact for fact in facts} + if len(actual_by_id) != len(facts) or set(actual_by_id) != set( + expected_by_id + ): + raise ValueError( + "after-state derived facts must cover the complete affected set" + ) + if any( + canonical_json_bytes(actual_by_id[source_id]) + != canonical_json_bytes(source) + for source_id, source in expected_by_id.items() + ): + raise ValueError( + "after-state derived facts must bind evaluation-scene sources" + ) + return self + + +class UprightSE2EndpointConstructionRecipe(HashBoundCanonicalModel): + """The complete source-bound recipe for a later selected endpoint. + + This is intentionally not an endpoint and does not carry a translation. + It records every source value needed by the pure materializer so a + compilation remains a statement about an authorized domain only. + """ + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/endpoint-construction-recipe/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "endpoint_construction_recipe_sha256" + + source_scene_state_sha256: Sha256Digest + operation_authorization_sha256: Sha256Digest + subject_id: CanonicalId + reference_id: CanonicalId + pivot_binding: FixedPivotBinding + quarter_turns_ccw: Annotated[StrictInt, Field(ge=0, le=3)] + translation_domain: UprightSE2TranslationDomain + subject_before_pose: RigidTransformV2 + reference_before_pose: RigidTransformV2 + subject_yaw_turns: CanonicalSO2Angle + evaluation_scene: CanonicalScene + endpoint_construction_recipe_sha256: Sha256Digest + + @property + def authorization_sha256(self) -> Sha256Digest: + """Expose the bound operation authorization without duplicating it.""" + + return self.operation_authorization_sha256 + + @property + def translation_domain_sha256(self) -> Sha256Digest: + """Return the explicit canonical root of the authorized XY domain.""" + + return canonical_sha256( + self.translation_domain, + domain="spatialcf/counterfactual/upright-se2/translation-domain/3.0", + ) + + @model_validator(mode="after") + def _validate_source_bound_recipe(self) -> Self: + subject = _bound_scene_object(self.evaluation_scene, self.subject_id) + reference = _bound_scene_object(self.evaluation_scene, self.reference_id) + if self.subject_id == self.reference_id: + raise ValueError("endpoint recipe subject and reference must differ") + if subject.pose.world_from_object != self.subject_before_pose: + raise ValueError( + "endpoint recipe subject pose must bind its evaluation scene" + ) + if reference.pose.world_from_object != self.reference_before_pose: + raise ValueError( + "endpoint recipe reference pose must bind its evaluation scene" + ) + validate_directed_yaw_quaternion_consistency( + self.subject_yaw_turns, + self.subject_before_pose.rotation, + ) + expected_pivot_id = ( + self.subject_id + if self.pivot_binding.pivot_mode is PivotMode.OWN + else self.reference_id + ) + if self.pivot_binding.pivot_entity_id != expected_pivot_id: + raise ValueError( + "endpoint recipe pivot must bind its selected scene object" + ) + expected_state_sha256 = canonical_sha256( + { + "scene_state_sha256": self.source_scene_state_sha256, + "pivot_entity_id": expected_pivot_id, + "object_pivot_pose": ( + subject.pose + if self.pivot_binding.pivot_mode is PivotMode.OWN + else reference.pose + ), + }, + domain=_UPRIGHT_SE2_PIVOT_STATE_HASH_DOMAIN, + ) + if self.pivot_binding.pivot_state_sha256 != expected_state_sha256: + raise ValueError( + "endpoint recipe pivot digest must bind the source scene state" + ) + return self + + +class UprightSE2ContinuousEndpointConstructionRecipe(HashBoundCanonicalModel): + """Source-bound continuous endpoint recipe, separate from cardinal bytes.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/continuous-endpoint-construction-recipe/1.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "continuous_endpoint_construction_recipe_sha256" + + source_scene_state_sha256: Sha256Digest + operation_authorization_sha256: Sha256Digest + subject_id: CanonicalId + reference_id: CanonicalId + pivot_binding: FixedPivotBinding + yaw_domain: ContinuousYawDomain + translation_domain: UprightSE2TranslationDomain + subject_before_pose: RigidTransformV2 + reference_before_pose: RigidTransformV2 + subject_yaw_turns: CanonicalSO2Angle + evaluation_scene: CanonicalScene + continuous_endpoint_construction_recipe_sha256: Sha256Digest + + @property + def authorization_sha256(self) -> Sha256Digest: + return self.operation_authorization_sha256 + + @property + def translation_domain_sha256(self) -> Sha256Digest: + return canonical_sha256( + self.translation_domain, + domain="spatialcf/counterfactual/upright-se2/translation-domain/3.0", + ) + + @model_validator(mode="after") + def _validate_source_bound_recipe(self) -> Self: + subject = _bound_scene_object(self.evaluation_scene, self.subject_id) + reference = _bound_scene_object(self.evaluation_scene, self.reference_id) + if self.subject_id == self.reference_id: + raise ValueError("continuous endpoint subject and reference must differ") + if subject.pose.world_from_object != self.subject_before_pose: + raise ValueError("continuous endpoint subject pose must bind its scene") + if reference.pose.world_from_object != self.reference_before_pose: + raise ValueError("continuous endpoint reference pose must bind its scene") + validate_directed_yaw_quaternion_consistency( + self.subject_yaw_turns, self.subject_before_pose.rotation + ) + expected_pivot_id = ( + self.subject_id + if self.pivot_binding.pivot_mode is PivotMode.OWN + else self.reference_id + ) + if self.pivot_binding.pivot_entity_id != expected_pivot_id: + raise ValueError("continuous endpoint pivot must bind its scene object") + expected_state_sha256 = canonical_sha256( + { + "scene_state_sha256": self.source_scene_state_sha256, + "pivot_entity_id": expected_pivot_id, + "object_pivot_pose": ( + subject.pose + if self.pivot_binding.pivot_mode is PivotMode.OWN + else reference.pose + ), + }, + domain=_UPRIGHT_SE2_PIVOT_STATE_HASH_DOMAIN, + ) + if self.pivot_binding.pivot_state_sha256 != expected_state_sha256: + raise ValueError("continuous endpoint pivot digest does not bind source") + return self + + +def _continuous_canonical_yaw_after( + yaw_before: CanonicalSO2Angle, + selected_lifted_yaw: ExactDyadic, +) -> CanonicalSO2Angle: + """Apply an authorized lifted delta through the directed-yaw owner.""" + + turns = Fraction.from_float(yaw_before.turns) + selected_lifted_yaw.as_fraction + while turns < Fraction(-1, 2): + turns += 1 + while turns >= Fraction(1, 2): + turns -= 1 + return CanonicalSO2Angle(turns=0.0 if turns == 0 else float(turns)) + + +def _continuous_materialized_expected_pose( + recipe: UprightSE2ContinuousEndpointConstructionRecipe, + translation_xy_m: Vec2, + selected_lifted_yaw: ExactDyadic, +) -> tuple[Vec2, CanonicalSO2Angle, RigidTransformV2, Vec2]: + """Replay one continuous endpoint pose from its source-bound recipe.""" + + selected = selected_lifted_yaw.as_fraction + yaw_after = _continuous_canonical_yaw_after( + recipe.subject_yaw_turns, + selected_lifted_yaw, + ) + radians = 2.0 * math.pi * float(selected) + cos_yaw = math.cos(radians) + sin_yaw = math.sin(radians) + subject_pose = recipe.subject_before_pose + reference_pose = recipe.reference_before_pose + pivot_pose = ( + subject_pose + if recipe.pivot_binding.pivot_mode is PivotMode.OWN + else reference_pose + ) + rel_x = subject_pose.translation.x - pivot_pose.translation.x + rel_y = subject_pose.translation.y - pivot_pose.translation.y + subject_after = Vec2( + x=( + 0.0 + if pivot_pose.translation.x + + cos_yaw * rel_x + - sin_yaw * rel_y + + translation_xy_m.x + == 0.0 + else pivot_pose.translation.x + + cos_yaw * rel_x + - sin_yaw * rel_y + + translation_xy_m.x + ), + y=( + 0.0 + if pivot_pose.translation.y + + sin_yaw * rel_x + + cos_yaw * rel_y + + translation_xy_m.y + == 0.0 + else pivot_pose.translation.y + + sin_yaw * rel_x + + cos_yaw * rel_y + + translation_xy_m.y + ), + ) + half_radians = math.pi * float(selected) + delta_z = math.sin(half_radians) + delta_w = math.cos(half_radians) + after_rotation = _compose_upright_quaternion_from_primary_yaw( + rotation=subject_pose.rotation, + delta_z=delta_z, + delta_w=delta_w, + primary_expected_yaw=yaw_after, + ) + return ( + subject_after, + yaw_after, + RigidTransformV2( + translation=Vec3( + x=subject_after.x, + y=subject_after.y, + z=( + 0.0 + if subject_pose.translation.z == 0.0 + else subject_pose.translation.z + ), + ), + rotation=after_rotation, + ), + Vec2( + x=reference_pose.translation.x, + y=reference_pose.translation.y, + ), + ) + + +class UprightSE2MaterializedEndpoint(HashBoundCanonicalModel): + """One explicit selected endpoint, separate from domain-level compilation.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/materialized-endpoint/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "materialized_endpoint_sha256" + + upright_se2_compilation_sha256: Sha256Digest + compilation: UprightSE2Compilation + endpoint_construction_recipe: UprightSE2EndpointConstructionRecipe + translation_xy_m: Vec2 + after_state: UprightSE2AfterStateTemplate + program: EditProgram + materialized_endpoint_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_endpoint_from_recipe(self) -> Self: + compilation = self.compilation + recipe = self.endpoint_construction_recipe + if ( + self.upright_se2_compilation_sha256 + != compilation.upright_se2_compilation_sha256 + or recipe != compilation.endpoint_construction_recipe + ): + raise ValueError( + "materialized endpoint must bind its exact compilation recipe" + ) + x = _fraction_from_float(self.translation_xy_m.x) + y = _fraction_from_float(self.translation_xy_m.y) + if ( + not recipe.translation_domain.x_lower.as_fraction + <= x + <= recipe.translation_domain.x_upper.as_fraction + ): + raise ValueError( + "materialized endpoint translation x is outside its domain" + ) + if ( + not recipe.translation_domain.y_lower.as_fraction + <= y + <= recipe.translation_domain.y_upper.as_fraction + ): + raise ValueError( + "materialized endpoint translation y is outside its domain" + ) + pivot_pose = ( + recipe.subject_before_pose + if recipe.pivot_binding.pivot_mode is PivotMode.OWN + else recipe.reference_before_pose + ) + rotated_x, rotated_y = _bound_rotate_cardinal_xy( + recipe.subject_before_pose.translation.x - pivot_pose.translation.x, + recipe.subject_before_pose.translation.y - pivot_pose.translation.y, + recipe.quarter_turns_ccw, + ) + expected_xy = Vec2( + x=0.0 + if pivot_pose.translation.x + rotated_x + self.translation_xy_m.x == 0.0 + else pivot_pose.translation.x + rotated_x + self.translation_xy_m.x, + y=0.0 + if pivot_pose.translation.y + rotated_y + self.translation_xy_m.y == 0.0 + else pivot_pose.translation.y + rotated_y + self.translation_xy_m.y, + ) + expected_yaw = _bound_cardinal_after_yaw( + recipe.subject_yaw_turns, + recipe.quarter_turns_ccw, + ) + expected_pose = RigidTransformV2( + translation=Vec3( + x=expected_xy.x, + y=expected_xy.y, + z=( + 0.0 + if recipe.subject_before_pose.translation.z == 0.0 + else recipe.subject_before_pose.translation.z + ), + ), + rotation=_bound_cardinal_quaternion( + recipe.subject_before_pose.rotation, + recipe.quarter_turns_ccw, + primary_expected_yaw=expected_yaw, + ), + ) + if ( + self.after_state.evaluation_scene != recipe.evaluation_scene + or self.after_state.subject_id != recipe.subject_id + or self.after_state.subject_before_pose != recipe.subject_before_pose + or self.after_state.reference_pivot_xy_m + != Vec2( + x=recipe.reference_before_pose.translation.x, + y=recipe.reference_before_pose.translation.y, + ) + or self.after_state.subject_pivot_xy_m != expected_xy + or self.after_state.subject_yaw_turns != expected_yaw + or self.after_state.subject_pose != expected_pose + ): + raise ValueError( + "materialized endpoint after state must derive from its recipe" + ) + _validate_materialized_endpoint_program(self) + return self + + @property + def program_sha256(self) -> Sha256Digest: + return self.program.program_sha256 + + @property + def after_scene_state_sha256(self) -> Sha256Digest: + return self.program.after_scene_state_sha256 + + +def _materialized_program_arguments( + compilation: UprightSE2Compilation, translation_xy_m: Vec2 +) -> tuple[OperationArgument, ...]: + """Derive the sole generic invocation payload from sealed endpoint inputs.""" + + return tuple( + sorted( + ( + OperationArgument( + argument_name="argument:spatialcf/upright-se2/pivot-entity-id", + value=TypedValue( + value_schema_ref="schema:spatialcf/upright-se2/entity-id/1.0", + payload=CanonicalIdValue( + value=compilation.operation.pivot_binding.pivot_entity_id + ), + ), + ), + OperationArgument( + argument_name="argument:spatialcf/upright-se2/quarter-turns-ccw", + value=TypedValue( + value_schema_ref="schema:spatialcf/upright-se2/cardinal-yaw/1.0", + payload=IntegerValue( + value=compilation.operation.quarter_turns_ccw + ), + ), + ), + OperationArgument( + argument_name="argument:spatialcf/upright-se2/subject-id", + value=TypedValue( + value_schema_ref="schema:spatialcf/upright-se2/entity-id/1.0", + payload=CanonicalIdValue( + value=compilation.endpoint_construction_recipe.subject_id + ), + ), + ), + OperationArgument( + argument_name="argument:spatialcf/upright-se2/translation-x-m", + value=TypedValue( + value_schema_ref="schema:spatialcf/upright-se2/metre/1.0", + payload=FiniteRealValue(value=translation_xy_m.x), + ), + ), + OperationArgument( + argument_name="argument:spatialcf/upright-se2/translation-y-m", + value=TypedValue( + value_schema_ref="schema:spatialcf/upright-se2/metre/1.0", + payload=FiniteRealValue(value=translation_xy_m.y), + ), + ), + ), + key=lambda argument: canonical_json_bytes(argument.argument_name), + ) + ) + + +def _materialized_derived_bundle( + after_state: UprightSE2AfterStateTemplate, +) -> ExtensionFactBundle: + """Carry each authorized primary/recomputed state value structurally.""" + + role_values = { + "subject-world-x": _materialized_state_real( + after_state.subject_pose.translation.x + ), + "subject-world-y": _materialized_state_real( + after_state.subject_pose.translation.y + ), + "subject-explicit-yaw": _materialized_state_real( + after_state.subject_yaw_turns.turns + ), + "subject-derived-canonical-pose": _materialized_pose_value( + after_state.subject_pose + ), + "subject-derived-collision": _materialized_derived_fact_roster_value( + after_state.collision_facts + ), + "subject-derived-support": _materialized_derived_fact_roster_value( + after_state.support_facts + ), + "subject-derived-relation": _materialized_derived_fact_roster_value( + after_state.relation_facts + ), + "subject-derived-visibility": _materialized_derived_fact_roster_value( + after_state.visibility_facts + ), + } + expected_roles = _UPRIGHT_SE2_PRIMARY_ROLES + _UPRIGHT_SE2_DERIVED_ROLES + if tuple(role_values) != expected_roles: + raise AssertionError("materialized role value roster drift") + + return ExtensionFactBundle.seal( + facts=tuple( + sorted( + ( + ExtensionFact( + fact_family_ref=_UPRIGHT_SE2_STATE_FAMILY_REF, + subject_entity_id=after_state.subject_id, + fact_key=f"fact-key:spatialcf/upright-se2/{role}", + value=role_values[role], + ) + for role in expected_roles + ), + key=canonical_json_bytes, + ) + ) + ) + + +def _materialized_state_real(value: float) -> TypedValue: + return TypedValue( + value_schema_ref=_UPRIGHT_SE2_REAL_SCHEMA_REF, + payload=FiniteRealValue(value=value), + ) + + +def _materialized_state_record( + schema_ref: str, fields: tuple[tuple[str, TypedValue], ...] +) -> TypedValue: + return TypedValue( + value_schema_ref=schema_ref, + payload=RecordValue( + fields=tuple( + sorted( + (NamedTypedValue(name=name, value=value) for name, value in fields), + key=lambda field: canonical_json_bytes(field.name), + ) + ) + ), + ) + + +def _materialized_pose_value(pose: RigidTransformV2) -> TypedValue: + return _materialized_state_record( + "schema:spatialcf/upright-se2/subject-derived-canonical-pose/1.0", + ( + ("translation_x_m", _materialized_state_real(pose.translation.x)), + ("translation_y_m", _materialized_state_real(pose.translation.y)), + ("translation_z_m", _materialized_state_real(pose.translation.z)), + ("rotation_x", _materialized_state_real(pose.rotation.x)), + ("rotation_y", _materialized_state_real(pose.rotation.y)), + ("rotation_z", _materialized_state_real(pose.rotation.z)), + ("rotation_w", _materialized_state_real(pose.rotation.w)), + ), + ) + + +def _materialized_derived_fact_value(fact: UprightSE2DerivedAfterFact) -> TypedValue: + return _materialized_state_record( + "schema:spatialcf/upright-se2/derived-after-fact/1.0", + ( + ( + "fact_kind", + TypedValue( + value_schema_ref="schema:spatialcf/upright-se2/enum-symbol/1.0", + payload=EnumSymbolValue(symbol=fact.fact_kind), + ), + ), + ( + "source_fact_id", + TypedValue( + value_schema_ref=_UPRIGHT_SE2_ID_SCHEMA_REF, + payload=CanonicalIdValue(value=fact.source_fact_id), + ), + ), + ( + "source_fact_sha256", + TypedValue( + value_schema_ref="schema:spatialcf/upright-se2/digest/1.0", + payload=DigestValue(value=fact.source_fact_sha256), + ), + ), + ("after_subject_pose", _materialized_pose_value(fact.after_subject_pose)), + ), + ) + + +def _materialized_derived_fact_roster_value( + facts: tuple[UprightSE2DerivedAfterFact, ...], +) -> TypedValue: + return TypedValue( + value_schema_ref="schema:spatialcf/upright-se2/derived-after-fact-roster/1.0", + payload=FiniteOrderedTupleValue( + element_schema_ref="schema:spatialcf/upright-se2/derived-after-fact/1.0", + items=tuple(_materialized_derived_fact_value(fact) for fact in facts), + ), + ) + + +def _materialized_expected_after_scene_state( + compilation: UprightSE2Compilation | UprightSE2ContinuousCompilation, + after_state: UprightSE2AfterStateTemplate, +) -> SceneStateEnvelope: + """Reconstruct the one complete source-preserving after scene exactly.""" + + before_state = compilation.source_solve_request.semantic_problem.scene_state + source_scene = before_state.base_scene_payload + objects = source_scene.objects.values + if objects is None: + raise ValueError("materialized program source scene must contain exact objects") + replaced = 0 + after_objects = [] + for object_ in objects: + if object_.object_id == after_state.subject_id: + replaced += 1 + after_objects.append( + object_.model_copy( + update={ + "pose": ObjectPose(world_from_object=after_state.subject_pose) + } + ) + ) + else: + after_objects.append(object_) + if replaced != 1: + raise ValueError("materialized program must replace exactly one subject pose") + after_scene = source_scene.model_copy( + update={ + "objects": source_scene.objects.model_copy( + update={"values": tuple(after_objects)} + ) + } + ) + return SceneStateEnvelope.seal( + base_scene_schema_ref=before_state.base_scene_schema_ref, + base_scene_payload=after_scene, + extension_fact_bundles=tuple( + sorted( + ( + *before_state.extension_fact_bundles, + _materialized_derived_bundle(after_state), + ), + key=canonical_json_bytes, + ) + ), + closed_entity_index=before_state.closed_entity_index, + canonical_state_leaf_index=before_state.canonical_state_leaf_index, + ) + + +def _validate_materialized_endpoint_program( + endpoint: UprightSE2MaterializedEndpoint, +) -> None: + """Close retained program fields over one sealed compilation and endpoint.""" + + compilation = endpoint.compilation + source_problem = compilation.source_solve_request.semantic_problem + before_state = source_problem.scene_state + program = endpoint.program + if ( + program.semantic_problem_sha256 != source_problem.semantic_problem_sha256 + or program.action_space_profile_sha256 + != compilation.semantic_closure.profile_registration.action_space_profile.action_space_profile_sha256 + or program.before_state_sha256 != before_state.scene_state_sha256 + or program.grounded_obligation_set_sha256 + != compilation.grounded_obligations.grounded_obligation_set_sha256 + or program.state_delta_manifest + != compilation.state_footprint.state_delta_manifest + ): + raise ValueError("materialized endpoint program roots do not bind compilation") + expected_step = OperationInvocation( + operator_ref=compilation.operation.authorization.operator_ref, + arguments=_materialized_program_arguments( + compilation, endpoint.translation_xy_m + ), + ) + if program.steps != (expected_step,): + raise ValueError( + "materialized endpoint program must contain exactly one bound invocation" + ) + expected_after = _materialized_expected_after_scene_state( + compilation, endpoint.after_state + ) + if ( + program.after_scene_state_sha256 != expected_after.scene_state_sha256 + or canonical_json_bytes(program.after_scene_state) + != canonical_json_bytes(expected_after) + ): + raise ValueError( + "materialized endpoint program must equal the complete expected after scene" + ) + + +class UprightSE2ProposalCandidate(HashBoundCanonicalModel): + """One compiler-materialized inward witness, ordered independently of cells.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/proposal-candidate/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "proposal_candidate_sha256" + + final_inward_cell: UprightSE2ProofCellEvaluation + selected_translation_xy_m: Vec2 + point_evaluation: UprightSE2ProposalPointEvaluation + point_objective: UprightSE2ProposalPointObjective + materialized_endpoint: UprightSE2MaterializedEndpoint + program: EditProgram + proposal_candidate_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_candidate(self) -> Self: + if ( + self.final_inward_cell.leaf_disposition + is not UprightSE2ProofLeafDisposition.INWARD_FEASIBLE + ): + raise ValueError( + "proposal candidates require one final inward-feasible cell" + ) + if canonical_json_bytes(self.point_objective) != canonical_json_bytes( + self.point_evaluation.point_objective + ): + raise ValueError( + "proposal candidate point objective must match retained point evaluation" + ) + if ( + self.selected_translation_xy_m + != self.materialized_endpoint.translation_xy_m + ): + raise ValueError( + "proposal candidate point must match its materialized endpoint" + ) + cell = self.final_inward_cell.compiled_cell + point_x = _fraction_from_float(self.selected_translation_xy_m.x) + point_y = _fraction_from_float(self.selected_translation_xy_m.y) + if not ( + cell.x_lower.as_fraction <= point_x <= cell.x_upper.as_fraction + and cell.y_lower.as_fraction <= point_y <= cell.y_upper.as_fraction + ): + raise ValueError("proposal candidate point is outside its final cell") + point_cell = self.point_evaluation.point_cell_evaluation.compiled_cell + if ( + point_cell.x_lower.as_fraction != point_x + or point_cell.x_upper.as_fraction != point_x + or point_cell.y_lower.as_fraction != point_y + or point_cell.y_upper.as_fraction != point_y + ): + raise ValueError( + "proposal candidate point must match its retained point cell" + ) + if canonical_json_bytes(self.program) != canonical_json_bytes( + self.materialized_endpoint.program + ): + raise ValueError( + "proposal candidate program must match its materialized endpoint" + ) + return self + + @property + def canonical_order_key(self) -> tuple[Fraction, Fraction, bytes, bytes]: + """Return the frozen witness key, deliberately excluding traversal cell IDs.""" + + return ( + self.point_objective.total_upper.as_fraction, + self.point_objective.total_lower.as_fraction, + canonical_json_bytes(self.point_objective.terms), + canonical_json_bytes(self.program), + ) + + +_M2_Q0_SHARED_VALUE_HASH_DOMAIN = ( + "spatialcf/counterfactual/upright-se2/m2-q0/shared-value/3.0" +) +_M2_Q0_MAPPING_DEFINITION_ROSTER_HASH_DOMAIN = ( + "spatialcf/counterfactual/upright-se2/m2-q0/mapping-definition-roster/3.0" +) +_M2_Q0_SOURCE_PROVENANCE_HASH_DOMAIN = ( + "spatialcf/counterfactual/upright-se2/m2-q0/source-provenance/3.0" +) +_M2_Q0_SUPPORTED_DOMAIN_REF = ( + "definition:spatialcf/upright-se2/m2-closed-axis-aligned-rect-domain/1.0" +) +_M2_Q0_DOMAIN_SCHEMA_REF = "schema:spatialcf/upright-se2/m2-q0/translation-domain/1.0" +_M2_Q0_DOMAIN_TRANSFORM_REF = ( + "definition:spatialcf/upright-se2/m2-q0/delta-xy-identity/1.0" +) + + +def _m2_q0_dyadic_from_float(value: float) -> ExactDyadic: + fraction = Fraction.from_float(0.0 if value == 0.0 else value) + return ExactDyadic(numerator=fraction.numerator, denominator=fraction.denominator) + + +def _m2_q0_authorized_domain_from_source( + source_compilation: PlanarTranslateCompilation, +) -> UprightSE2TranslationDomain: + """Reconstruct the sole supported M2 world-XY delta domain from provenance.""" + + source_problem = source_compilation.source_artifacts.problem + constraints = source_problem.constraints + position = constraints.position_domain + if ( + position.region_interpretation.value != "SUBJECT_ANCHOR_LOCUS" + or position.workspace_aggregation.value != "INTERSECTION" + or position.boundary_policy.value != "CLOSED" + or position.known_free_space_fact_ids + or position.subject_occupancy_body_ids + or position.minimum_boundary_clearance_m != 0.0 + or len(position.workspace_fact_ids) != 1 + or tuple(item.value for item in position.required_completeness) != ("EXACT",) + ): + raise ValueError( + "q=0 construction source has no supported total world-XY representation" + ) + workspace = tuple( + item + for item in source_problem.scene.workspace_boundaries.values or () + if item.fact_id == position.workspace_fact_ids[0] + ) + if len(workspace) != 1: + raise ValueError("q=0 construction source must bind one exact workspace fact") + region = workspace[0].region_world_xy + if len(region.components) != 1 or region.components[0].holes: + raise ValueError( + "q=0 construction source must be one closed axis-aligned rectangle" + ) + vertices = region.components[0].exterior.vertices + xs = tuple(sorted({point.x for point in vertices})) + ys = tuple(sorted({point.y for point in vertices})) + if ( + len(vertices) != 4 + or len(xs) != 2 + or len(ys) != 2 + or {(point.x, point.y) for point in vertices} + != {(x, y) for x in xs for y in ys} + ): + raise ValueError( + "q=0 construction source must be one closed axis-aligned rectangle" + ) + subject = tuple( + item + for item in source_problem.scene.objects.values or () + if item.object_id == constraints.allowed_edit.subject_id + ) + if len(subject) != 1: + raise ValueError("q=0 construction source must bind one source subject pose") + before = subject[0].pose.world_from_object.translation + return UprightSE2TranslationDomain( + x_lower=_m2_q0_dyadic_from_float(xs[0] - before.x), + x_upper=_m2_q0_dyadic_from_float(xs[1] - before.x), + y_lower=_m2_q0_dyadic_from_float(ys[0] - before.y), + y_upper=_m2_q0_dyadic_from_float(ys[1] - before.y), + ) + + +def _m2_q0_domain_typed_value(domain: UprightSE2TranslationDomain) -> TypedValue: + return TypedValue( + value_schema_ref=( + "schema:spatialcf/upright-se2/executable-m2-q0-domain-policy/1.0" + ), + payload=RecordValue( + fields=tuple( + sorted( + ( + NamedTypedValue( + name=name, + value=TypedValue( + value_schema_ref=_UPRIGHT_SE2_REAL_SCHEMA_REF, + payload=FiniteRealValue(value=float(value.as_fraction)), + ), + ) + for name, value in ( + ("x_lower", domain.x_lower), + ("x_upper", domain.x_upper), + ("y_lower", domain.y_lower), + ("y_upper", domain.y_upper), + ) + ), + key=canonical_json_bytes, + ) + ) + ), + ) + + +def _m2_q0_non_equivalence_values( + source_compilation: PlanarTranslateCompilation, + policy_bundle: UprightSE2ExecutablePolicyBundle, +) -> dict[str, tuple[object, object]]: + """Return the exact M2 and construction-bound M3 roots for each mismatch.""" + + source_problem = source_compilation.source_artifacts.problem + return { + "source:policy/objective": ( + source_problem.objective, + policy_bundle.policy_for("objective").payload, + ), + "source:policy/relation": ( + source_problem.relation_semantics, + tuple( + policy.payload + for policy in policy_bundle.policies + if policy.policy_key.startswith("relation:") + ), + ), + "source:policy/visibility": ( + source_problem.visibility_semantics, + policy_bundle.policy_for("visibility").payload, + ), + "source:policy/support": ( + source_problem.constraints.support_constraints, + policy_bundle.policy_for("support").payload, + ), + "source:policy/numeric": ( + source_problem.numeric_policy, + policy_bundle.policy_for("numeric").payload, + ), + "source:policy/resource": ( + source_compilation.source_artifacts.config, + policy_bundle.policy_for("resource").payload, + ), + "source:policy/tie-break": ( + source_problem.objective.tie_break, + policy_bundle.policy_for("objective").payload, + ), + } + + +class UprightSE2M2Q0MappingDefinition(HashBoundCanonicalModel): + """One versioned source-to-M3 construction relation definition. + + This record deliberately describes a relation, never an evaluator. It is + carried as compiler-owned evidence for the narrow q=0 construction route + and cannot alter the Task 1 profile registration or executable M3 policy. + """ + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/m2-q0/mapping-definition/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "mapping_definition_sha256" + + mapping_definition_ref: DefinitionRef + row_kind: Literal["EQUALITY", "SOURCE_CONTEXT", "NON_EQUIVALENCE"] + source_selector: CanonicalId + target_selector: CanonicalId | None + source_value_schema_ref: CanonicalId + target_value_schema_ref: CanonicalId | None + source_unit_ref: CanonicalId | None + target_unit_ref: CanonicalId | None + transform_ref: DefinitionRef | None + non_equivalence_reason_ref: DefinitionRef | None + mapping_owner_ref: OwnerRef + mapping_version: CanonicalId + accepted_source_domain_ref: DefinitionRef + mapping_definition_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_relation_shape(self) -> Self: + if self.mapping_owner_ref != UPRIGHT_SE2_COMPILER_OWNER_REF: + raise ValueError("q=0 mapping definitions must use the compiler owner") + if self.mapping_version != "mapping-version:spatialcf/upright-se2/m2-q0/1": + raise ValueError("q=0 mapping definitions must use the fixed version") + expected_mapping_ref = ( + "definition:spatialcf/upright-se2/m2-q0/mapping/" + f"{self.source_selector.removeprefix('source:').replace('/', '-')}/1.0" + ) + if self.mapping_definition_ref != expected_mapping_ref: + raise ValueError( + "q=0 mapping definition reference must bind its source selector" + ) + if self.accepted_source_domain_ref != _M2_Q0_SUPPORTED_DOMAIN_REF: + raise ValueError( + "q=0 mapping definitions must name the sole supported domain" + ) + if self.row_kind == "EQUALITY": + if ( + self.target_selector is None + or self.target_value_schema_ref is None + or self.transform_ref is None + or self.non_equivalence_reason_ref is not None + ): + raise ValueError( + "equality mapping definition must declare one exact transform" + ) + if ( + self.source_selector != "source:constraints/position-domain" + or self.target_selector != "target:operation/translation-domain" + or self.source_value_schema_ref != _M2_Q0_DOMAIN_SCHEMA_REF + or self.target_value_schema_ref != _M2_Q0_DOMAIN_SCHEMA_REF + or self.source_unit_ref + != "definition:spatialcf/upright-se2/world-xy/metre/1.0" + or self.target_unit_ref + != "definition:spatialcf/upright-se2/world-xy/metre/1.0" + or self.transform_ref != _M2_Q0_DOMAIN_TRANSFORM_REF + ): + raise ValueError( + "q=0 equality must be the exact world-XY domain mapping" + ) + elif self.row_kind == "SOURCE_CONTEXT": + if any( + value is not None + for value in ( + self.target_selector, + self.target_value_schema_ref, + self.target_unit_ref, + self.transform_ref, + self.non_equivalence_reason_ref, + ) + ): + raise ValueError( + "source-context mapping definition must not name an M3 target" + ) + if ( + not self.source_selector.startswith("source:compilation/") + or self.source_value_schema_ref + != "schema:spatialcf/upright-se2/m2-q0/source-leaf/1.0" + or self.source_unit_ref is not None + ): + raise ValueError( + "source-context mapping definition must bind one typed source leaf" + ) + else: + if ( + self.target_selector is None + or self.target_value_schema_ref is None + or self.non_equivalence_reason_ref is None + or self.transform_ref is not None + ): + raise ValueError( + "non-equivalence mapping definition must name target and reason" + ) + expected_non_equivalence = { + "source:policy/objective": ( + "target:policy/objective", + "definition:spatialcf/upright-se2/m2-q0/heterogeneous-objective/1.0", + ), + "source:policy/relation": ( + "target:policy/relation", + "definition:spatialcf/upright-se2/m2-q0/heterogeneous-relation/1.0", + ), + "source:policy/visibility": ( + "target:policy/visibility", + "definition:spatialcf/upright-se2/m2-q0/heterogeneous-visibility/1.0", + ), + "source:policy/support": ( + "target:policy/support", + "definition:spatialcf/upright-se2/m2-q0/heterogeneous-support/1.0", + ), + "source:policy/numeric": ( + "target:policy/numeric", + "definition:spatialcf/upright-se2/m2-q0/heterogeneous-numeric/1.0", + ), + "source:policy/resource": ( + "target:policy/resource", + "definition:spatialcf/upright-se2/m2-q0/heterogeneous-resource/1.0", + ), + "source:policy/tie-break": ( + "target:policy/tie-break", + "definition:spatialcf/upright-se2/m2-q0/heterogeneous-tie-break/1.0", + ), + } + if ( + self.source_selector not in expected_non_equivalence + or self.target_selector + != expected_non_equivalence[self.source_selector][0] + or self.non_equivalence_reason_ref + != expected_non_equivalence[self.source_selector][1] + or self.source_value_schema_ref + != "schema:spatialcf/upright-se2/m2-q0/source-leaf/1.0" + or self.target_value_schema_ref + != "schema:spatialcf/upright-se2/m2-q0/target-leaf/1.0" + or self.source_unit_ref is not None + or self.target_unit_ref is not None + ): + raise ValueError( + "q=0 non-equivalence must bind its fixed heterogeneous policy" + ) + return self + + +class UprightSE2M2Q0MappingRow(HashBoundCanonicalModel): + """One fully bound q=0 relation row over exact source provenance.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/m2-q0/mapping-row/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "mapping_row_sha256" + + mapping_definition: UprightSE2M2Q0MappingDefinition + row_kind: Literal["EQUALITY", "SOURCE_CONTEXT", "NON_EQUIVALENCE"] + source_value_sha256: Sha256Digest + target_value_sha256: Sha256Digest | None + shared_value: TypedValue | None + shared_value_sha256: Sha256Digest | None + non_equivalence_reason_ref: DefinitionRef | None + mapping_row_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_bound_relation(self) -> Self: + if self.row_kind != self.mapping_definition.row_kind: + raise ValueError("q=0 mapping row kind must match its definition") + if self.row_kind == "EQUALITY": + if ( + self.target_value_sha256 != self.source_value_sha256 + or self.shared_value is None + or self.shared_value_sha256 is None + or self.source_value_sha256 != self.shared_value_sha256 + or self.non_equivalence_reason_ref is not None + ): + raise ValueError( + "equality mapping row must bind one shared normalized value" + ) + if self.shared_value_sha256 != canonical_sha256( + self.shared_value, + domain=_M2_Q0_SHARED_VALUE_HASH_DOMAIN, + ): + raise ValueError("equality mapping row shared-value digest is wrong") + elif self.row_kind == "SOURCE_CONTEXT": + if any( + value is not None + for value in ( + self.target_value_sha256, + self.shared_value, + self.shared_value_sha256, + self.non_equivalence_reason_ref, + ) + ): + raise ValueError("source-context mapping row must retain source only") + else: + if ( + self.target_value_sha256 is None + or self.shared_value is not None + or self.shared_value_sha256 is not None + or self.non_equivalence_reason_ref + != self.mapping_definition.non_equivalence_reason_ref + ): + raise ValueError( + "non-equivalence mapping row must bind target and reason" + ) + return self + + +class UprightSE2M2Q0SourceFreeConstructionRow(HashBoundCanonicalModel): + """One target-only angular or ordering constant, never a source mapping.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/m2-q0/source-free-construction-row/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "source_free_construction_row_sha256" + + construction_selector: CanonicalId + target_value: TypedValue + target_value_sha256: Sha256Digest + construction_owner_ref: OwnerRef + construction_version: CanonicalId + source_free_construction_row_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_source_free_value(self) -> Self: + if self.construction_owner_ref != UPRIGHT_SE2_COMPILER_OWNER_REF: + raise ValueError("source-free construction row must use the compiler owner") + if ( + self.construction_version + != "construction-version:spatialcf/upright-se2/m2-q0/1" + ): + raise ValueError("source-free construction row must use the fixed version") + if self.target_value_sha256 != canonical_sha256( + self.target_value, + domain=_M2_Q0_SHARED_VALUE_HASH_DOMAIN, + ): + raise ValueError("source-free construction value digest is wrong") + return self + + +class UprightSE2M2Q0Construction(HashBoundCanonicalModel): + """The narrow domain-only bridge from one exact retained M2 compilation. + + The full source compilation is embedded verbatim as provenance. The sole + equality claim is the declared supported world-XY domain transform; every + heterogeneous policy root remains source context or explicit + non-equivalence evidence. + """ + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/m2-q0/construction/3.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "m2_q0_construction_sha256" + + source_compilation: PlanarTranslateCompilation + source_compilation_sha256: Sha256Digest + source_provenance_sha256: Sha256Digest + authorized_domain: UprightSE2TranslationDomain + authorized_domain_sha256: Sha256Digest + construction_q: Literal[0] = 0 + mapping_definitions: tuple[UprightSE2M2Q0MappingDefinition, ...] + mapping_definition_roster_sha256: Sha256Digest + rows: tuple[UprightSE2M2Q0MappingRow, ...] + source_free_construction_rows: tuple[UprightSE2M2Q0SourceFreeConstructionRow, ...] + frozen_m3_policy_bundle: UprightSE2ExecutablePolicyBundle + frozen_m3_policy_bundle_sha256: Sha256Digest + m2_q0_construction_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_closed_construction(self) -> Self: + if self.source_compilation_sha256 != self.source_compilation.compilation_sha256: + raise ValueError("q=0 construction must bind its exact source compilation") + if self.source_provenance_sha256 != canonical_sha256( + self.source_compilation, + domain=_M2_Q0_SOURCE_PROVENANCE_HASH_DOMAIN, + ): + raise ValueError("q=0 construction source provenance digest is wrong") + if self.authorized_domain_sha256 != canonical_sha256( + self.authorized_domain, + domain="spatialcf/counterfactual/upright-se2/translation-domain/3.0", + ): + raise ValueError("q=0 construction authorized-domain digest is wrong") + if ( + self.frozen_m3_policy_bundle_sha256 + != self.frozen_m3_policy_bundle.policy_bundle_sha256 + ): + raise ValueError("q=0 construction must bind its executable M3 policy") + expected_domain = _m2_q0_authorized_domain_from_source(self.source_compilation) + if self.authorized_domain != expected_domain: + raise ValueError( + "q=0 construction authorized domain must derive from source provenance" + ) + definitions = tuple( + sorted( + self.mapping_definitions, key=lambda item: canonical_json_bytes(item) + ) + ) + if self.mapping_definitions != definitions: + raise ValueError("q=0 mapping definitions must use canonical source order") + if self.mapping_definition_roster_sha256 != canonical_sha256( + tuple(item.mapping_definition_sha256 for item in definitions), + domain=_M2_Q0_MAPPING_DEFINITION_ROSTER_HASH_DOMAIN, + ): + raise ValueError("q=0 mapping-definition roster digest is wrong") + definition_bytes = {canonical_json_bytes(item) for item in definitions} + if any( + canonical_json_bytes(row.mapping_definition) not in definition_bytes + for row in self.rows + ): + raise ValueError("q=0 mapping rows must use the closed definition roster") + if ( + len(self.rows) != len(definitions) + or {canonical_json_bytes(row.mapping_definition) for row in self.rows} + != definition_bytes + ): + raise ValueError( + "q=0 mapping rows must bind every closed definition exactly once" + ) + if self.rows != tuple(sorted(self.rows, key=canonical_json_bytes)): + raise ValueError("q=0 mapping rows must use canonical source order") + selectors = tuple(row.mapping_definition.source_selector for row in self.rows) + if len(selectors) != len(set(selectors)): + raise ValueError( + "q=0 source selectors must be exhaustive without duplicates" + ) + source_context_selectors = { + row.mapping_definition.source_selector + for row in self.rows + if row.row_kind == "SOURCE_CONTEXT" + } + if source_context_selectors != _m2_q0_source_leaf_selectors( + self.source_compilation + ): + raise ValueError( + "q=0 construction must retain every source leaf exactly once" + ) + equality_rows = tuple(row for row in self.rows if row.row_kind == "EQUALITY") + if len(equality_rows) != 1: + raise ValueError("q=0 construction must bind one supported-domain equality") + equality = equality_rows[0] + expected_domain_value = _m2_q0_domain_typed_value(expected_domain) + expected_domain_digest = canonical_sha256( + expected_domain_value, + domain=_M2_Q0_SHARED_VALUE_HASH_DOMAIN, + ) + if ( + equality.shared_value != expected_domain_value + or equality.source_value_sha256 != expected_domain_digest + or equality.target_value_sha256 != expected_domain_digest + or equality.shared_value_sha256 != expected_domain_digest + ): + raise ValueError( + "q=0 construction authorized-domain equality must bind source and target" + ) + source_leaf_values = _m2_q0_source_leaf_values(self.source_compilation) + for row in self.rows: + if row.row_kind != "SOURCE_CONTEXT": + continue + selector = row.mapping_definition.source_selector + if row.source_value_sha256 != canonical_sha256( + source_leaf_values[selector], + domain="spatialcf/counterfactual/upright-se2/m2-q0/source-leaf/3.0", + ): + raise ValueError( + "q=0 source-context mapping digest must bind source provenance" + ) + expected_non_equivalence = _m2_q0_non_equivalence_values( + self.source_compilation, + self.frozen_m3_policy_bundle, + ) + non_equivalence_rows = { + row.mapping_definition.source_selector: row + for row in self.rows + if row.row_kind == "NON_EQUIVALENCE" + } + if set(non_equivalence_rows) != set(expected_non_equivalence): + raise ValueError( + "q=0 construction must bind every heterogeneous policy root" + ) + for selector, (source_value, target_value) in expected_non_equivalence.items(): + row = non_equivalence_rows[selector] + if row.source_value_sha256 != canonical_sha256( + source_value, + domain="spatialcf/counterfactual/upright-se2/m2-q0/source-leaf/3.0", + ) or row.target_value_sha256 != canonical_sha256( + target_value, + domain="spatialcf/counterfactual/upright-se2/m2-q0/target-leaf/3.0", + ): + raise ValueError( + "q=0 non-equivalence mapping digest must bind source and M3 policy" + ) + if not any(row.row_kind == "SOURCE_CONTEXT" for row in self.rows): + raise ValueError("q=0 construction must retain source provenance context") + if not self.source_free_construction_rows: + raise ValueError("q=0 construction must bind source-free angular constants") + if self.source_free_construction_rows != tuple( + sorted(self.source_free_construction_rows, key=canonical_json_bytes) + ): + raise ValueError( + "q=0 source-free construction rows must use canonical order" + ) + source_free_by_selector = { + row.construction_selector: row for row in self.source_free_construction_rows + } + if set(source_free_by_selector) != { + "construction:cardinal-own-pivot-q", + "construction:target-tie-break", + }: + raise ValueError( + "q=0 construction must bind its exact source-free constants" + ) + q_row = source_free_by_selector["construction:cardinal-own-pivot-q"] + tie_break_row = source_free_by_selector["construction:target-tie-break"] + if ( + q_row.target_value.value_schema_ref + != "schema:spatialcf/upright-se2/integer/1.0" + or type(q_row.target_value.payload) is not IntegerValue + or q_row.target_value.payload.value != 0 + or tie_break_row.target_value.value_schema_ref + != "schema:spatialcf/upright-se2/enum-symbol/1.0" + or type(tie_break_row.target_value.payload) is not EnumSymbolValue + or tie_break_row.target_value.payload.symbol != "T_R_V_S_A" + ): + raise ValueError( + "q=0 source-free constants must be fixed construction values" + ) + return self + + +class UprightSE2Compilation(HashBoundCanonicalModel): + """A deterministic cardinal compilation, never a solve/check/certificate result.""" + + HASH_DOMAIN: ClassVar[str] = "spatialcf/counterfactual/upright-se2/compilation/3.0" + SELF_DIGEST_FIELD: ClassVar[str] = "upright_se2_compilation_sha256" + + solve_request_sha256: Sha256Digest + source_solve_request: CounterfactualSolveRequest + closure: UprightSE2CompilerClosure + operation: UprightSE2CardinalOperation + endpoint_construction_recipe: UprightSE2EndpointConstructionRecipe + state_footprint: UprightSE2StateFootprint + grounded_obligations: GroundedObligationSet + semantic_closure: UprightSE2SemanticClosure + compiled_cells: tuple[UprightSE2CompiledCell, ...] + m2_q0_construction: UprightSE2M2Q0Construction | None = None + upright_se2_compilation_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_compilation_cells(self) -> Self: + if self.source_solve_request.solve_request_sha256 != self.solve_request_sha256: + raise ValueError( + "compilation solve request digest does not bind its source request" + ) + if ( + self.source_solve_request.semantic_problem_sha256 + != self.semantic_closure.semantic_problem.semantic_problem_sha256 + or canonical_json_bytes(self.source_solve_request.semantic_problem) + != canonical_json_bytes(self.semantic_closure.semantic_problem) + ): + raise ValueError( + "compilation semantic closure does not bind the source request" + ) + if ( + self.closure.profile_registration_sha256 + != self.semantic_closure.profile_registration_sha256 + ): + raise ValueError( + "compiler and semantic closures must bind the same profile digest" + ) + if ( + self.semantic_closure.profile_registration.profile_registration_sha256 + != self.semantic_closure.profile_registration_sha256 + ): + raise ValueError( + "semantic closure profile does not bind its profile digest" + ) + if ( + self.endpoint_construction_recipe.operation_authorization_sha256 + != self.operation.authorization_sha256 + or self.endpoint_construction_recipe.translation_domain + != self.operation.translation_domain + ): + raise ValueError( + "compilation endpoint recipe must bind its operation domain" + ) + if ( + self.semantic_closure.grounded_obligation_set_sha256 + != self.grounded_obligations.grounded_obligation_set_sha256 + ): + raise ValueError( + "semantic closure must bind the compiled grounded obligations" + ) + if ( + self.semantic_closure.semantic_closure_sha256 + != self.closure.semantic_closure_sha256 + ): + raise ValueError("compiler closure must bind the semantic closure") + if ( + self.semantic_closure.policy_bundle_sha256 + != self.closure.policy_bundle_sha256 + ): + raise ValueError("compiler closure must bind the executable policy bundle") + if ( + self.semantic_closure.resource_policy.resource_policy_sha256 + != self.closure.resource_policy_sha256 + ): + raise ValueError("compiler closure must bind the request resource policy") + if ( + self.semantic_closure.definition_bundle_sha256 + != self.closure.definition_bundle_sha256 + ): + raise ValueError( + "compiler closure must bind the semantic definition bundle" + ) + if not self.compiled_cells: + raise ValueError( + "upright se2 compilation must include one or more canonical cells" + ) + if tuple(cell.authorization_sha256 for cell in self.compiled_cells) != ( + self.operation.authorization_sha256, + ): + raise ValueError( + "compiled cells must bind the resolved cardinal authorization" + ) + if self.m2_q0_construction is not None: + construction = self.m2_q0_construction + if ( + self.operation.quarter_turns_ccw != 0 + or self.operation.pivot_binding.pivot_mode is not PivotMode.OWN + or self.operation.translation_domain != construction.authorized_domain + or self.semantic_closure.policy_bundle_sha256 + != construction.frozen_m3_policy_bundle_sha256 + ): + raise ValueError( + "q=0 construction must bind the domain-only M3 operation" + ) + construction_facts = tuple( + fact + for bundle in self.source_solve_request.semantic_problem.scene_state.extension_fact_bundles + for fact in bundle.facts + if fact.fact_family_ref + == "definition:spatialcf/upright-se2/m2-q0-construction/1.0" + ) + if len(construction_facts) != 1: + raise ValueError( + "q=0 construction root must be present in the source request" + ) + fact = construction_facts[0] + if ( + fact.fact_key != "fact-key:spatialcf/upright-se2/m2-q0-construction" + or type(fact.value.payload) is not DigestValue + or fact.value.payload.value != construction.m2_q0_construction_sha256 + ): + raise ValueError( + "q=0 construction request root does not bind construction" + ) + _validate_compilation_source_binding(self) + return self + + +class UprightSE2ContinuousCompilation(HashBoundCanonicalModel): + """A deterministic continuous compilation, never a checked result. + + This additive model has its own hash domain and carries the canonical + lifted-yaw roots. Cardinal compilation remains closed over its existing + type and wire. + """ + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/continuous-compilation/1.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "continuous_upright_se2_compilation_sha256" + + solve_request_sha256: Sha256Digest + source_solve_request: CounterfactualSolveRequest + closure: UprightSE2CompilerClosure + operation: UprightSE2ContinuousOperation + endpoint_construction_recipe: UprightSE2ContinuousEndpointConstructionRecipe + state_footprint: UprightSE2StateFootprint + grounded_obligations: GroundedObligationSet + semantic_closure: UprightSE2ContinuousSemanticClosure + compiled_cells: tuple[UprightSE2CompiledCell, ...] + continuous_yaw_lift: ContinuousYawLift + continuous_upright_se2_compilation_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_continuous_compilation(self) -> Self: + if self.source_solve_request.solve_request_sha256 != self.solve_request_sha256: + raise ValueError("continuous compilation must bind its source request") + if ( + self.closure.profile_registration_sha256 + != self.semantic_closure.profile_registration_sha256 + or self.closure.semantic_closure_sha256 + != self.semantic_closure.semantic_closure_sha256 + or self.closure.policy_bundle_sha256 + != self.semantic_closure.policy_bundle_sha256 + or self.closure.resource_policy_sha256 + != self.semantic_closure.resource_policy.resource_policy_sha256 + ): + raise ValueError("continuous compiler closure does not bind semantics") + if ( + self.endpoint_construction_recipe.operation_authorization_sha256 + != self.operation.authorization_sha256 + or self.endpoint_construction_recipe.translation_domain + != self.operation.translation_domain + or self.endpoint_construction_recipe.yaw_domain != self.operation.yaw_domain + ): + raise ValueError("continuous endpoint recipe does not bind operation") + if self.continuous_yaw_lift.yaw_domain != self.operation.yaw_domain: + raise ValueError("continuous lift does not bind operation yaw domain") + if len(self.compiled_cells) != 1: + raise ValueError("continuous compilation requires one canonical lift root") + cell = self.compiled_cells[0] + interval = self.continuous_yaw_lift.intervals[0] + if ( + cell.authorization_sha256 != self.operation.authorization_sha256 + or cell.x_lower != self.operation.translation_domain.x_lower + or cell.x_upper != self.operation.translation_domain.x_upper + or cell.y_lower != self.operation.translation_domain.y_lower + or cell.y_upper != self.operation.translation_domain.y_upper + or cell.yaw_interval != interval + ): + raise ValueError("continuous compilation root does not bind operation") + _validate_continuous_compilation_source_binding(self) + return self + + +class UprightSE2ContinuousMaterializedEndpoint(HashBoundCanonicalModel): + """One continuous endpoint selected by the compiler-owned materializer.""" + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/continuous-materialized-endpoint/1.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "continuous_materialized_endpoint_sha256" + + continuous_upright_se2_compilation_sha256: Sha256Digest + compilation: UprightSE2ContinuousCompilation + endpoint_construction_recipe: UprightSE2ContinuousEndpointConstructionRecipe + translation_xy_m: Vec2 + selected_lifted_yaw: ExactDyadic + after_state: UprightSE2AfterStateTemplate + program: EditProgram + continuous_materialized_endpoint_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_endpoint_from_recipe(self) -> Self: + compilation = self.compilation + recipe = self.endpoint_construction_recipe + if ( + self.continuous_upright_se2_compilation_sha256 + != compilation.continuous_upright_se2_compilation_sha256 + or recipe != compilation.endpoint_construction_recipe + ): + raise ValueError("continuous endpoint must bind its compilation recipe") + x = _fraction_from_float(self.translation_xy_m.x) + y = _fraction_from_float(self.translation_xy_m.y) + yaw = self.selected_lifted_yaw.as_fraction + root = compilation.compiled_cells[0] + if not ( + recipe.translation_domain.x_lower.as_fraction + <= x + <= recipe.translation_domain.x_upper.as_fraction + and recipe.translation_domain.y_lower.as_fraction + <= y + <= recipe.translation_domain.y_upper.as_fraction + and root.yaw_interval.lower.as_fraction + <= yaw + <= root.yaw_interval.upper.as_fraction + ): + raise ValueError("continuous endpoint point is outside its authorized root") + if ( + isinstance(recipe.yaw_domain, ContinuousYawFullCircle) + and yaw == root.yaw_interval.upper.as_fraction + ): + raise ValueError( + "continuous endpoint rejects the upper full-circle seam alias" + ) + expected_xy, expected_yaw, expected_pose, expected_reference = ( + _continuous_materialized_expected_pose( + recipe, + self.translation_xy_m, + self.selected_lifted_yaw, + ) + ) + if ( + self.after_state.evaluation_scene != recipe.evaluation_scene + or self.after_state.subject_id != recipe.subject_id + or self.after_state.subject_before_pose != recipe.subject_before_pose + or self.after_state.reference_pivot_xy_m != expected_reference + or self.after_state.subject_pivot_xy_m != expected_xy + or self.after_state.subject_pivot_z_m != expected_pose.translation.z + or self.after_state.subject_yaw_turns != expected_yaw + or self.after_state.subject_pose != expected_pose + ): + raise ValueError( + "continuous endpoint after state must derive from its recipe" + ) + _validate_continuous_materialized_endpoint_program(self) + return self + + +def _continuous_materialized_program_arguments( + compilation: UprightSE2ContinuousCompilation, + translation_xy_m: Vec2, + selected_lifted_yaw: ExactDyadic, +) -> tuple[OperationArgument, ...]: + """Derive the sole continuous invocation from sealed endpoint inputs.""" + + return tuple( + sorted( + ( + OperationArgument( + argument_name="argument:spatialcf/upright-se2/pivot-entity-id", + value=TypedValue( + value_schema_ref="schema:spatialcf/upright-se2/entity-id/1.0", + payload=CanonicalIdValue( + value=compilation.operation.pivot_binding.pivot_entity_id + ), + ), + ), + OperationArgument( + argument_name="argument:spatialcf/upright-se2/selected-lifted-yaw-turn", + value=TypedValue( + value_schema_ref="schema:spatialcf/upright-se2/exact-dyadic-turn/1.0", + payload=CanonicalIdValue( + value=( + "exact-dyadic:" + f"{selected_lifted_yaw.numerator}/" + f"{selected_lifted_yaw.denominator}" + ), + ), + ), + ), + OperationArgument( + argument_name="argument:spatialcf/upright-se2/subject-id", + value=TypedValue( + value_schema_ref="schema:spatialcf/upright-se2/entity-id/1.0", + payload=CanonicalIdValue( + value=compilation.endpoint_construction_recipe.subject_id + ), + ), + ), + OperationArgument( + argument_name="argument:spatialcf/upright-se2/translation-x-m", + value=TypedValue( + value_schema_ref="schema:spatialcf/upright-se2/metre/1.0", + payload=FiniteRealValue(value=translation_xy_m.x), + ), + ), + OperationArgument( + argument_name="argument:spatialcf/upright-se2/translation-y-m", + value=TypedValue( + value_schema_ref="schema:spatialcf/upright-se2/metre/1.0", + payload=FiniteRealValue(value=translation_xy_m.y), + ), + ), + ), + key=lambda argument: canonical_json_bytes(argument.argument_name), + ) + ) + + +def _validate_continuous_materialized_endpoint_program( + endpoint: UprightSE2ContinuousMaterializedEndpoint, +) -> None: + """Close one continuous program over its sealed source and endpoint.""" + + compilation = endpoint.compilation + source_problem = compilation.source_solve_request.semantic_problem + before_state = source_problem.scene_state + program = endpoint.program + if ( + program.semantic_problem_sha256 != source_problem.semantic_problem_sha256 + or program.action_space_profile_sha256 + != compilation.semantic_closure.profile_registration.action_space_profile.action_space_profile_sha256 + or program.before_state_sha256 != before_state.scene_state_sha256 + or program.grounded_obligation_set_sha256 + != compilation.grounded_obligations.grounded_obligation_set_sha256 + or program.state_delta_manifest + != compilation.state_footprint.state_delta_manifest + ): + raise ValueError("continuous endpoint program roots do not bind compilation") + expected_step = OperationInvocation( + operator_ref=compilation.operation.authorization.operator_ref, + arguments=_continuous_materialized_program_arguments( + compilation, + endpoint.translation_xy_m, + endpoint.selected_lifted_yaw, + ), + ) + if program.steps != (expected_step,): + raise ValueError( + "continuous endpoint program must contain exactly one bound invocation" + ) + expected_after = _materialized_expected_after_scene_state( + compilation, + endpoint.after_state, + ) + if ( + program.after_scene_state_sha256 != expected_after.scene_state_sha256 + or canonical_json_bytes(program.after_scene_state) + != canonical_json_bytes(expected_after) + ): + raise ValueError( + "continuous endpoint program must equal the complete expected after scene" + ) + + +class UprightSE2ContinuousProofTuple(HashBoundCanonicalModel): + """The one request-authorized continuous operator/reference/lift tuple. + + Continuous search may subdivide this root, but it must never add another + operator, pivot, reference, translation domain, or lifted yaw domain. The + record intentionally mirrors the cardinal tuple without widening it. + """ + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/continuous-proof-tuple/1.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "continuous_proof_tuple_sha256" + + authorization: ContinuousYawAuthorization + reference_id: CanonicalId + translation_domain: UprightSE2TranslationDomain + continuous_yaw_lift: ContinuousYawLift + compiled_cells: tuple[UprightSE2CompiledCell, ...] + continuous_proof_tuple_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_continuous_tuple(self) -> Self: + if len(self.compiled_cells) != 1: + raise ValueError("continuous proof tuple requires one lift root") + root = self.compiled_cells[0] + interval = self.continuous_yaw_lift.intervals[0] + if ( + root.authorization_sha256 + != self.authorization.continuous_yaw_authorization_sha256 + or root.x_lower != self.translation_domain.x_lower + or root.x_upper != self.translation_domain.x_upper + or root.y_lower != self.translation_domain.y_lower + or root.y_upper != self.translation_domain.y_upper + or root.yaw_interval != interval + or self.continuous_yaw_lift.yaw_domain != self.authorization.yaw_domain + ): + raise ValueError("continuous proof tuple root does not bind its authority") + return self + + +class UprightSE2ContinuousProposalCandidate(HashBoundCanonicalModel): + """One compiler-materialized continuous inward witness. + + It transports retained point evidence only. The backend may choose the + candidate, but it cannot construct the endpoint, program, objective, or + certificate represented here. + """ + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/continuous-proposal-candidate/1.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "continuous_proposal_candidate_sha256" + + final_inward_cell: UprightSE2ProofCellEvaluation + selected_translation_xy_m: Vec2 + selected_lifted_yaw: ExactDyadic + point_evaluation: UprightSE2ProposalPointEvaluation + point_objective: UprightSE2ProposalPointObjective + materialized_endpoint: UprightSE2ContinuousMaterializedEndpoint + program: EditProgram + continuous_proposal_candidate_sha256: Sha256Digest + + @model_validator(mode="after") + def _validate_continuous_candidate(self) -> Self: + if self.final_inward_cell.leaf_disposition not in ( + UprightSE2ProofLeafDisposition.INWARD_FEASIBLE, + UprightSE2ProofLeafDisposition.UNRESOLVED, + ): + raise ValueError( + "continuous proposal candidates require one final inward or unresolved cell" + ) + if canonical_json_bytes(self.point_objective) != canonical_json_bytes( + self.point_evaluation.point_objective + ): + raise ValueError("continuous candidate objective must bind point evidence") + if ( + self.selected_translation_xy_m + != self.materialized_endpoint.translation_xy_m + or self.selected_lifted_yaw + != self.materialized_endpoint.selected_lifted_yaw + or canonical_json_bytes(self.program) + != canonical_json_bytes(self.materialized_endpoint.program) + ): + raise ValueError("continuous candidate must bind its materialized endpoint") + cell = self.final_inward_cell.compiled_cell + point_x = _fraction_from_float(self.selected_translation_xy_m.x) + point_y = _fraction_from_float(self.selected_translation_xy_m.y) + point_yaw = self.selected_lifted_yaw.as_fraction + if not ( + cell.x_lower.as_fraction <= point_x <= cell.x_upper.as_fraction + and cell.y_lower.as_fraction <= point_y <= cell.y_upper.as_fraction + and cell.yaw_interval.lower.as_fraction + <= point_yaw + <= cell.yaw_interval.upper.as_fraction + ): + raise ValueError("continuous candidate point is outside its final cell") + point_cell = self.point_evaluation.point_cell_evaluation.compiled_cell + if ( + point_cell.x_lower.as_fraction != point_x + or point_cell.x_upper.as_fraction != point_x + or point_cell.y_lower.as_fraction != point_y + or point_cell.y_upper.as_fraction != point_y + or point_cell.yaw_interval.lower.as_fraction != point_yaw + or point_cell.yaw_interval.upper.as_fraction != point_yaw + ): + raise ValueError( + "continuous candidate point must bind its exact point cell" + ) + return self + + @property + def canonical_order_key(self) -> tuple[Fraction, Fraction, bytes, bytes]: + """Use the frozen objective/program witness key, never traversal IDs.""" + + return ( + self.point_objective.total_upper.as_fraction, + self.point_objective.total_lower.as_fraction, + canonical_json_bytes(self.point_objective.terms), + canonical_json_bytes(self.program), + ) + + +def _continuous_proof_cell_within_root( + cell: UprightSE2CompiledCell, + root: UprightSE2CompiledCell, +) -> bool: + """Return whether a proof cell is an exact-dyadic `(x, y, u)` descendant.""" + + return ( + cell.authorization_sha256 == root.authorization_sha256 + and root.x_lower.as_fraction <= cell.x_lower.as_fraction + and cell.x_upper.as_fraction <= root.x_upper.as_fraction + and root.y_lower.as_fraction <= cell.y_lower.as_fraction + and cell.y_upper.as_fraction <= root.y_upper.as_fraction + and root.yaw_interval.lower.as_fraction <= cell.yaw_interval.lower.as_fraction + and cell.yaw_interval.upper.as_fraction <= root.yaw_interval.upper.as_fraction + ) + + +def _continuous_proof_leaf_owns_coordinate( + *, + lower: Fraction, + upper: Fraction, + coordinate: Fraction, + root_lower: Fraction, + root_upper: Fraction, + full_circle_alias: bool, +) -> bool: + """Apply lower-owned seams, including the full-circle upper closure alias.""" + + if full_circle_alias and coordinate == root_upper: + return lower == root_lower + return (coordinate == root_lower and lower == root_lower) or ( + lower < coordinate <= upper + ) + + +def _typed_continuous_bound_proves_inward_feasibility(value: TypedValue) -> bool: + """Read one complete retained V4 feasibility record from a structural bound.""" + + payload = value.payload + if type(payload) is not RecordValue: + return False + fields = {field.name: field.value for field in payload.fields} + inner_hard_constraint_proven = fields.get("inner_hard_constraint_proven") + relation_inner_success = fields.get("relation_inner_success") + classification = fields.get("visibility_classification") + return ( + inner_hard_constraint_proven is not None + and type(inner_hard_constraint_proven.payload) is BooleanValue + and inner_hard_constraint_proven.payload.value is True + and relation_inner_success is not None + and type(relation_inner_success.payload) is BooleanValue + and relation_inner_success.payload.value is True + and classification is not None + and type(classification.payload) is CanonicalIdValue + and classification.payload.value == "INWARD" + ) + + +def _continuous_point_retains_inward_evidence( + point_row: UprightSE2ProofCellEvaluation, +) -> bool: + """Bind feasibility to one exact V4 compound row, not objective transport.""" + + return any( + evaluation.outcome_kind is UprightSE2RetainedOwnerOutcomeKind.EXACT + and "proof:spatialcf/upright-se2/continuous-compound-cell" + in evaluation.proof_rows + and any( + _typed_continuous_bound_proves_inward_feasibility(bound) + for bound in evaluation.exact_bounds + ) + for evaluation in point_row.owner_evaluations + ) + + +def _continuous_proof_validate_leaf_partition( + root: UprightSE2CompiledCell, + leaves: tuple[UprightSE2CompiledCell, ...], + *, + full_circle_alias: bool, +) -> None: + """Require one exact-dyadic, lower-owned `(x, y, u)` leaf partition.""" + + if not leaves or any( + not _continuous_proof_cell_within_root(leaf, root) for leaf in leaves + ): + raise ValueError("continuous proof leaves must cover one exact root") + axes = ( + ("x", root.x_lower.as_fraction, root.x_upper.as_fraction), + ("y", root.y_lower.as_fraction, root.y_upper.as_fraction), + ("u", root.yaw_interval.lower.as_fraction, root.yaw_interval.upper.as_fraction), + ) + leaf_bounds = { + "x": tuple( + (leaf.x_lower.as_fraction, leaf.x_upper.as_fraction) for leaf in leaves + ), + "y": tuple( + (leaf.y_lower.as_fraction, leaf.y_upper.as_fraction) for leaf in leaves + ), + "u": tuple( + ( + leaf.yaw_interval.lower.as_fraction, + leaf.yaw_interval.upper.as_fraction, + ) + for leaf in leaves + ), + } + coordinates: dict[str, tuple[Fraction, ...]] = {} + segments: dict[str, tuple[tuple[Fraction, Fraction], ...]] = {} + for axis, root_lower, root_upper in axes: + values = tuple( + sorted( + { + root_lower, + root_upper, + *(value for pair in leaf_bounds[axis] for value in pair), + } + ) + ) + coordinates[axis] = values + segments[axis] = ( + ((root_lower, root_upper),) + if root_lower == root_upper + else tuple(pairwise(values)) + ) + for x_lower, x_upper in segments["x"]: + for y_lower, y_upper in segments["y"]: + for u_lower, u_upper in segments["u"]: + covering = tuple( + leaf + for leaf in leaves + if ( + leaf.x_lower.as_fraction <= x_lower + and x_upper <= leaf.x_upper.as_fraction + and leaf.y_lower.as_fraction <= y_lower + and y_upper <= leaf.y_upper.as_fraction + and leaf.yaw_interval.lower.as_fraction <= u_lower + and u_upper <= leaf.yaw_interval.upper.as_fraction + ) + ) + if len(covering) != 1: + raise ValueError( + "continuous proof leaves must form one exact-dyadic partition" + ) + for x in coordinates["x"]: + for y in coordinates["y"]: + for u in coordinates["u"]: + owners = tuple( + leaf + for leaf in leaves + if _continuous_proof_leaf_owns_coordinate( + lower=leaf.x_lower.as_fraction, + upper=leaf.x_upper.as_fraction, + coordinate=x, + root_lower=root.x_lower.as_fraction, + root_upper=root.x_upper.as_fraction, + full_circle_alias=False, + ) + and _continuous_proof_leaf_owns_coordinate( + lower=leaf.y_lower.as_fraction, + upper=leaf.y_upper.as_fraction, + coordinate=y, + root_lower=root.y_lower.as_fraction, + root_upper=root.y_upper.as_fraction, + full_circle_alias=False, + ) + and _continuous_proof_leaf_owns_coordinate( + lower=leaf.yaw_interval.lower.as_fraction, + upper=leaf.yaw_interval.upper.as_fraction, + coordinate=u, + root_lower=root.yaw_interval.lower.as_fraction, + root_upper=root.yaw_interval.upper.as_fraction, + full_circle_alias=full_circle_alias, + ) + ) + if len(owners) != 1: + raise ValueError( + "continuous proof leaves must assign every exact seam once" + ) + + +class UprightSE2ContinuousProofMaterial(HashBoundCanonicalModel): + """Complete untrusted continuous branch-and-bound evidence for Task 8. + + This is deliberately a profile proof transport only: fresh geometry replay, + proof acceptance, certificates, and terminal result assembly remain outside + this domain record and outside the proposal backend. + """ + + HASH_DOMAIN: ClassVar[str] = ( + "spatialcf/counterfactual/upright-se2/continuous-proof-material/1.0" + ) + SELF_DIGEST_FIELD: ClassVar[str] = "continuous_proof_material_sha256" + + solve_request_sha256: Sha256Digest + semantic_closure_sha256: Sha256Digest + continuous_upright_se2_compilation_sha256: Sha256Digest + compilation: UprightSE2ContinuousCompilation + continuous_tuple_roster: tuple[UprightSE2ContinuousProofTuple, ...] + coverage_artifact: UprightSE2CoverageArtifact + compiled_cell_sha256s: tuple[Sha256Digest, ...] + evaluated_cells: tuple[UprightSE2ProofCellEvaluation, ...] + proposal_order: tuple[UprightSE2ProofCellEvaluation, ...] + proposal_candidates: tuple[UprightSE2ContinuousProposalCandidate, ...] = () + incumbent_candidate_sha256: Sha256Digest | None + prune_decisions: tuple[UprightSE2ProofPruneDecision, ...] + unresolved_frontier: tuple[UprightSE2ProofFrontierRow, ...] + resource_ledger: UprightSE2ProofResourceLedger + continuous_proof_material_sha256: Sha256Digest + + @property + def total_resource_usage(self) -> ResourceUsage: + """Expose the one shared ledger total for the outer submission.""" + + return self.resource_ledger.canonical_total_resource_usage + + @model_validator(mode="after") + def _validate_continuous_proof_material(self) -> Self: + compilation = self.compilation + if ( + self.solve_request_sha256 != compilation.solve_request_sha256 + or self.semantic_closure_sha256 + != compilation.semantic_closure.semantic_closure_sha256 + or self.continuous_upright_se2_compilation_sha256 + != compilation.continuous_upright_se2_compilation_sha256 + ): + raise ValueError("continuous proof material roots do not bind compilation") + if len(self.continuous_tuple_roster) != 1: + raise ValueError("continuous proof tuple roster must contain one tuple") + proof_tuple = self.continuous_tuple_roster[0] + source_input = _bound_continuous_source_input( + compilation.source_solve_request.semantic_problem + ) + if ( + canonical_json_bytes(proof_tuple.authorization) + != canonical_json_bytes(compilation.operation.authorization) + or proof_tuple.reference_id != source_input.reference_id + or proof_tuple.translation_domain + != compilation.operation.translation_domain + or proof_tuple.continuous_yaw_lift != compilation.continuous_yaw_lift + or tuple(canonical_json_bytes(cell) for cell in proof_tuple.compiled_cells) + != tuple(canonical_json_bytes(cell) for cell in compilation.compiled_cells) + or proof_tuple.authorization.operator_ref != source_input.operator_ref + ): + raise ValueError("continuous proof tuple does not bind the source root") + root = proof_tuple.compiled_cells[0] + if ( + self.coverage_artifact.authorization_sha256 + != compilation.operation.authorization_sha256 + ): + raise ValueError("continuous proof coverage does not bind authorization") + _continuous_proof_validate_leaf_partition( + root, + self.coverage_artifact.cells, + full_circle_alias=( + type(compilation.operation.yaw_domain) is ContinuousYawFullCircle + ), + ) + expected_cell_sha256s = tuple( + cell.compiled_cell_sha256 for cell in self.coverage_artifact.cells + ) + if self.compiled_cell_sha256s != expected_cell_sha256s: + raise ValueError("continuous proof must bind the canonical leaf roster") + if self.evaluated_cells != tuple( + sorted( + self.evaluated_cells, + key=lambda row: _lifted_cell_order_key(row.compiled_cell), + ) + ): + raise ValueError("continuous evaluated cells must use deterministic order") + if len({row.compiled_cell.cell_id for row in self.evaluated_cells}) != len( + self.evaluated_cells + ) or len( + {canonical_json_bytes(row.compiled_cell) for row in self.evaluated_cells} + ) != len(self.evaluated_cells): + raise ValueError("continuous proof rows must not duplicate cells") + if any( + not _continuous_proof_cell_within_root(row.compiled_cell, root) + for row in self.evaluated_cells + ): + raise ValueError("continuous proof rows must be root descendants") + final_rows = tuple( + row for row in self.evaluated_cells if row.leaf_disposition is not None + ) + if tuple( + canonical_json_bytes(row.compiled_cell) for row in final_rows + ) != tuple(canonical_json_bytes(cell) for cell in self.coverage_artifact.cells): + raise ValueError("continuous final rows must exactly bind coverage leaves") + expected_proposals = tuple( + row + for row in final_rows + if row.leaf_disposition is UprightSE2ProofLeafDisposition.INWARD_FEASIBLE + ) + if tuple(canonical_json_bytes(row) for row in self.proposal_order) != tuple( + canonical_json_bytes(row) for row in expected_proposals + ): + raise ValueError("continuous proposal order must be complete and stable") + candidate_cells = tuple( + canonical_json_bytes(candidate.final_inward_cell) + for candidate in self.proposal_candidates + ) + expected_inward_candidate_cells = tuple( + canonical_json_bytes(row) for row in expected_proposals + ) + final_rows_by_bytes = {canonical_json_bytes(row): row for row in final_rows} + if len(set(candidate_cells)) != len(candidate_cells) or not set( + expected_inward_candidate_cells + ).issubset(candidate_cells): + raise ValueError( + "continuous candidates must cover inward leaves exactly once" + ) + if any( + candidate_cell not in final_rows_by_bytes + for candidate_cell in candidate_cells + ): + raise ValueError("continuous candidate parent must be a final proof leaf") + if self.proposal_candidates != tuple( + sorted( + self.proposal_candidates, + key=lambda candidate: candidate.canonical_order_key, + ) + ): + raise ValueError("continuous proposal candidates use the wrong order") + expected_incumbent = ( + None + if not self.proposal_candidates + else self.proposal_candidates[0].continuous_proposal_candidate_sha256 + ) + if self.incumbent_candidate_sha256 != expected_incumbent: + raise ValueError("continuous incumbent must be the canonical witness") + for candidate in self.proposal_candidates: + point_row = candidate.point_evaluation.point_cell_evaluation + parent = candidate.final_inward_cell + if parent.leaf_disposition is UprightSE2ProofLeafDisposition.UNRESOLVED: + if not any( + canonical_json_bytes(frontier.cell_evaluation) + == canonical_json_bytes(parent) + for frontier in self.unresolved_frontier + ): + raise ValueError( + "continuous unresolved candidate parent must be in frontier" + ) + if point_row.leaf_disposition is not None: + raise ValueError( + "continuous unresolved candidates require an internal point row" + ) + if point_row.compiled_cell.cell_id != ( + f"{parent.compiled_cell.cell_id}/proposal-point" + ): + raise ValueError( + "continuous unresolved candidate point must bind its parent identity" + ) + if not _continuous_point_retains_inward_evidence(point_row): + raise ValueError( + "continuous unresolved candidate requires inward feasibility evidence" + ) + if not any( + canonical_json_bytes(row) == canonical_json_bytes(point_row) + for row in self.evaluated_cells + ): + raise ValueError("continuous candidate point row is absent from proof") + point_cell = point_row.compiled_cell + selected_u = candidate.selected_lifted_yaw.as_fraction + if ( + point_cell.yaw_interval.lower.as_fraction != selected_u + or point_cell.yaw_interval.upper.as_fraction != selected_u + ): + raise ValueError("continuous candidate point yaw is not degenerate") + if ( + type(compilation.operation.yaw_domain) is ContinuousYawFullCircle + and selected_u == root.yaw_interval.upper.as_fraction + ): + raise ValueError("full-circle upper closure alias cannot be a witness") + final_cell = candidate.final_inward_cell.compiled_cell + for lower, upper, coordinate in ( + ( + final_cell.x_lower.as_fraction, + final_cell.x_upper.as_fraction, + point_cell.x_lower.as_fraction, + ), + ( + final_cell.y_lower.as_fraction, + final_cell.y_upper.as_fraction, + point_cell.y_lower.as_fraction, + ), + ( + final_cell.yaw_interval.lower.as_fraction, + final_cell.yaw_interval.upper.as_fraction, + selected_u, + ), + ): + if (lower < upper and not lower < coordinate < upper) or ( + lower == upper and coordinate != lower + ): + raise ValueError( + "continuous proposal points require strict interior descendants" + ) + owners = tuple( + row + for row in final_rows + if _continuous_proof_cell_within_root(point_cell, row.compiled_cell) + and _continuous_proof_leaf_owns_coordinate( + lower=row.compiled_cell.x_lower.as_fraction, + upper=row.compiled_cell.x_upper.as_fraction, + coordinate=point_cell.x_lower.as_fraction, + root_lower=root.x_lower.as_fraction, + root_upper=root.x_upper.as_fraction, + full_circle_alias=False, + ) + and _continuous_proof_leaf_owns_coordinate( + lower=row.compiled_cell.y_lower.as_fraction, + upper=row.compiled_cell.y_upper.as_fraction, + coordinate=point_cell.y_lower.as_fraction, + root_lower=root.y_lower.as_fraction, + root_upper=root.y_upper.as_fraction, + full_circle_alias=False, + ) + and _continuous_proof_leaf_owns_coordinate( + lower=row.compiled_cell.yaw_interval.lower.as_fraction, + upper=row.compiled_cell.yaw_interval.upper.as_fraction, + coordinate=selected_u, + root_lower=root.yaw_interval.lower.as_fraction, + root_upper=root.yaw_interval.upper.as_fraction, + full_circle_alias=( + type(compilation.operation.yaw_domain) + is ContinuousYawFullCircle + ), + ) + ) + if len(owners) != 1 or canonical_json_bytes( + owners[0] + ) != canonical_json_bytes(candidate.final_inward_cell): + raise ValueError("continuous candidate point lacks one owning leaf") + policy_terms = compilation.semantic_closure.objective_policy.terms + if tuple(term.term_id for term in candidate.point_objective.terms) != tuple( + term.term_id for term in policy_terms + ): + raise ValueError("continuous candidate terms do not bind policy") + expected_lower = sum( + ( + point_term.lower.as_fraction + * Fraction.from_float(policy_term.weight) + / Fraction.from_float(policy_term.normalizer) + for point_term, policy_term in zip( + candidate.point_objective.terms, + policy_terms, + strict=True, + ) + ), + start=Fraction(0), + ) + expected_upper = sum( + ( + point_term.upper.as_fraction + * Fraction.from_float(policy_term.weight) + / Fraction.from_float(policy_term.normalizer) + for point_term, policy_term in zip( + candidate.point_objective.terms, + policy_terms, + strict=True, + ) + ), + start=Fraction(0), + ) + if ( + candidate.point_objective.total_lower.as_fraction != expected_lower + or candidate.point_objective.total_upper.as_fraction != expected_upper + ): + raise ValueError("continuous candidate total does not bind policy") + if ( + candidate.materialized_endpoint.continuous_upright_se2_compilation_sha256 + != compilation.continuous_upright_se2_compilation_sha256 + or candidate.program.semantic_problem_sha256 + != compilation.source_solve_request.semantic_problem_sha256 + or candidate.program.action_space_profile_sha256 + != compilation.semantic_closure.profile_registration.action_space_profile.action_space_profile_sha256 + or candidate.program.before_state_sha256 + != compilation.source_solve_request.semantic_problem.scene_state.scene_state_sha256 + or candidate.program.grounded_obligation_set_sha256 + != compilation.grounded_obligations.grounded_obligation_set_sha256 + or candidate.program.state_delta_manifest + != compilation.state_footprint.state_delta_manifest + ): + raise ValueError("continuous candidate program does not bind roots") + expected_pruned = tuple( + row + for row in final_rows + if row.leaf_disposition is UprightSE2ProofLeafDisposition.PRUNED + ) + if len(self.prune_decisions) != len(expected_pruned) or any( + canonical_json_bytes(decision.cell_evaluation) + != canonical_json_bytes(expected) + for decision, expected in zip( + self.prune_decisions, expected_pruned, strict=True + ) + ): + raise ValueError("continuous prune decisions must be complete") + expected_unresolved = tuple( + row + for row in final_rows + if row.leaf_disposition is UprightSE2ProofLeafDisposition.UNRESOLVED + ) + if len(self.unresolved_frontier) != len(expected_unresolved) or any( + canonical_json_bytes(frontier.cell_evaluation) + != canonical_json_bytes(expected) + for frontier, expected in zip( + self.unresolved_frontier, expected_unresolved, strict=True + ) + ): + raise ValueError("continuous frontier must be complete") + if self.coverage_artifact.unresolved_cell_sha256s != tuple( + sorted( + row.compiled_cell.compiled_cell_sha256 for row in expected_unresolved + ) + ): + raise ValueError("continuous coverage frontier does not bind leaves") + expected_owner_evaluations = tuple( + sorted( + ( + evaluation + for row in self.evaluated_cells + for evaluation in row.owner_evaluations + ), + key=canonical_json_bytes, + ) + ) + staged_owner_evaluations = tuple( + evaluation + for stage in self.resource_ledger.stage_deltas + for evaluation in stage.owner_evaluations + ) + if tuple(sorted(staged_owner_evaluations, key=canonical_json_bytes)) != ( + expected_owner_evaluations + ): + raise ValueError("continuous shared ledger must account for every owner") + return self + + +UprightSE2MaterializedEndpoint.model_rebuild() +UprightSE2ProofMaterial.model_rebuild() +UprightSE2ContinuousMaterializedEndpoint.model_rebuild() +UprightSE2ContinuousProofMaterial.model_rebuild() + + +def _bound_state_leaf(subject_id: CanonicalId, role: str) -> StateVariableRef: + return StateVariableRef( + state_variable_schema_ref=f"schema:spatialcf/upright-se2/{role}/1.0", + state_schema_ref=_UPRIGHT_SE2_STATE_SCHEMA_REF, + fact_family_ref=_UPRIGHT_SE2_STATE_FAMILY_REF, + entity_or_fact_key=subject_id, + field_path_ref=f"field-path:upright-se2-{role}", + ) + + +def _bound_translation_domain( + problem: CounterfactualProblemIR, + subject_id: CanonicalId, +) -> UprightSE2TranslationDomain: + expected = { + "subject-world-x": _bound_state_leaf(subject_id, "subject-world-x"), + "subject-world-y": _bound_state_leaf(subject_id, "subject-world-y"), + } + bounds = problem.intervention_authorization.variable_bounds + if len(bounds) != len(expected): + raise ValueError("source authorization must bind complete world-XY bounds") + resolved: dict[str, tuple[ExactDyadic, ExactDyadic]] = {} + for bound in bounds: + role = next( + ( + candidate + for candidate, state_leaf in expected.items() + if bound.state_variable_ref == state_leaf + ), + None, + ) + if role is None or role in resolved: + raise ValueError("source authorization bounds must target subject world XY") + if ( + bound.value_schema_ref, + bound.frame_ref, + bound.unit_ref, + bound.topology_ref, + ) != ( + _UPRIGHT_SE2_REAL_SCHEMA_REF, + _UPRIGHT_SE2_WORLD_XY_FRAME_REF, + _UPRIGHT_SE2_METRE_UNIT_REF, + _UPRIGHT_SE2_CLOSED_INTERVAL_TOPOLOGY_REF, + ): + raise ValueError("source authorization bounds use the wrong type semantics") + domain = bound.typed_domain + if ( + domain.value_schema_ref != _UPRIGHT_SE2_REAL_SCHEMA_REF + or type(domain.payload) is not IntervalValue + or domain.payload.endpoint_schema_ref != _UPRIGHT_SE2_REAL_SCHEMA_REF + or type(domain.payload.lower) is not FiniteRealValue + or type(domain.payload.upper) is not FiniteRealValue + or not domain.payload.lower_closed + or not domain.payload.upper_closed + ): + raise ValueError( + "source authorization bounds must be closed finite-real intervals" + ) + lower = ExactDyadic( + numerator=Fraction.from_float(domain.payload.lower.value).numerator, + denominator=Fraction.from_float(domain.payload.lower.value).denominator, + ) + upper = ExactDyadic( + numerator=Fraction.from_float(domain.payload.upper.value).numerator, + denominator=Fraction.from_float(domain.payload.upper.value).denominator, + ) + resolved[role] = (lower, upper) + if set(resolved) != set(expected): + raise ValueError("source authorization must provide both world-XY bounds") + return UprightSE2TranslationDomain( + x_lower=resolved["subject-world-x"][0], + x_upper=resolved["subject-world-x"][1], + y_lower=resolved["subject-world-y"][0], + y_upper=resolved["subject-world-y"][1], + ) + + +def _bound_cardinal_after_yaw( + yaw_before: CanonicalSO2Angle, + q: int, +) -> CanonicalSO2Angle: + turns = ( + Fraction.from_float(yaw_before.turns) + _UPRIGHT_SE2_CARDINAL_TURN_FRACTIONS[q] + ) + while turns < Fraction(-1, 2): + turns += 1 + while turns >= Fraction(1, 2): + turns -= 1 + return CanonicalSO2Angle(turns=0.0 if turns == 0 else float(turns)) + + +def _bound_rotate_cardinal_xy(x: float, y: float, q: int) -> tuple[float, float]: + if q == 0: + return (0.0 if x == 0.0 else x, 0.0 if y == 0.0 else y) + if q == 1: + return (0.0 if y == 0.0 else -y, 0.0 if x == 0.0 else x) + if q == 2: + return (0.0 if x == 0.0 else -x, 0.0 if y == 0.0 else -y) + return (0.0 if y == 0.0 else y, 0.0 if x == 0.0 else -x) + + +def _bound_cardinal_quaternion( + rotation: Quaternion, + q: int, + *, + primary_expected_yaw: CanonicalSO2Angle, +) -> Quaternion: + delta_z, delta_w = _UPRIGHT_SE2_CARDINAL_QUATERNIONS[q] + return _compose_upright_quaternion_from_primary_yaw( + rotation=rotation, + delta_z=delta_z, + delta_w=delta_w, + primary_expected_yaw=primary_expected_yaw, + ) + + +def _bound_cardinal_compiled_cell( + operation: UprightSE2CardinalOperation, +) -> UprightSE2CompiledCell: + """Derive the sole exact cardinal cell from an already bound operation.""" + + yaw = _UPRIGHT_SE2_CARDINAL_TURN_FRACTIONS[operation.quarter_turns_ccw] + endpoint = ExactDyadic(numerator=yaw.numerator, denominator=yaw.denominator) + return UprightSE2CompiledCell.seal( + cell_id=( + f"cell:spatialcf/upright-se2/cardinal/{operation.authorization_sha256}" + ), + authorization_sha256=operation.authorization_sha256, + x_lower=operation.translation_domain.x_lower, + x_upper=operation.translation_domain.x_upper, + y_lower=operation.translation_domain.y_lower, + y_upper=operation.translation_domain.y_upper, + yaw_interval=LiftedYawInterval( + lower=endpoint, + upper=endpoint, + seam_ownership="NONE", + ), + ) + + +def _validate_compilation_source_binding(compilation: UprightSE2Compilation) -> None: + """Recompute the Task 2 transition and closure inputs from the bound request.""" + + source_request = compilation.source_solve_request + problem = source_request.semantic_problem + source_input = _bound_source_input(problem) + scene = problem.scene_state.base_scene_payload + subject = _bound_scene_object(scene, source_input.subject_id) + reference = _bound_scene_object(scene, source_input.reference_id) + authorization = problem.intervention_authorization + if ( + authorization.editable_entity_ids != (f"entity:{source_input.subject_id}",) + or authorization.allowed_operator_refs != (source_input.operator_ref,) + or authorization.authorized_primary_write_set + != tuple( + sorted( + ( + _bound_state_leaf(source_input.subject_id, role) + for role in _UPRIGHT_SE2_PRIMARY_ROLES + ), + key=canonical_json_bytes, + ) + ) + or authorization.maximum_program_steps != 1 + or authorization.maximum_edited_entities != 1 + or authorization.required_derived_rule_refs != (_UPRIGHT_SE2_DERIVED_RULE_REF,) + or authorization.complete_state_delta_policy_ref + != "definition:spatialcf/upright-se2/complete-state-delta/1.0" + ): + raise ValueError("compilation source authorization does not close") + operation = compilation.operation + expected_pivot_mode = ( + PivotMode.OWN + if source_input.operator_ref == UPRIGHT_SE2_CARDINAL_OWN_PIVOT_OPERATOR_REF + else PivotMode.REFERENCE + ) + expected_pivot_id = ( + source_input.subject_id + if expected_pivot_mode is PivotMode.OWN + else source_input.reference_id + ) + expected_pivot_state_sha256 = canonical_sha256( + { + "scene_state_sha256": problem.scene_state.scene_state_sha256, + "pivot_entity_id": expected_pivot_id, + "object_pivot_pose": ( + subject.pose if expected_pivot_mode is PivotMode.OWN else reference.pose + ), + }, + domain=_UPRIGHT_SE2_PIVOT_STATE_HASH_DOMAIN, + ) + if ( + source_input.operator_ref + not in ( + UPRIGHT_SE2_CARDINAL_OWN_PIVOT_OPERATOR_REF, + UPRIGHT_SE2_CARDINAL_REFERENCE_PIVOT_OPERATOR_REF, + ) + or source_input.subject_id == source_input.reference_id + or operation.subject_id != source_input.subject_id + or operation.authorization.operator_ref != source_input.operator_ref + or operation.quarter_turns_ccw != source_input.quarter_turns_ccw + or operation.pivot_binding.pivot_mode is not expected_pivot_mode + or operation.pivot_binding.pivot_entity_id != expected_pivot_id + or operation.pivot_binding.pivot_state_sha256 != expected_pivot_state_sha256 + or operation.translation_domain + != _bound_translation_domain(problem, source_input.subject_id) + ): + raise ValueError("compilation operation does not bind the source request") + subject_pose = subject.pose.world_from_object + reference_pose = reference.pose.world_from_object + validate_directed_yaw_quaternion_consistency( + source_input.subject_yaw_turns, + subject_pose.rotation, + ) + expected_recipe = UprightSE2EndpointConstructionRecipe.seal( + source_scene_state_sha256=problem.scene_state.scene_state_sha256, + operation_authorization_sha256=operation.authorization_sha256, + subject_id=source_input.subject_id, + reference_id=source_input.reference_id, + pivot_binding=operation.pivot_binding, + quarter_turns_ccw=source_input.quarter_turns_ccw, + translation_domain=operation.translation_domain, + subject_before_pose=subject_pose, + reference_before_pose=reference_pose, + subject_yaw_turns=source_input.subject_yaw_turns, + evaluation_scene=scene, + ) + if canonical_json_bytes( + compilation.endpoint_construction_recipe + ) != canonical_json_bytes(expected_recipe): + raise ValueError( + "compilation endpoint recipe does not derive from the source request" + ) + leaf_index = problem.scene_state.canonical_state_leaf_index + footprint = compilation.state_footprint + expected_primary_writes = tuple( + sorted( + ( + _bound_state_leaf(source_input.subject_id, role) + for role in _UPRIGHT_SE2_PRIMARY_ROLES + ), + key=canonical_json_bytes, + ) + ) + expected_derived_writes = tuple( + sorted( + ( + _bound_state_leaf(source_input.subject_id, role) + for role in _UPRIGHT_SE2_DERIVED_ROLES + ), + key=canonical_json_bytes, + ) + ) + expected_written_bytes = { + *(canonical_json_bytes(leaf) for leaf in expected_primary_writes), + *(canonical_json_bytes(leaf) for leaf in expected_derived_writes), + } + expected_frozen_leaf_refs = tuple( + leaf + for leaf in leaf_index.leaves + if canonical_json_bytes(leaf) not in expected_written_bytes + ) + manifest = footprint.state_delta_manifest + partition = tuple( + sorted( + ( + *footprint.state_delta_manifest.authorized_primary_writes, + *footprint.state_delta_manifest.recomputed_derived_writes, + *footprint.frozen_leaf_refs, + ), + key=canonical_json_bytes, + ) + ) + if ( + partition != leaf_index.leaves + or manifest.authorized_primary_writes != expected_primary_writes + or manifest.recomputed_derived_writes != expected_derived_writes + or footprint.frozen_leaf_refs != expected_frozen_leaf_refs + or manifest.unchanged_leaves_digest + != canonical_sha256( + expected_frozen_leaf_refs, + domain=_UPRIGHT_SE2_UNCHANGED_LEAVES_HASH_DOMAIN, + ) + or footprint.complete_before_leaf_index_sha256 + != leaf_index.state_leaf_index_sha256 + or footprint.complete_after_leaf_index_sha256 + != leaf_index.state_leaf_index_sha256 + ): + raise ValueError( + "compilation state footprint does not bind the source leaf index" + ) + expected_cell = _bound_cardinal_compiled_cell(operation) + if len(compilation.compiled_cells) != 1 or canonical_json_bytes( + compilation.compiled_cells[0] + ) != canonical_json_bytes(expected_cell): + raise ValueError("compiled cells do not derive from the source-bound operation") + if ( + compilation.closure.definition_bundle_sha256 + != problem.definition_bundle.definition_bundle_sha256 + or compilation.closure.solve_policy_definition_bundle_sha256 + != source_request.solve_policy_definition_bundle.definition_bundle_sha256 + or dict( + source_request.implementation_registry_snapshot.implementation_build_hashes + ).get(UPRIGHT_SE2_COMPILER_OWNER_REF) + != compilation.closure.compiler_build_sha256 + ): + raise ValueError("compiler closure does not bind the source request") + + +def _validate_continuous_compilation_source_binding( + compilation: UprightSE2ContinuousCompilation, +) -> None: + """Replay continuous operation and recipe roots from the bound request.""" + + source_request = compilation.source_solve_request + problem = source_request.semantic_problem + source_input = _bound_continuous_source_input(problem) + scene = problem.scene_state.base_scene_payload + subject = _bound_scene_object(scene, source_input.subject_id) + reference = _bound_scene_object(scene, source_input.reference_id) + expected_pivot_mode = ( + PivotMode.OWN + if source_input.operator_ref == UPRIGHT_SE2_CONTINUOUS_OWN_PIVOT_OPERATOR_REF + else PivotMode.REFERENCE + ) + expected_pivot_id = ( + source_input.subject_id + if expected_pivot_mode is PivotMode.OWN + else source_input.reference_id + ) + expected_pivot_pose = ( + subject.pose if expected_pivot_mode is PivotMode.OWN else reference.pose + ) + expected_pivot = FixedPivotBinding.seal( + subject_id=source_input.subject_id, + pivot_mode=expected_pivot_mode, + pivot_entity_id=expected_pivot_id, + pivot_state_sha256=canonical_sha256( + { + "scene_state_sha256": problem.scene_state.scene_state_sha256, + "pivot_entity_id": expected_pivot_id, + "object_pivot_pose": expected_pivot_pose, + }, + domain=_UPRIGHT_SE2_PIVOT_STATE_HASH_DOMAIN, + ), + ) + expected_authorization = ContinuousYawAuthorization.seal( + subject_id=source_input.subject_id, + operator_ref=source_input.operator_ref, + pivot_binding=expected_pivot, + yaw_domain=source_input.yaw_domain, + ) + operation = compilation.operation + if ( + source_input.operator_ref + not in ( + UPRIGHT_SE2_CONTINUOUS_OWN_PIVOT_OPERATOR_REF, + UPRIGHT_SE2_CONTINUOUS_REFERENCE_PIVOT_OPERATOR_REF, + ) + or source_input.subject_id == source_input.reference_id + or operation.subject_id != source_input.subject_id + or canonical_json_bytes(operation.authorization) + != canonical_json_bytes(expected_authorization) + or operation.pivot_binding != expected_pivot + or operation.yaw_domain != source_input.yaw_domain + or operation.translation_domain + != _bound_translation_domain(problem, source_input.subject_id) + ): + raise ValueError( + "continuous compilation operation does not bind source request" + ) + subject_pose = subject.pose.world_from_object + reference_pose = reference.pose.world_from_object + validate_directed_yaw_quaternion_consistency( + source_input.subject_yaw_turns, + subject_pose.rotation, + ) + expected_recipe = UprightSE2ContinuousEndpointConstructionRecipe.seal( + source_scene_state_sha256=problem.scene_state.scene_state_sha256, + operation_authorization_sha256=operation.authorization_sha256, + subject_id=source_input.subject_id, + reference_id=source_input.reference_id, + pivot_binding=expected_pivot, + yaw_domain=source_input.yaw_domain, + translation_domain=operation.translation_domain, + subject_before_pose=subject_pose, + reference_before_pose=reference_pose, + subject_yaw_turns=source_input.subject_yaw_turns, + evaluation_scene=scene, + ) + if canonical_json_bytes( + compilation.endpoint_construction_recipe + ) != canonical_json_bytes(expected_recipe): + raise ValueError( + "continuous compilation endpoint recipe does not derive from source request" + ) + + +# ``UprightSE2ProofMaterial`` is deliberately declared before the compilation it +# transports so the existing verification bundle keeps its public placement. +# Resolve that forward reference only after the complete source-bound compiler +# record is available. +UprightSE2CardinalProofTuple.model_rebuild() +UprightSE2ProofMaterial.model_rebuild() +UprightSE2VerificationBundle.model_rebuild()