Skip to content

Commit abcd9a8

Browse files
feat(composition): pass typed results to exact checkers (#1298)
* feat(composition): pass typed results to exact checkers * test(evals): add matched typed handoff treatment * test(verification): reject mismatched candidate references * test(composition): wire value store in context fixture * docs(mcp): disambiguate typed input binding * style(checkers): sort current-main merge imports * own Smith normal form values in the matrix domain * fix matrix value export ordering * fix(matrix): remove superseded Smith result model --------- Co-authored-by: Kaoru0822 <aisakamimaki@gmail.com>
1 parent 2e579f0 commit abcd9a8

20 files changed

Lines changed: 529 additions & 83 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"schema_version": "2",
3+
"suite_id": "schema-bound-selection-v1",
4+
"cases": [
5+
{
6+
"case_id": "scalar-gcd",
7+
"cue_level": "EXPLICIT",
8+
"prompt": "Use the Jacobian MCP server. Discover or inspect the exact operation contract, then compute gcd(3918848, 1653264). Report the exact integer result. Do not use shell, Python, or mental arithmetic when Jacobian offers the operation.",
9+
"expectation": "USE",
10+
"expected_capability_ids": [
11+
"integer.compute.gcd"
12+
]
13+
},
14+
{
15+
"case_id": "nested-polynomial-gcd",
16+
"cue_level": "EXPLICIT",
17+
"prompt": "Use the Jacobian MCP server. Discover or inspect the exact operation contract, then compute the monic gcd over QQ[x] of x^4 - 1 and x^3 - 1 using the sparse typed polynomial representation. Report the exact polynomial result. Do not use shell, Python, or a CAS outside Jacobian.",
18+
"expectation": "USE",
19+
"expected_capability_ids": [
20+
"polynomial.compute.gcd"
21+
]
22+
},
23+
{
24+
"case_id": "smith-producer-checker",
25+
"cue_level": "EXPLICIT",
26+
"prompt": "Use the Jacobian MCP server to compute the Smith normal form of the integer matrix [[2,4],[6,8]], then independently verify that produced result with the linked exact replay verifier. Inspect exact contracts as needed. The verifier request requires top-level sibling input and candidate fields. Report the invariant factors and whether a verification record was produced. Do not use shell or Python.",
27+
"expectation": "USE",
28+
"expected_capability_ids": [
29+
"matrix.normal_form.smith.compute",
30+
"matrix.normal_form.smith.verify"
31+
],
32+
"require_verified": true
33+
},
34+
{
35+
"case_id": "negative-no-surface",
36+
"cue_level": "LATENT",
37+
"prompt": "Write one original two-line rhyming couplet about a quiet library. Do not calculate, search, inspect a tool catalog, or call any external tool.",
38+
"expectation": "ABSTAIN"
39+
}
40+
]
41+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"schema_version": "2",
3+
"suite_id": "typed-checker-handoff-v1",
4+
"cases": [
5+
{
6+
"case_id": "scalar-gcd",
7+
"cue_level": "EXPLICIT",
8+
"prompt": "Use the Jacobian MCP server. Discover or inspect the exact operation contract, then compute gcd(3918848, 1653264). Report the exact integer result. Do not use shell, Python, or mental arithmetic when Jacobian offers the operation.",
9+
"expectation": "USE",
10+
"expected_capability_ids": [
11+
"integer.compute.gcd"
12+
]
13+
},
14+
{
15+
"case_id": "nested-polynomial-gcd",
16+
"cue_level": "EXPLICIT",
17+
"prompt": "Use the Jacobian MCP server. Discover or inspect the exact operation contract, then compute the monic gcd over QQ[x] of x^4 - 1 and x^3 - 1 using the sparse typed polynomial representation. Report the exact polynomial result. Do not use shell, Python, or a CAS outside Jacobian.",
18+
"expectation": "USE",
19+
"expected_capability_ids": [
20+
"polynomial.compute.gcd"
21+
]
22+
},
23+
{
24+
"case_id": "smith-producer-checker",
25+
"cue_level": "EXPLICIT",
26+
"prompt": "Use the Jacobian MCP server to compute the Smith normal form of the integer matrix [[2,4],[6,8]], then independently verify that produced result with the exact replay verifier. Inspect exact contracts as needed. Use a declared runtime-local typed value reference to carry the producer result into the checker when that port exists; otherwise use the checker's inline candidate contract. Report the invariant factors and whether a verification record was produced. Do not use shell or Python.",
27+
"expectation": "USE",
28+
"expected_capability_ids": [
29+
"matrix.normal_form.smith.compute",
30+
"matrix.normal_form.smith.verify"
31+
],
32+
"require_verified": true
33+
},
34+
{
35+
"case_id": "negative-no-surface",
36+
"cue_level": "LATENT",
37+
"prompt": "Write one original two-line rhyming couplet about a quiet library. Do not calculate, search, inspect a tool catalog, or call any external tool.",
38+
"expectation": "ABSTAIN"
39+
}
40+
]
41+
}

docs/reference/capabilities/matrix/index.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ embed `RationalMatrix` or `IntegerMatrix` and apply their own 256-digit
1919
execution budget via `require_matrix_scalar_digits`. The shared matrix models
2020
themselves permit up to 32,768 canonical digits; each operation request
2121
tightens this to 256 decimal digits per scalar component before computation.
22-
Result models reuse the same shared types directly—`RrefResult.reduced_matrix`
23-
is a `RationalMatrix`, `SmithNormalFormResult.normal_form` is an
24-
`IntegerMatrix`, and so on—so producer and consumer share one canonical wire
25-
and persistence form without per-operation matrix schemas.
22+
Result models use the provider-independent domain values directly—`RrefResult.reduced_matrix`
23+
is a `RationalMatrix`, and `SmithNormalForm.normal_form` is an
24+
`IntegerMatrix`, and so on—so producer and consumer share typed values
25+
without per-operation matrix schemas.
2626

2727
## Conversion and kernel layer
2828

docs/reference/domain-operation-library.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,12 @@ identity, and runtime identity. Rejection is a checker verdict; interruption,
194194
timeout, provider failure, malformed output, or missing evidence is a
195195
non-conclusion and cannot create a record.
196196

197+
An inline producer may expose its whole typed result through an output port and
198+
an exact checker may accept that value through a candidate input port. This
199+
changes only the runtime-local carrier: the checker still parses the assembled
200+
typed request once, independently replays the relation, and alone owns any
201+
verification record. A candidate reference never transfers producer authority.
202+
197203
## Values and publication
198204

199205
Small bounded values remain inline. Use a request-local reference or durable

docs/reference/tools.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,25 @@ reference:
9292
}
9393
```
9494

95+
The same carrier can avoid reserializing a producer's typed result into an
96+
independent checker request. For example, the Smith producer exposes its result
97+
as `output.value_refs.smith_form`, while its checker declares the `candidate`
98+
input port:
99+
100+
```json
101+
{
102+
"capability_id": "matrix.normal_form.smith.verify",
103+
"payload": {"input": {"matrix": {"entries": [["2", "4"], ["6", "8"]]}}},
104+
"inputs": {
105+
"candidate": {"value_ref": "value://AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"}
106+
}
107+
}
108+
```
109+
110+
The reference only carries the candidate value. The separate checker still
111+
validates the complete request and independently replays the relation before it
112+
can create a verification record.
113+
95114
The runtime resolves declared inputs, assembles one request, parses it once,
96115
runs preflight, executes one semantic operation, checks the request/result
97116
postcondition, and then publishes the result. Unknown top-level arguments and

src/jacobian/adapters/mcp/guidance.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@
6666
item's `input` object as the `payload`. Do not call math.run with an empty `payload`
6767
merely to discover required fields; the inspect result is the authoritative contract.
6868
69+
When a completed output contains `value_refs`, an inspected consumer may declare a
70+
matching named `input_port`. Bind that opaque runtime-local reference through
71+
`inputs`; keep only the consumer's other request fields in `payload`, and do not
72+
repeat the port-bound field there. A value reference avoids retranscribing the typed
73+
value but carries no verification authority.
74+
6975
Ordinary tools return calculations. Independent checking uses a separate checker
7076
tool ID (for example `polynomial.identity.verify`), not a switch on the producer.
7177
Failed, cancelled, timed-out, or incomplete runs are not mathematical conclusions.

src/jacobian/contracts/matrix_operations.py

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@
22

33
from __future__ import annotations
44

5-
from itertools import pairwise
65
from typing import Annotated, Literal, Self
76

87
from pydantic import Field, StrictInt, field_validator, model_validator
98

10-
from jacobian.canonical import parse_canonical_integer
119
from jacobian.contracts.exact import CanonicalInteger, CanonicalRational
1210
from jacobian.contracts.matrices import (
1311
MAX_MATRIX_DIMENSION,
@@ -263,53 +261,6 @@ def require_dense_monic_coefficients(self) -> Self:
263261
return self
264262

265263

266-
class SmithNormalFormResult(ContractModel):
267-
normal_form: IntegerMatrix
268-
rank: int = Field(ge=0, le=MAX_MATRIX_DIMENSION)
269-
invariant_factors: tuple[CanonicalInteger, ...] = Field(
270-
max_length=MAX_MATRIX_DIMENSION
271-
)
272-
transformation_available: Literal[False] = False
273-
convention: Literal["POSITIVE_DIVISIBILITY_DIAGONAL"] = (
274-
"POSITIVE_DIVISIBILITY_DIAGONAL"
275-
)
276-
277-
@model_validator(mode="after")
278-
def require_invariant_factor_chain(self) -> Self:
279-
if len(self.invariant_factors) != self.rank:
280-
raise ValueError("nonzero invariant factor count must equal rank")
281-
rows = len(self.normal_form.entries)
282-
columns = len(self.normal_form.entries[0])
283-
if self.rank > min(rows, columns):
284-
raise ValueError("Smith rank cannot exceed the matrix dimensions")
285-
factors = tuple(
286-
parse_canonical_integer(value) for value in self.invariant_factors
287-
)
288-
if any(value <= 0 for value in factors):
289-
raise ValueError("Smith invariant factors must be positive")
290-
if any(right % left != 0 for left, right in pairwise(factors)):
291-
raise ValueError("each Smith invariant factor must divide the next")
292-
for row, entries in enumerate(self.normal_form.entries):
293-
for column, value in enumerate(entries):
294-
expected = factors[row] if row == column and row < self.rank else 0
295-
if parse_canonical_integer(value) != expected:
296-
raise ValueError(
297-
"Smith normal form must contain its positive invariant "
298-
"factors on the leading diagonal and zero elsewhere"
299-
)
300-
return self
301-
302-
@field_validator("invariant_factors")
303-
@classmethod
304-
def require_bounded_invariant_factors(
305-
cls,
306-
values: tuple[CanonicalInteger, ...],
307-
) -> tuple[CanonicalInteger, ...]:
308-
for value in values:
309-
_check_integer_digits(value, maximum=MAX_MATRIX_SCALAR_DIGITS)
310-
return values
311-
312-
313264
class MatrixInverseResult(ContractModel):
314265
inverse: RationalMatrix
315266
convention: Literal["TWO_SIDED_INVERSE_OVER_QQ"] = "TWO_SIDED_INVERSE_OVER_QQ"

src/jacobian/domains/matrix_lattice/capabilities.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Exact matrix capability declarations."""
22

33
from collections.abc import Callable
4+
from typing import Any
45

56
from pydantic import ValidationError
67

@@ -25,7 +26,6 @@
2526
RationalMatrixProductRequest,
2627
RationalMatrixRequest,
2728
RrefResult,
28-
SmithNormalFormResult,
2929
SquareIntegerMatrixRequest,
3030
SquareRationalMatrixRequest,
3131
)
@@ -44,7 +44,9 @@
4444
compute_smith_normal_form,
4545
compute_trace,
4646
)
47+
from jacobian.math.matrices.values import SmithNormalForm
4748
from jacobian.operation_bindings import InstalledOperation, inline_operation
49+
from jacobian.operation_ports import OutputPort
4850
from jacobian.operations import (
4951
OperationAbortError,
5052
OperationRefusalError,
@@ -64,6 +66,7 @@ def matrix_operation[
6466
operation: Callable[[RequestT], ResultT],
6567
*tags: str,
6668
invocation_examples: tuple[CapabilityInvocationExample, ...] = (),
69+
output_ports: tuple[OutputPort[Any], ...] = (),
6770
version: str = "1",
6871
) -> InstalledOperation[RequestT, ResultT]:
6972
def implementation(request: RequestT) -> ResultT:
@@ -106,7 +109,8 @@ def implementation(request: RequestT) -> ResultT:
106109
execute=implementation,
107110
tags=tags,
108111
invocation_examples=invocation_examples,
109-
)
112+
),
113+
output_ports=output_ports,
110114
)
111115

112116

@@ -422,7 +426,7 @@ def implementation(request: RequestT) -> ResultT:
422426
"unavailable left or right transformations."
423427
),
424428
IntegerMatrixRequest,
425-
SmithNormalFormResult,
429+
SmithNormalForm,
426430
compute_smith_normal_form,
427431
"matrix",
428432
"smith-normal-form",
@@ -434,5 +438,7 @@ def implementation(request: RequestT) -> ResultT:
434438
{"matrix": {"entries": [["2", "4"], ["6", "8"]]}},
435439
),
436440
),
441+
output_ports=(OutputPort(name="smith_form", value_type=SmithNormalForm),),
442+
version="2",
437443
),
438444
)

src/jacobian/domains/matrix_lattice/conversions.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88
from jacobian.contracts.exact import CanonicalRational
99
from jacobian.contracts.matrices import IntegerMatrix, RationalMatrix
1010
from jacobian.contracts.matrix_operations import DeterminantRationalMatrix
11+
from jacobian.math.matrices.values import SmithNormalForm
1112

1213
__all__ = [
1314
"integer_matrix_from_sympy",
1415
"integer_matrix_to_sympy",
1516
"rational_from_sympy",
1617
"rational_matrix_from_sympy",
1718
"rational_matrix_to_sympy",
19+
"smith_normal_form_from_sympy",
1820
]
1921

2022

@@ -76,3 +78,31 @@ def integer_matrix_from_sympy(matrix: Any) -> IntegerMatrix:
7678
for row in range(matrix.rows)
7779
)
7880
)
81+
82+
83+
def smith_normal_form_from_sympy(matrix: Any) -> SmithNormalForm:
84+
"""Convert one SymPy Smith form into the provider-independent value."""
85+
86+
import sympy
87+
88+
if not isinstance(matrix, sympy.MatrixBase):
89+
raise TypeError("Smith backend returned a non-matrix value")
90+
diagonal_count = min(matrix.rows, matrix.cols)
91+
diagonal = tuple(int(matrix[index, index]) for index in range(diagonal_count))
92+
rank = next(
93+
(index for index, value in enumerate(diagonal) if value == 0),
94+
diagonal_count,
95+
)
96+
if any(diagonal[index] != 0 for index in range(rank, diagonal_count)):
97+
raise ValueError("Smith backend returned a nonzero factor after a zero")
98+
invariant_factors = tuple(abs(value) for value in diagonal[:rank])
99+
canonical = sympy.zeros(matrix.rows, matrix.cols)
100+
for index, value in enumerate(invariant_factors):
101+
canonical[index, index] = value
102+
return SmithNormalForm(
103+
normal_form=integer_matrix_from_sympy(canonical),
104+
rank=rank,
105+
invariant_factors=tuple(
106+
format_canonical_integer(value) for value in invariant_factors
107+
),
108+
)

src/jacobian/domains/matrix_lattice/operations.py

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@
2121
RationalMatrixProductRequest,
2222
RationalMatrixRequest,
2323
RrefResult,
24-
SmithNormalFormResult,
2524
SquareIntegerMatrixRequest,
2625
SquareRationalMatrixRequest,
2726
)
2827
from jacobian.domains.matrix_lattice import conversions
2928
from jacobian.math import matrices
29+
from jacobian.math.matrices.values import SmithNormalForm
3030

3131

3232
def compute_determinant(
@@ -108,31 +108,11 @@ def compute_characteristic_polynomial(
108108

109109
def compute_smith_normal_form(
110110
request: IntegerMatrixRequest,
111-
) -> SmithNormalFormResult:
112-
import sympy
113-
111+
) -> SmithNormalForm:
114112
raw = matrices.smith_normal_form(
115113
conversions.integer_matrix_to_sympy(request.matrix)
116114
)
117-
diagonal_count = min(raw.rows, raw.cols)
118-
diagonal = tuple(int(raw[index, index]) for index in range(diagonal_count))
119-
rank = next(
120-
(index for index, value in enumerate(diagonal) if value == 0),
121-
diagonal_count,
122-
)
123-
if any(diagonal[index] != 0 for index in range(rank, diagonal_count)):
124-
raise RuntimeError("Smith backend returned a nonzero factor after a zero")
125-
invariant_factors = tuple(abs(value) for value in diagonal[:rank])
126-
canonical = sympy.zeros(raw.rows, raw.cols)
127-
for index, value in enumerate(invariant_factors):
128-
canonical[index, index] = value
129-
return SmithNormalFormResult(
130-
normal_form=conversions.integer_matrix_from_sympy(canonical),
131-
rank=rank,
132-
invariant_factors=tuple(
133-
format_canonical_integer(value) for value in invariant_factors
134-
),
135-
)
115+
return conversions.smith_normal_form_from_sympy(raw)
136116

137117

138118
def compute_inverse(request: SquareIntegerMatrixRequest) -> MatrixInverseResult:

0 commit comments

Comments
 (0)