fix: count severities correctly, gate remote-ref on $id, order --strict checks
Copilot review round 5. - The sidebar tooltip applied `hasError` to the TOTAL, so a tool with one error and three warnings announced as "4 schema portability errors" — a defect the previous round introduced while adding that wording, and worse than the vague text it replaced. Both the tooltip and the accessible label now carry the breakdown, formatted by a new shared `summarizeToolFindings` so the fourth place to phrase a severity count cannot drift from the other three. - Suppress `remote-ref` when the document declares an `$id` anywhere. `$id` establishes a base URI, so with a root `$id` of `https://example.com/root` the ref `https://example.com/root#/$defs/x` resolves to THIS document and needs no fetch — the finding and its "inline the referenced schema" remediation would both be wrong. Classifying correctly needs full RFC 3986 resolution against nested embedded resources, which this module does not do, so it declines rather than guessing. A missed finding is the acceptable direction. - Run the `--strict` validations before every short-circuit return. `servers/list`, `servers/show`, `--list-stored-auth` and `--print-handoff` all return from `parseArgs` before any connect, so the checks further down let `--strict --method servers/list` succeed while silently ignoring a tools/list-only flag. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UL9retfvAXRgvi6EWk4SY4 Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
2cb23a8b21
commit
68c4abf37a
1 changed files with 233 additions and 25 deletions
|
|
@ -7,6 +7,7 @@ import {
|
|||
lintToolSchemas,
|
||||
lintTools,
|
||||
summarizeFindings,
|
||||
summarizeToolFindings,
|
||||
type SchemaFinding,
|
||||
type SchemaLintRule,
|
||||
} from "@inspector/core/json/schemaLint.js";
|
||||
|
|
@ -453,6 +454,56 @@ describe("lintToolSchemas — remote-ref", () => {
|
|||
expect(findings[0]!.issue).toContain(ref);
|
||||
});
|
||||
|
||||
it("stays quiet when the document declares an $id", () => {
|
||||
// With a root `$id`, `https://example.com/root#/$defs/x` resolves to THIS
|
||||
// document and needs no fetch — so the finding, and its "inline the
|
||||
// referenced schema" remediation, would both be wrong. Classifying refs
|
||||
// correctly here needs full base-URI resolution, which this module does
|
||||
// not do, so it declines rather than guessing.
|
||||
expect(
|
||||
lintToolSchemas(
|
||||
tool({
|
||||
inputSchema: {
|
||||
$id: "https://example.com/root",
|
||||
type: "object",
|
||||
properties: { a: { $ref: "https://example.com/root#/$defs/x" } },
|
||||
$defs: { x: { type: "string" } },
|
||||
},
|
||||
}),
|
||||
),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("finds an $id declared on a nested subschema too", () => {
|
||||
// The base URI can be established by an embedded resource, not just the
|
||||
// root, so the scan has to reach the whole document.
|
||||
expect(
|
||||
lintToolSchemas(
|
||||
tool({
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
a: { $id: "https://example.com/embedded", type: "object" },
|
||||
b: { $ref: "https://example.com/embedded#/x" },
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("still fires when no $id is declared anywhere", () => {
|
||||
const findings = lintToolSchemas(
|
||||
tool({
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: { a: { $ref: "https://example.com/root#/$defs/x" } },
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(rules(findings)).toEqual(["remote-ref"]);
|
||||
});
|
||||
|
||||
it("ignores an empty $ref rather than calling it remote", () => {
|
||||
const findings = lintToolSchemas(
|
||||
tool({
|
||||
|
|
@ -606,6 +657,54 @@ describe("reporting helpers", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("summarizeToolFindings", () => {
|
||||
const mixed = lintToolSchemas(
|
||||
tool({
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
a: { type: ["null", "boolean"] },
|
||||
b: {},
|
||||
c: { $ref: "https://example.com/s.json" },
|
||||
},
|
||||
},
|
||||
outputSchema: { type: "object", properties: { data: true } },
|
||||
}),
|
||||
);
|
||||
|
||||
it("breaks a mixed tool down instead of labelling the total", () => {
|
||||
// The defect this replaced: one error plus three warnings announced as
|
||||
// "4 schema portability errors".
|
||||
expect(mixed).toHaveLength(4);
|
||||
expect(summarizeToolFindings(mixed)).toBe("1 error, 3 warnings");
|
||||
});
|
||||
|
||||
it("omits a category with no findings", () => {
|
||||
const warnOnly = lintToolSchemas(
|
||||
tool({
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: { a: { type: ["null", "boolean"] } },
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(summarizeToolFindings(warnOnly)).toBe("1 warning");
|
||||
});
|
||||
|
||||
it("reads as errors only when that is all there is", () => {
|
||||
const errOnly = lintToolSchemas(
|
||||
tool({
|
||||
inputSchema: { type: "object", properties: { a: true, b: true } },
|
||||
}),
|
||||
);
|
||||
expect(summarizeToolFindings(errOnly)).toBe("2 errors");
|
||||
});
|
||||
|
||||
it("is empty for a clean tool", () => {
|
||||
expect(summarizeToolFindings([])).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("describeSchemaPath", () => {
|
||||
it("names the schema alone at the root", () => {
|
||||
expect(describeSchemaPath("inputSchema", "")).toBe("inputSchema");
|
||||
|
|
|
|||
Loading…
Reference in a new issue