Skip to content

Commit 2174b32

Browse files
polynomial: bound canonical input accumulation
1 parent de5593d commit 2174b32

3 files changed

Lines changed: 119 additions & 12 deletions

File tree

docs/reference/evaluations/canonical-input-recovery-pr1.md

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,15 @@ canonicalization-plus-recovery condition.
108108
## Engineering closeout
109109

110110
The accepted canonicalization mechanism was rebased without an experimental
111-
rerun onto upstream `519dd7b9e34b641dcc138e03af6ffc6e3ed736af`. Review hardening
112-
now validates a canonical-support representative of the complete typed request
113-
before duplicate-coefficient accumulation, caps each coefficient involved in an
114-
accumulation at 256 digits, and validates the exact combined request again. This
115-
preserves canonical candidate and artifact identity while failing closed before
116-
expensive arithmetic for invalid cross-field requests.
111+
rerun onto upstream `71fa917c289c6214733d369b3dce35904ff47c18`. Review hardening
112+
now validates an inverse-verification structural representative of the complete
113+
typed request before duplicate-coefficient accumulation, caps each coefficient
114+
involved in an accumulation at 256 digits, caps each duplicate group at 64
115+
terms, and validates the exact combined request again. The dedicated preflight
116+
checks ordered-ring alignment without applying composition budgets to supports
117+
that exact cancellation may remove. This preserves canonical candidate and
118+
artifact identity, accepts representations whose out-of-budget terms cancel,
119+
and fails closed before unbounded duplicate arithmetic or artifact writes.
117120

118121
The final-tree focused lane passed 48 contract, inverse-composition, and identity
119122
boundary tests in tmux session `jac-pr920-review-hardening-r3`. `make check`
@@ -125,3 +128,17 @@ restack, the same 48-test lane passed again in `jac-pr920-latest-final`, and
125128
`make check` passed 884 unit tests plus lint, format, and type checking in
126129
`jac-pr920-latest-check`. The final checker-seam restack passed the 48 focused
127130
tests and `make check` with 854 unit tests in `jac-pr920-main519-final`.
131+
After rebasing onto `71fa917c`, the two final review findings were reproduced and
132+
covered by the focused lane: cancelled degree-33 terms no longer trigger the
133+
degree-32 operation budget, and a 65-term duplicate group is rejected before
134+
coefficient accumulation. The final focused lane contains 56 tests, including
135+
the interval-verification seam exposed by the broad gate; no model rollout was
136+
rerun.
137+
138+
The final `make test-changed BASE=origin/main` run passed unit, domain,
139+
composition, storage, process, MCP, end-to-end, static, build, and documentation
140+
lanes. Its component lane had one unrelated macOS timeout-marker failure in
141+
`test_carcara_timeout_fails_closed`; the branch does not change that checker or
142+
test, and the same environment race also reproduced in the isolated DRAT timeout
143+
test. This is retained as an upstream/environment obligation rather than folded
144+
into the canonicalization change.

src/jacobian/polynomials/_support.py

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
PolynomialMapEvaluation,
3737
PolynomialMapInverseSynthesisRequest,
3838
PolynomialMapInverseVerifyRequest,
39+
PolynomialVariable,
3940
RationalPolynomialMap,
4041
RationalPolynomialPoint,
4142
RationalPolynomialTerm,
@@ -57,6 +58,7 @@
5758

5859
_INVERSE_SOLVER_SHUTDOWN_TIMEOUT_SECONDS = 1.0
5960
_MAX_CANONICALIZATION_COEFFICIENT_DIGITS = 256
61+
_MAX_CANONICALIZATION_DUPLICATE_TERMS = 64
6062

6163

6264
class _SparsePolynomialInputTerm(ContractModel):
@@ -89,12 +91,40 @@ class _SparsePolynomialInput(ContractModel):
8991
)
9092

9193

94+
class _PolynomialMapInverseVerifyInput(ContractModel):
95+
"""Inverse-check request shape without composition operation budgets."""
96+
97+
forward_map: RationalPolynomialMap
98+
inverse_map: RationalPolynomialMap
99+
source_variables: tuple[PolynomialVariable, ...] = Field(min_length=1, max_length=4)
100+
target_variables: tuple[PolynomialVariable, ...] = Field(min_length=1, max_length=4)
101+
102+
@model_validator(mode="after")
103+
def require_compatible_ordered_rings(self) -> Self:
104+
if self.forward_map.variables != self.source_variables:
105+
raise ValueError("forward map variables must equal source_variables")
106+
if self.inverse_map.variables != self.target_variables:
107+
raise ValueError("inverse map variables must equal target_variables")
108+
if len(self.source_variables) != len(self.target_variables):
109+
raise ValueError("source and target dimensions must agree")
110+
if len(self.forward_map.coordinates) != len(self.target_variables):
111+
raise ValueError("forward map coordinate count must match target_variables")
112+
if len(self.inverse_map.coordinates) != len(self.source_variables):
113+
raise ValueError("inverse map coordinate count must match source_variables")
114+
return self
115+
116+
92117
def _require_bounded_duplicate_accumulation(
93118
parsed: _SparsePolynomialInput,
94119
) -> None:
95120
counts: dict[tuple[int, ...], int] = {}
96121
for term in parsed.terms:
97122
counts[term.exponents] = counts.get(term.exponents, 0) + 1
123+
if any(count > _MAX_CANONICALIZATION_DUPLICATE_TERMS for count in counts.values()):
124+
raise ValueError(
125+
"duplicate-term canonicalization exceeds the "
126+
f"{_MAX_CANONICALIZATION_DUPLICATE_TERMS}-term accumulation budget"
127+
)
98128
for term in parsed.terms:
99129
if counts[term.exponents] > 1:
100130
require_bounded_rational(
@@ -107,7 +137,7 @@ def _require_bounded_duplicate_accumulation(
107137
def _structural_sparse_polynomial(
108138
value: object,
109139
) -> SparseRationalPolynomial:
110-
"""Build a cheap canonical support representative without coefficient sums."""
140+
"""Build a cheap shape representative without applying operation budgets."""
111141

112142
parsed = _SparsePolynomialInput.model_validate(value)
113143
_require_bounded_duplicate_accumulation(parsed)
@@ -601,11 +631,12 @@ def _validate_request[RequestModel: ContractModel](
601631
error_factory: Callable[[str, str, str], CapabilityInvocationError] | None = None,
602632
) -> RequestModel:
603633
try:
604-
structural_payload = _normalize_sparse_polynomial_inputs(
605-
payload,
606-
normalize=_structural_sparse_polynomial,
607-
)
608-
model.model_validate(structural_payload)
634+
if model is PolynomialMapInverseVerifyRequest:
635+
structural_payload = _normalize_sparse_polynomial_inputs(
636+
payload,
637+
normalize=_structural_sparse_polynomial,
638+
)
639+
_PolynomialMapInverseVerifyInput.model_validate(structural_payload)
609640
canonical_payload = _normalize_sparse_polynomial_inputs(
610641
payload,
611642
normalize=_canonical_sparse_polynomial,

tests/composition/runtime/test_polynomial_map_inverse_verify.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,65 @@ def test_duplicate_accumulation_rejects_oversized_coefficients(
228228
assert result.artifact_uris == ()
229229

230230

231+
def test_duplicate_accumulation_rejects_oversized_groups(
232+
authorized_complete_runtime,
233+
) -> None:
234+
forward, inverse = _triangular_maps()
235+
forward["coordinates"][0]["terms"] = [
236+
_term(1, [1, 0])
237+
for _ in range(polynomial_support._MAX_CANONICALIZATION_DUPLICATE_TERMS + 1)
238+
]
239+
240+
result = authorized_complete_runtime.core.capabilities.invoke(
241+
_request(forward, inverse)
242+
)
243+
244+
assert result.execution.status is ExecutionStatus.ERROR
245+
assert result.output["error"]["code"] == "INVALID_POLYNOMIAL_MAP_INVERSE_REQUEST"
246+
assert result.artifact_uris == ()
247+
248+
249+
def test_cancelled_high_degree_terms_do_not_apply_operation_budget(
250+
authorized_complete_runtime,
251+
) -> None:
252+
forward = {
253+
"map_schema_version": "1",
254+
"domain": "QQ",
255+
"variables": ["x"],
256+
"coordinates": [
257+
{
258+
"terms": [
259+
_term(1, [33]),
260+
_term(-1, [33]),
261+
_term(1, [1]),
262+
]
263+
}
264+
],
265+
}
266+
inverse = {
267+
"map_schema_version": "1",
268+
"domain": "QQ",
269+
"variables": ["u"],
270+
"coordinates": [{"terms": [_term(1, [1])]}],
271+
}
272+
273+
result = authorized_complete_runtime.core.capabilities.invoke(
274+
CapabilityRequest(
275+
capability_id="polynomial.map.inverse.verify",
276+
mode=CapabilityMode.VERIFY,
277+
input={
278+
"forward_map": forward,
279+
"inverse_map": inverse,
280+
"source_variables": ["x"],
281+
"target_variables": ["u"],
282+
},
283+
)
284+
)
285+
286+
assert result.output["inverse_verified"] is True
287+
assert result.assurance.level is CapabilityAssuranceLevel.VERIFIED
288+
289+
231290
def test_overlapping_variable_names_use_simultaneous_composition(
232291
authorized_complete_runtime,
233292
) -> None:

0 commit comments

Comments
 (0)