Context (review #362 finding 3)
#362's schema-key guard runs at skill-tool registration and validates top-level property keys only. That fully covers the defect source (flat, platform-materialized skill params), but two vectors can still put a provider-invalid key into the assembled request and 400 every call:
- MCP-server and builtin tool schemas — an MCP server's
tools/list (or a baked Schemas entry for type=user servers) can carry property keys the provider rejects; builtins are trusted today by convention, not by check.
- Nested object keys —
properties.foo.properties["bad name"] passes the top-level check.
Change
Move (or duplicate) the guard to the provider boundary: validate the assembled tools array right before the request is sent (once per toolset change, cached — not per call). On violation:
Validation walks nested properties recursively (bounded depth), same pattern ^[a-zA-Z0-9_.-]{1,64}$.
Non-goals
Renaming/sanitizing keys — a renamed key desyncs the schema from the server/skill contract (the dead-instruction defect class); validate-and-skip only.
Context (review #362 finding 3)
#362's schema-key guard runs at skill-tool registration and validates top-level property keys only. That fully covers the defect source (flat, platform-materialized skill params), but two vectors can still put a provider-invalid key into the assembled request and 400 every call:
tools/list(or a bakedSchemasentry for type=user servers) can carry property keys the provider rejects; builtins are trusted today by convention, not by check.properties.foo.properties["bad name"]passes the top-level check.Change
Move (or duplicate) the guard to the provider boundary: validate the assembled
toolsarray right before the request is sent (once per toolset change, cached — not per call). On violation:llm_call_failedfamily or a dedicatedtool_schema_invalidevent) so it's visible beyond pod logs.Validation walks nested
propertiesrecursively (bounded depth), same pattern^[a-zA-Z0-9_.-]{1,64}$.Non-goals
Renaming/sanitizing keys — a renamed key desyncs the schema from the server/skill contract (the dead-instruction defect class); validate-and-skip only.