This commit is contained in:
1 parent
34f4923566
commit
146aa3a281
3 files changed
+98
-10
No files matched your search
@@ -1285,10 +1285,10 @@ def create_mcp_server(
|
||||
|
||||
@server.tool(name="proposal_submit", structured_output=False)
|
||||
def proposal_submit(proposal_json: str, ctx: Context) -> str:
|
||||
"""Validate and submit one bounded typed proposal revision."""
|
||||
"""Validate and submit one bounded proposal; report any safe input repair."""
|
||||
|
||||
principal = _principal(ctx, identity_provider)
|
||||
proposal = _parse_proposal(proposal_json)
|
||||
proposal, input_repaired = _parse_proposal(proposal_json)
|
||||
context = context_provider()
|
||||
_authorize_proposal(principal, proposal, context)
|
||||
staged_extractions: dict[str, ExtractionRecord] = {}
|
||||
@@ -1321,6 +1321,7 @@ def create_mcp_server(
|
||||
"status": "accepted" if outcome.receipt else outcome.validation.status,
|
||||
"validation_status": outcome.validation.status,
|
||||
"eligible_for_auto_accept": outcome.validation.eligible_for_auto_accept,
|
||||
"input_repaired": input_repaired,
|
||||
"findings": [
|
||||
finding.model_dump(mode="json") for finding in outcome.validation.findings
|
||||
],
|
||||
@@ -1339,8 +1340,20 @@ def create_mcp_server(
|
||||
raise ValueError("Proposal exceeds the supported request size")
|
||||
try:
|
||||
draft = json.loads(proposal_json, object_pairs_hook=_reject_duplicate_keys)
|
||||
except (ValueError, TypeError) as error:
|
||||
raise ValueError("Proposal draft must be valid JSON") from error
|
||||
except (ValueError, TypeError):
|
||||
return json.dumps(
|
||||
{
|
||||
"status": "invalid",
|
||||
"issues": [
|
||||
{
|
||||
"path": "",
|
||||
"code": "invalid_proposal_draft",
|
||||
"message": "Proposal draft must be valid JSON",
|
||||
}
|
||||
],
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
if not isinstance(draft, dict):
|
||||
raise ValueError("Proposal draft must be a JSON object")
|
||||
try:
|
||||
@@ -1378,10 +1391,36 @@ def create_mcp_server(
|
||||
|
||||
@server.tool(name="proposal_evaluate", structured_output=False)
|
||||
def proposal_evaluate(proposal_json: str, ctx: Context) -> str:
|
||||
"""Evaluate one bounded proposal without storing or publishing it."""
|
||||
"""Evaluate one bounded proposal without storing or publishing it.
|
||||
|
||||
A single missing final outer brace is repaired only if the result
|
||||
is valid JSON and matches the full contract. The response exposes
|
||||
input_repaired=true. Other malformed JSON returns status=invalid.
|
||||
A valid proposal may instead return needs-review when an applicable
|
||||
policy requires an owner decision.
|
||||
"""
|
||||
|
||||
principal = _principal(ctx, identity_provider)
|
||||
proposal = _parse_proposal(proposal_json)
|
||||
try:
|
||||
proposal, input_repaired = _parse_proposal(proposal_json)
|
||||
except ValueError:
|
||||
return json.dumps(
|
||||
{
|
||||
"proposal_id": None,
|
||||
"revision": None,
|
||||
"status": "invalid",
|
||||
"eligible_for_auto_accept": False,
|
||||
"findings": [
|
||||
{
|
||||
"code": "PROPOSAL_JSON_INVALID",
|
||||
"message": "Proposal does not match the GEWU v1 contract",
|
||||
"path": "proposal_json",
|
||||
}
|
||||
],
|
||||
"affected_policy_ids": [],
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
context = context_provider()
|
||||
_authorize_proposal(principal, proposal, context)
|
||||
staged_extractions: dict[str, ExtractionRecord] = {}
|
||||
@@ -1408,6 +1447,7 @@ def create_mcp_server(
|
||||
"revision": proposal.revision,
|
||||
"status": report.status,
|
||||
"eligible_for_auto_accept": report.eligible_for_auto_accept,
|
||||
"input_repaired": input_repaired,
|
||||
"findings": [finding.model_dump(mode="json") for finding in report.findings],
|
||||
"affected_policy_ids": report.affected_policy_ids,
|
||||
},
|
||||
@@ -1673,17 +1713,32 @@ def _reject_duplicate_keys(pairs: list[tuple[str, object]]) -> dict[str, object]
|
||||
return result
|
||||
|
||||
|
||||
def _parse_proposal(proposal_json: str) -> ProposalRevision:
|
||||
def _parse_proposal(proposal_json: str) -> tuple[ProposalRevision, bool]:
|
||||
if len(proposal_json.encode("utf-8")) > 1_000_000:
|
||||
raise ValueError("Proposal exceeds the supported request size")
|
||||
try:
|
||||
parsed = json.loads(proposal_json, object_pairs_hook=_reject_duplicate_keys)
|
||||
proposal = ProposalRevision.model_validate_json(proposal_json, strict=True)
|
||||
except json.JSONDecodeError as error:
|
||||
# Tool-call argument serialization can drop the final outer brace while
|
||||
# copying a prepared JSON string. Accept only this unambiguous one-byte
|
||||
# truncation; nested truncation, duplicate keys and schema errors remain
|
||||
# invalid. The response exposes the repair so the caller can report it.
|
||||
candidate = proposal_json.rstrip()
|
||||
if error.pos != len(candidate) or not candidate.startswith("{"):
|
||||
raise ValueError("Proposal does not match the GEWU v1 contract") from error
|
||||
repaired_json = candidate + "}"
|
||||
try:
|
||||
parsed = json.loads(repaired_json, object_pairs_hook=_reject_duplicate_keys)
|
||||
proposal = ProposalRevision.model_validate_json(repaired_json, strict=True)
|
||||
except (ValueError, ValidationError) as repaired_error:
|
||||
raise ValueError("Proposal does not match the GEWU v1 contract") from repaired_error
|
||||
return proposal, True
|
||||
except (ValueError, ValidationError) as error:
|
||||
raise ValueError("Proposal does not match the GEWU v1 contract") from error
|
||||
if not isinstance(parsed, dict):
|
||||
raise ValueError("Proposal must be a JSON object")
|
||||
return proposal
|
||||
return proposal, False
|
||||
|
||||
|
||||
def _parse_run_request[RequestT: (RunCreateRequest, RunCheckpointRequest)](
|
||||
|
||||
Reference in new issue
Block a user