Skip to content

Commit 9e87a45

Browse files
yuelgrace1810-opsmorlutoGrace Lee Rui Yue
authored
Derive verifier navigation from installed checker metadata (#899)
* Fix graph reliability verifier discovery * Derive verifier navigation from checker metadata * fix: compact discovery relationships within byte limit * fix: bound capability relationship inspection * fix: avoid duplicate full-view relationships * fix: reserve verifier relationships for checker authority * fix: keep verifier relationship authority installer-private * fix: fail closed on oversized capability inspection * fix: scope inspection bounds to relationships * test: avoid fixed checker inventory count * test: cover oversized exact inspections * fix: account for discovery metadata before compaction * fix: snapshot validated capability descriptors --------- Co-authored-by: morluto <76467478+morluto@users.noreply.github.com> Co-authored-by: Grace Lee Rui Yue <graceleeruiyue@Graces-MacBook-Air-2.local>
1 parent 1a5048c commit 9e87a45

14 files changed

Lines changed: 865 additions & 79 deletions

File tree

src/jacobian/adapters/mcp/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
_LOGGER = logging.getLogger(__name__)
1010
CAPABILITY_DISCOVERY_RESPONSE_BYTE_LIMIT = 16_384
11+
CAPABILITY_INSPECTION_RELATIONSHIPS_BYTE_LIMIT = 16_384
1112
CapabilityDescriptionView = Literal["SUMMARY", "CONTRACT", "FULL"]
1213

1314

src/jacobian/adapters/mcp/projections.py

Lines changed: 82 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
import threading
99
from typing import TYPE_CHECKING, Any, Literal, cast
1010

11-
from jacobian.adapters.mcp.constants import CAPABILITY_DISCOVERY_RESPONSE_BYTE_LIMIT
11+
from jacobian.adapters.mcp.constants import (
12+
CAPABILITY_DISCOVERY_RESPONSE_BYTE_LIMIT,
13+
CAPABILITY_INSPECTION_RELATIONSHIPS_BYTE_LIMIT,
14+
)
1215
from jacobian.bounded_process import bounded_process_cancellation
1316
from jacobian.canonical import canonicalize_json
1417
from jacobian.capability_service import CapabilityDiscoveryCursorError
@@ -62,54 +65,6 @@ def _mcp_text_json_bytes(value: object) -> bytes:
6265
"materialize named Boolean CNF for finite colorings and forbidden patterns",
6366
),
6467
),
65-
"graph.invariant.maximum_matching.compute": (
66-
(
67-
"graph.invariant.maximum_matching.verify",
68-
"independently replay the stored Tutte-Berge certificate",
69-
),
70-
),
71-
"graph.invariant.maximum_matching.verify": (
72-
(
73-
"graph.invariant.maximum_matching.compute",
74-
"produce a matching witness and Tutte-Berge certificate",
75-
),
76-
),
77-
"graph.hamiltonian_path.decide": (
78-
(
79-
"graph.hamiltonian_path.verify",
80-
"independently verify the stored positive or negative decision",
81-
),
82-
),
83-
"graph.hamiltonian_path.verify": (
84-
(
85-
"graph.hamiltonian_path.decide",
86-
"produce a complete bounded decision and optional path witness",
87-
),
88-
),
89-
"polynomial.jacobian_syzygy.minimum_degree.compute": (
90-
(
91-
"polynomial.jacobian_syzygy.minimum_degree.verify",
92-
"independently rebuild the graded maps, ranks, minors, and first kernel",
93-
),
94-
),
95-
"polynomial.jacobian_syzygy.minimum_degree.verify": (
96-
(
97-
"polynomial.jacobian_syzygy.minimum_degree.compute",
98-
"produce the provenance-bound graded rank ledger and kernel witness",
99-
),
100-
),
101-
"geometry.projective_line_arrangement.flats.materialize": (
102-
(
103-
"geometry.projective_line_arrangement.flats.verify",
104-
"independently rebuild all projective flats and pair accounting",
105-
),
106-
),
107-
"geometry.projective_line_arrangement.flats.verify": (
108-
(
109-
"geometry.projective_line_arrangement.flats.materialize",
110-
"materialize normalized lines, exact flats, incidences and multiplicities",
111-
),
112-
),
11368
}
11469

11570

@@ -128,16 +83,27 @@ def _capability_inspection_extensions(
12883
descriptors: dict[str, CapabilityDescriptor],
12984
) -> dict[str, Any]:
13085
extensions: dict[str, Any] = {}
131-
related = [
86+
related = {
87+
item.capability_id: item.model_dump(mode="json")
88+
for item in descriptors[capability_id].related_capabilities
89+
if item.capability_id in descriptors and item.capability_id != capability_id
90+
}
91+
related.update(
13292
{
133-
"capability_id": related_id,
134-
"relationship": relationship,
93+
related_id: {
94+
"capability_id": related_id,
95+
"relationship": relationship,
96+
}
97+
for related_id, relationship in _RELATED_CAPABILITIES.get(capability_id, ())
98+
if related_id in descriptors
99+
and related_id != capability_id
100+
and related_id not in related
135101
}
136-
for related_id, relationship in _RELATED_CAPABILITIES.get(capability_id, ())
137-
if related_id in descriptors
138-
]
102+
)
139103
if related:
140-
extensions["related_capabilities"] = related
104+
extensions["related_capabilities"] = [
105+
related[related_id] for related_id in sorted(related)
106+
]
141107
if capability_id.startswith(("sat.", "smt.")):
142108
extensions["synchronous_execution"] = {
143109
"remote_safe_wall_seconds_max": 150,
@@ -210,16 +176,26 @@ def _discovery_operation_card(
210176
"""Add compact decision facts without recommending a research action."""
211177

212178
runtime = descriptor.provider_runtime
213-
related = [
179+
related = {
180+
item.capability_id: item.model_dump(mode="json")
181+
for item in descriptor.related_capabilities
182+
if item.capability_id in descriptors
183+
and item.capability_id != descriptor.capability_id
184+
}
185+
related.update(
214186
{
215-
"capability_id": related_id,
216-
"relationship": relationship,
187+
related_id: {
188+
"capability_id": related_id,
189+
"relationship": relationship,
190+
}
191+
for related_id, relationship in _RELATED_CAPABILITIES.get(
192+
descriptor.capability_id, ()
193+
)
194+
if related_id in descriptors
195+
and related_id != descriptor.capability_id
196+
and related_id not in related
217197
}
218-
for related_id, relationship in _RELATED_CAPABILITIES.get(
219-
descriptor.capability_id, ()
220-
)
221-
if related_id in descriptors
222-
]
198+
)
223199
invocation_example = None
224200
if descriptor.invocation_examples:
225201
example = descriptor.invocation_examples[0]
@@ -247,7 +223,7 @@ def _discovery_operation_card(
247223
"provider_availability": (
248224
runtime.availability.value if runtime is not None else "UNKNOWN"
249225
),
250-
"related_capabilities": related,
226+
"related_capabilities": [related[item] for item in sorted(related)],
251227
**(
252228
{"invocation_example": invocation_example}
253229
if invocation_example is not None
@@ -258,6 +234,41 @@ def _discovery_operation_card(
258234
}
259235

260236

237+
def _compact_discovery_relationships(
238+
response: dict[str, Any],
239+
matches: list[dict[str, Any]],
240+
) -> None:
241+
"""Remove deterministic suffix links until the discovery response is bounded."""
242+
243+
while (
244+
len(_mcp_text_json_bytes(response)) > CAPABILITY_DISCOVERY_RESPONSE_BYTE_LIMIT
245+
):
246+
for match in reversed(matches):
247+
related = match.get("related_capabilities")
248+
if isinstance(related, list) and related:
249+
related.pop()
250+
response["related_capabilities_truncated"] = True
251+
response["truncation_reason"] = "BYTE_LIMIT"
252+
break
253+
else:
254+
return
255+
256+
257+
def _compact_inspection_relationships(response: dict[str, Any]) -> None:
258+
"""Bound exact-inspection relationships without truncating the descriptor."""
259+
260+
related = response.get("related_capabilities")
261+
while (
262+
len(_mcp_text_json_bytes(related))
263+
> CAPABILITY_INSPECTION_RELATIONSHIPS_BYTE_LIMIT
264+
and isinstance(related, list)
265+
and related
266+
):
267+
related.pop()
268+
response["related_capabilities_truncated"] = True
269+
response["truncation_reason"] = "BYTE_LIMIT"
270+
271+
261272
def _discovery_recovery_paths(
262273
request: CapabilityDiscoveryRequest,
263274
*,
@@ -313,7 +324,7 @@ def _capability_descriptor_view(
313324
view: CapabilityDescriptionView,
314325
) -> dict[str, Any]:
315326
if view == "FULL":
316-
return descriptor.model_dump(mode="json")
327+
return descriptor.model_dump(mode="json", exclude={"related_capabilities"})
317328
runtime = descriptor.provider_runtime
318329
if view == "SUMMARY":
319330
runtime_summary = (
@@ -462,8 +473,13 @@ def _capability_discovery_response(
462473
"recovery_paths_are_unranked": True,
463474
"response_byte_limit": CAPABILITY_DISCOVERY_RESPONSE_BYTE_LIMIT,
464475
"truncation_reason": None,
476+
"related_capabilities_truncated": False,
477+
"available_domains_total": len(discovered_payload["available_domains"]),
478+
"available_domains_truncated": False,
479+
"match_metadata_truncated": False,
465480
}
466481
matches = cast(list[dict[str, Any]], response["matches"])
482+
_compact_discovery_relationships(response, matches)
467483
while (
468484
len(_mcp_text_json_bytes(response)) > CAPABILITY_DISCOVERY_RESPONSE_BYTE_LIMIT
469485
and len(matches) > 1
@@ -473,16 +489,13 @@ def _capability_discovery_response(
473489
response["next_cursor"] = matches[-1]["capability_id"]
474490
response["truncation_reason"] = "BYTE_LIMIT"
475491
available_domains = cast(list[str], response["available_domains"])
476-
response["available_domains_total"] = len(available_domains)
477-
response["available_domains_truncated"] = False
478492
while (
479493
len(_mcp_text_json_bytes(response)) > CAPABILITY_DISCOVERY_RESPONSE_BYTE_LIMIT
480494
and available_domains
481495
):
482496
available_domains.pop()
483497
response["available_domains_truncated"] = True
484498
response["truncation_reason"] = "BYTE_LIMIT"
485-
response["match_metadata_truncated"] = False
486499
compact_fields = (
487500
"tags",
488501
"matched_on",

src/jacobian/adapters/mcp/tools.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,17 @@
1010
from mcp_types import CallToolResult, TextContent
1111
from pydantic import BaseModel, ConfigDict, Field, RootModel, StrictInt
1212

13-
from jacobian.adapters.mcp.constants import _CAPABILITY_SCOPE_RULE, ReasoningLogMode
13+
from jacobian.adapters.mcp.constants import (
14+
_CAPABILITY_SCOPE_RULE,
15+
CAPABILITY_INSPECTION_RELATIONSHIPS_BYTE_LIMIT,
16+
ReasoningLogMode,
17+
)
1418
from jacobian.adapters.mcp.context import AppState, _runtime
1519
from jacobian.adapters.mcp.projections import (
1620
_capability_descriptor_view,
1721
_capability_discovery_response,
1822
_capability_inspection_extensions,
23+
_compact_inspection_relationships,
1924
)
2025
from jacobian.adapters.mcp.tooling import (
2126
AgentRecoveryError,
@@ -84,6 +89,7 @@ class _CapabilityDiscoveryResult(_CapabilityDiscoveryFields):
8489
truncation_reason: str | None = None
8590
available_domains_total: StrictInt
8691
available_domains_truncated: bool
92+
related_capabilities_truncated: bool
8793
match_metadata_truncated: bool
8894

8995

@@ -92,6 +98,9 @@ class _CapabilityInspectionResult(_CapabilityDiscoveryFields):
9298
view: CapabilityDescriptionView
9399
capability: dict[str, Any]
94100
scope_rule: str | dict[str, Any]
101+
related_capabilities_byte_limit: StrictInt
102+
truncation_reason: str | None = None
103+
related_capabilities_truncated: bool
95104
invocations: list[dict[str, Any]] | None = None
96105
related_capabilities: list[dict[str, Any]] | None = None
97106
synchronous_execution: dict[str, Any] | None = None
@@ -198,6 +207,10 @@ def _find_text_projection(response: dict[str, Any]) -> dict[str, Any]:
198207
"routing_basis": response.get("routing_basis"),
199208
"total_matches": response.get("total_matches"),
200209
"truncated": response.get("truncated"),
210+
"truncation_reason": response.get("truncation_reason"),
211+
"related_capabilities_truncated": response.get(
212+
"related_capabilities_truncated"
213+
),
201214
"next_cursor": response.get("next_cursor"),
202215
"available_recovery_paths": response.get("available_recovery_paths"),
203216
}
@@ -229,6 +242,13 @@ def _find_text_projection(response: dict[str, Any]) -> dict[str, Any]:
229242
projection["invocations"] = response["invocations"]
230243
if response.get("related_capabilities"):
231244
projection["related_capabilities"] = response["related_capabilities"]
245+
projection["related_capabilities_byte_limit"] = response.get(
246+
"related_capabilities_byte_limit"
247+
)
248+
projection["truncation_reason"] = response.get("truncation_reason")
249+
projection["related_capabilities_truncated"] = response.get(
250+
"related_capabilities_truncated"
251+
)
232252
return projection
233253

234254

@@ -410,6 +430,11 @@ async def capability_describe(
410430
"policy_digest": capability_catalog.policy_digest,
411431
"capability": _capability_descriptor_view(descriptor, view=view),
412432
"scope_rule": _CAPABILITY_SCOPE_RULE,
433+
"related_capabilities_byte_limit": (
434+
CAPABILITY_INSPECTION_RELATIONSHIPS_BYTE_LIMIT
435+
),
436+
"truncation_reason": None,
437+
"related_capabilities_truncated": False,
413438
}
414439
if view == "SUMMARY":
415440
response["next_views"] = {
@@ -471,6 +496,7 @@ async def capability_describe(
471496
else {"status": "UNAVAILABLE", "detail": None}
472497
),
473498
}
499+
_compact_inspection_relationships(response)
474500
return _find_result(response)
475501

476502

src/jacobian/capability_dispatch.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def invoke(self: Any, request: CapabilityRequest) -> CapabilityResult:
4747
result = _unknown_capability_failure(self, request)
4848
log_invocation(result, started)
4949
return result
50-
descriptor = adapter.descriptor
50+
descriptor = self._descriptors[request.capability_id]
5151
resolution = _capability_resolution_failure(self, request, descriptor)
5252
if resolution is not None:
5353
log_invocation(resolution, started)

0 commit comments

Comments
 (0)