1 parent 10b6c45 commit 6dccf52Copy full SHA for 6dccf52
4 files changed
src/jacobian/contracts/combinatorics.py
@@ -895,7 +895,9 @@ def require_complete_replay_prefix(self) -> Self:
895
raise ValueError("the greatest requested index must bind replay_scope_end")
896
if any(item.value != self.replay_prefix[item.index] for item in self.values):
897
raise ValueError("indexed values must match the recurrence replay prefix")
898
- if self.scope == "PREFIX" and indices != tuple(range(len(indices))):
+ if self.scope == "PREFIX" and indices != tuple(
899
+ range(self.replay_scope_end + 1)
900
+ ):
901
raise ValueError(
902
"PREFIX results must contain consecutive indices from zero"
903
)
src/jacobian_checkers/recurrence_series.py
@@ -162,6 +162,7 @@ def _validate_recurrence_values(
162
if (
163
not isinstance(item, dict)
164
or set(item) != {"index", "value"}
165
+ or type(item["index"]) is not int
166
or item["index"] != index
167
or _fraction(item["value"], max_digits=32_768) != replay[index]
168
):
@@ -281,6 +282,7 @@ def _p_recursive_residuals_match(
281
282
return all(
283
isinstance(item, dict)
284
and set(item) == {"index", "value"}
285
+ and type(item["index"]) is int
286
and item["index"] == index
287
and _fraction(item["value"], max_digits=32_768) == residual
288
and residual == 0
@@ -354,7 +356,12 @@ def _replay_polynomial_coefficient_recurrence(
354
356
return False
355
357
initial = _fractions(source["initial_values"], minimum=1, maximum=16, max_digits=64)
358
order = len(polynomials) - 1
- if len(initial) != order or result["recurrence_order"] != order:
359
+ if (
360
+ len(initial) != order
361
+ or type(result["recurrence_order"]) is not int
362
+ or not 1 <= result["recurrence_order"] <= 16
363
+ or result["recurrence_order"] != order
364
365
366
raw_indices = source["indices"]
367
if not isinstance(raw_indices, list) or len(raw_indices) > 256:
tests/component/checkers/test_recurrence_series_checker.py
@@ -224,3 +224,28 @@ def test_recurrence_series_checker_has_no_sympy_or_producer_dependency() -> None
224
source = inspect.getsource(checker_module)
225
assert "import sympy" not in source
226
assert "domains.combinatorics" not in source
227
+
228
229
+@pytest.mark.parametrize(
230
+ ("path", "value"),
231
+ [
232
+ (("recurrence_order",), True),
233
+ (("values", 0, "index"), False),
234
+ (("residuals", 0, "index"), True),
235
+ ],
236
+)
237
+def test_polynomial_recurrence_checker_rejects_boolean_result_integers(
238
+ path: tuple[str | int, ...],
239
+ value: bool,
240
+) -> None:
241
+ forged = copy.deepcopy(_CASES[1][1])
242
+ target: Any = forged["candidate"]["payload"]
243
+ for key in path[:-1]:
244
+ target = target[key]
245
+ target[path[-1]] = value
246
+ forged["candidate"]["payload_digest"] = _digest(forged["candidate"]["payload"])
247
248
+ checked = check_polynomial_coefficient_recurrence_evaluation(forged)
249
250
+ assert checked["accepted"] is False
251
+ assert checked["conclusion"] == "UNKNOWN"
tests/unit/contracts/test_recurrence_contracts.py
@@ -51,6 +51,19 @@ def _result() -> dict[str, object]:
51
{"index": 2, "value": _q(2)},
52
{"index": 3, "value": _q(3)},
53
],
54
55
+ {"index": 0, "value": _q(1)},
56
+ {"index": 2, "value": _q(2)},
57
+ {"index": 1, "value": _q(1)},
58
+ {"index": 3, "value": _q(3)},
59
60
61
62
63
64
65
+ {"index": 4, "value": _q(5)},
66
67
68
69
def test_polynomial_recurrence_result_rejects_malformed_prefix_projection(
@@ -76,6 +89,10 @@ def test_polynomial_recurrence_result_rejects_malformed_prefix_projection(
76
89
{"index": 2, "value": _q(0)},
77
90
{"index": 4, "value": _q(0)},
78
91
92
93
+ {"index": 3, "value": _q(0)},
94
+ {"index": 2, "value": _q(0)},
95
79
96
80
97
81
98
def test_polynomial_recurrence_result_requires_exact_residual_range(
0 commit comments