mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
fix(browser): preserve unknown WebMCP comparison facts (#7833)
This commit is contained in:
1 parent
6c76a7ffb0
commit
cc5a4062ff
2 files changed
+199
-31
No files matched your search
+122
-31
@@ -827,7 +827,6 @@ const webMcpDimension = (
|
||||
before: WebMcpDiscovery | null,
|
||||
after: WebMcpDiscovery | null,
|
||||
): Dimension => {
|
||||
type Tool = WebMcpDiscovery["tools"]["items"][number];
|
||||
const groups = (discovery: WebMcpDiscovery | null) => {
|
||||
const result = new Map<string, Tool[]>();
|
||||
for (const tool of discovery?.tools.items ?? []) {
|
||||
@@ -875,41 +874,21 @@ const webMcpDimension = (
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const beforeTool = beforeTools[0];
|
||||
const afterTool = afterTools[0];
|
||||
const consequentialKnown =
|
||||
beforeTool?.annotations.consequential !== undefined &&
|
||||
afterTool?.annotations.consequential !== undefined;
|
||||
if (
|
||||
beforeTool !== undefined &&
|
||||
afterTool !== undefined &&
|
||||
!consequentialKnown
|
||||
)
|
||||
valuesComplete = false;
|
||||
const project = (tool: Tool) => {
|
||||
const {
|
||||
tool_key: _key,
|
||||
frame_id: _frameId,
|
||||
annotations,
|
||||
...content
|
||||
} = tool;
|
||||
const { consequential, ...otherAnnotations } = annotations;
|
||||
return digestCanonicalValue({
|
||||
...content,
|
||||
annotations: {
|
||||
...otherAnnotations,
|
||||
...(consequentialKnown ? { consequential } : {}),
|
||||
},
|
||||
});
|
||||
};
|
||||
if (beforeTool !== undefined) left.set(identity, project(beforeTool));
|
||||
if (afterTool !== undefined) right.set(identity, project(afterTool));
|
||||
const projection = projectWebMcpTools(
|
||||
beforeTools[0],
|
||||
afterTools[0],
|
||||
beforeComplete,
|
||||
afterComplete,
|
||||
);
|
||||
if (!projection.complete) valuesComplete = false;
|
||||
if (projection.before !== undefined) left.set(identity, projection.before);
|
||||
if (projection.after !== undefined) right.set(identity, projection.after);
|
||||
}
|
||||
return compareDimension(
|
||||
left,
|
||||
right,
|
||||
inventoryComplete && valuesComplete,
|
||||
"WebMCP discovery, owner correspondence, or consequential annotation evidence was unavailable or incomplete in at least one capture.",
|
||||
"WebMCP discovery, owner correspondence, or declaration evidence was unavailable or incomplete in at least one capture.",
|
||||
(identity) =>
|
||||
!previous.has(identity)
|
||||
? beforeComplete
|
||||
@@ -919,6 +898,118 @@ const webMcpDimension = (
|
||||
);
|
||||
};
|
||||
|
||||
type Tool = WebMcpDiscovery["tools"]["items"][number];
|
||||
|
||||
/** Compare only declaration facts retained by both captures. */
|
||||
const projectWebMcpTools = (
|
||||
beforeTool: Tool | undefined,
|
||||
afterTool: Tool | undefined,
|
||||
beforeComplete: boolean,
|
||||
afterComplete: boolean,
|
||||
): {
|
||||
before: string | undefined;
|
||||
after: string | undefined;
|
||||
complete: boolean;
|
||||
} => {
|
||||
let valuesComplete = true;
|
||||
const inventoryComplete = beforeComplete && afterComplete;
|
||||
const paired = beforeTool !== undefined && afterTool !== undefined;
|
||||
const consequentialKnown =
|
||||
beforeTool?.annotations.consequential !== undefined &&
|
||||
afterTool?.annotations.consequential !== undefined;
|
||||
if (
|
||||
beforeTool !== undefined &&
|
||||
afterTool !== undefined &&
|
||||
!consequentialKnown
|
||||
)
|
||||
valuesComplete = false;
|
||||
// A missing stack or malformed coordinate is not a different source.
|
||||
// Compare only jointly observed components, but retain unknown coverage.
|
||||
const beforeSource = beforeTool?.registration_source;
|
||||
const afterSource = afterTool?.registration_source;
|
||||
const sourcePresent = beforeSource != null && afterSource != null;
|
||||
const bothAbsent =
|
||||
beforeSource === null && afterSource === null && inventoryComplete;
|
||||
const sourceKnown = sourcePresent || bothAbsent;
|
||||
const lineKnown =
|
||||
sourcePresent && beforeSource.line !== null && afterSource.line !== null;
|
||||
const columnKnown =
|
||||
sourcePresent &&
|
||||
beforeSource.column !== null &&
|
||||
afterSource.column !== null;
|
||||
if (
|
||||
paired &&
|
||||
(!sourceKnown || (sourcePresent && (!lineKnown || !columnKnown)))
|
||||
)
|
||||
valuesComplete = false;
|
||||
const schemaKnown =
|
||||
!paired ||
|
||||
((beforeComplete || beforeTool.input_schema_sha256 !== null) &&
|
||||
(afterComplete || afterTool.input_schema_sha256 !== null));
|
||||
const annotationKeys = [
|
||||
"read_only",
|
||||
"untrusted_content",
|
||||
"autosubmit",
|
||||
] as const;
|
||||
const knownAnnotations = new Set(
|
||||
annotationKeys.filter(
|
||||
(key) =>
|
||||
!paired ||
|
||||
((beforeComplete || beforeTool.annotations[key] !== null) &&
|
||||
(afterComplete || afterTool.annotations[key] !== null)),
|
||||
),
|
||||
);
|
||||
if (
|
||||
paired &&
|
||||
(!schemaKnown || knownAnnotations.size !== annotationKeys.length)
|
||||
)
|
||||
valuesComplete = false;
|
||||
const project = (tool: Tool) => {
|
||||
const {
|
||||
tool_key: _key,
|
||||
frame_id: _frameId,
|
||||
annotations,
|
||||
registration_source: source,
|
||||
input_schema: schema,
|
||||
input_schema_sha256: schemaDigest,
|
||||
...content
|
||||
} = tool;
|
||||
return digestCanonicalValue({
|
||||
...content,
|
||||
...(schemaKnown
|
||||
? { input_schema: schema, input_schema_sha256: schemaDigest }
|
||||
: {}),
|
||||
...(sourceKnown
|
||||
? {
|
||||
registration_source:
|
||||
source === null
|
||||
? null
|
||||
: {
|
||||
url: source.url,
|
||||
...(lineKnown ? { line: source.line } : {}),
|
||||
...(columnKnown ? { column: source.column } : {}),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
annotations: {
|
||||
...Object.fromEntries(
|
||||
annotationKeys
|
||||
.filter((key) => knownAnnotations.has(key))
|
||||
.map((key) => [key, annotations[key]]),
|
||||
),
|
||||
...(consequentialKnown
|
||||
? { consequential: annotations.consequential }
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
};
|
||||
return {
|
||||
before: beforeTool === undefined ? undefined : project(beforeTool),
|
||||
after: afterTool === undefined ? undefined : project(afterTool),
|
||||
complete: valuesComplete,
|
||||
};
|
||||
};
|
||||
|
||||
const webMcpComplete = (discovery: WebMcpDiscovery | null): boolean =>
|
||||
discovery !== null &&
|
||||
discovery.status === "available" &&
|
||||
|
||||
@@ -251,6 +251,81 @@ add(
|
||||
"webmcp",
|
||||
"changed",
|
||||
);
|
||||
const knownSource = structuredClone(discovery);
|
||||
knownSource.tools.items[0]!.registration_source = {
|
||||
url: "https://app.example.test/app.js",
|
||||
line: 12,
|
||||
column: 3,
|
||||
};
|
||||
const malformedSource = structuredClone(discovery);
|
||||
malformedSource.completeness.unavailable_sections.push("webmcp_tools");
|
||||
malformedSource.completeness.excluded.push({
|
||||
section: "webmcp_tools",
|
||||
reason: "invalid_protocol_value",
|
||||
count: 1,
|
||||
});
|
||||
for (const [name, before, after] of [
|
||||
["WebMCP unknown registration source", knownSource, malformedSource],
|
||||
["WebMCP unknown registration source reversed", malformedSource, knownSource],
|
||||
] as const)
|
||||
add(
|
||||
name,
|
||||
{ inspection, webmcp: before },
|
||||
{ inspection, webmcp: after },
|
||||
"webmcp",
|
||||
"unknown",
|
||||
);
|
||||
const malformedChanged = structuredClone(malformedSource);
|
||||
malformedChanged.tools.items[0]!.description = "Changed description";
|
||||
add(
|
||||
"WebMCP known description change despite unknown source",
|
||||
{ inspection, webmcp: knownSource },
|
||||
{ inspection, webmcp: malformedChanged },
|
||||
"webmcp",
|
||||
"changed",
|
||||
);
|
||||
const otherSource = structuredClone(knownSource);
|
||||
otherSource.tools.items[0]!.registration_source!.line = 13;
|
||||
add(
|
||||
"WebMCP known registration source change",
|
||||
{ inspection, webmcp: knownSource },
|
||||
{ inspection, webmcp: otherSource },
|
||||
"webmcp",
|
||||
"changed",
|
||||
);
|
||||
const missingCoordinate = structuredClone(knownSource);
|
||||
missingCoordinate.tools.items[0]!.registration_source!.line = null;
|
||||
add(
|
||||
"WebMCP unknown registration coordinate",
|
||||
{ inspection, webmcp: knownSource },
|
||||
{ inspection, webmcp: missingCoordinate },
|
||||
"webmcp",
|
||||
"unknown",
|
||||
);
|
||||
const malformedAnnotation = structuredClone(discovery);
|
||||
malformedAnnotation.completeness = structuredClone(
|
||||
malformedSource.completeness,
|
||||
);
|
||||
malformedAnnotation.tools.items[0]!.annotations.read_only = null;
|
||||
add(
|
||||
"WebMCP malformed annotation",
|
||||
{ inspection, webmcp: discovery },
|
||||
{ inspection, webmcp: malformedAnnotation },
|
||||
"webmcp",
|
||||
"unknown",
|
||||
);
|
||||
const knownSchema = structuredClone(discovery);
|
||||
knownSchema.tools.items[0]!.input_schema = { type: "object" };
|
||||
knownSchema.tools.items[0]!.input_schema_sha256 = "b".repeat(64);
|
||||
const malformedSchema = structuredClone(discovery);
|
||||
malformedSchema.completeness = structuredClone(malformedSource.completeness);
|
||||
add(
|
||||
"WebMCP malformed schema",
|
||||
{ inspection, webmcp: knownSchema },
|
||||
{ inspection, webmcp: malformedSchema },
|
||||
"webmcp",
|
||||
"unknown",
|
||||
);
|
||||
const droppedTools = structuredClone(discovery);
|
||||
droppedTools.completeness = withPassiveDroppedEvents(inspection, {
|
||||
webmcp_tools: 1,
|
||||
@@ -413,6 +488,8 @@ const cliCaseNames = new Set([
|
||||
"observed HTTP value absent from complete peer",
|
||||
"same-length partial WebSocket streams",
|
||||
"WebMCP capture-local identity renamed",
|
||||
"WebMCP unknown registration source",
|
||||
"WebMCP known description change despite unknown source",
|
||||
"script retained digest change without producer hash",
|
||||
"script digest unavailable",
|
||||
]);
|
||||
|
||||
Reference in new issue
Block a user