mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
fix(enhanced): bound batch decompile input size (#5937)
This commit is contained in:
1 parent
fc3ff7b833
commit
585fe56712
4 files changed
+106
No files matched your search
@@ -0,0 +1,35 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { z } from "zod";
|
||||
|
||||
import type { AnalysisOperationPort } from "./AnalysisProvider.js";
|
||||
import { EnhancedTools } from "./EnhancedTools.js";
|
||||
import { AnalysisInputError } from "../domain/analysisErrorCore.js";
|
||||
import { MAX_ENHANCED_RECORDS } from "../contracts/enhancedInputs.js";
|
||||
|
||||
describe("EnhancedTools input validation", () => {
|
||||
it("rejects an oversized batch_decompile before provider admission", async () => {
|
||||
let calls = 0;
|
||||
const analysis: AnalysisOperationPort = {
|
||||
execute: async () => {
|
||||
calls += 1;
|
||||
throw new Error("provider must not be called for invalid input");
|
||||
},
|
||||
};
|
||||
|
||||
const result = await new EnhancedTools(analysis).execute(
|
||||
"batch_decompile",
|
||||
{
|
||||
addresses: Array.from(
|
||||
{ length: MAX_ENHANCED_RECORDS + 1 },
|
||||
() => "0x0",
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) return;
|
||||
expect(result.error).toBeInstanceOf(AnalysisInputError);
|
||||
expect(result.error.cause).toBeInstanceOf(z.ZodError);
|
||||
expect(calls).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Ajv2020 } from "ajv/dist/2020.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
enhancedInputSchemas,
|
||||
MAX_ENHANCED_RECORDS,
|
||||
} from "./enhancedInputs.js";
|
||||
import { advertisedInputJsonSchema } from "./toolSchemaMetadata.js";
|
||||
import { toolContract } from "./toolContracts.js";
|
||||
|
||||
describe("batch_decompile input contract", () => {
|
||||
it("bounds addresses in the canonical and advertised schemas", () => {
|
||||
const addresses = Array.from({ length: MAX_ENHANCED_RECORDS }, () => "0x0");
|
||||
const schema = enhancedInputSchemas.batch_decompile;
|
||||
expect(schema.safeParse({ addresses }).success).toBe(true);
|
||||
expect(schema.safeParse({ addresses: [...addresses, "0x1"] }).success).toBe(
|
||||
false,
|
||||
);
|
||||
|
||||
const advertised = advertisedInputJsonSchema(
|
||||
toolContract("batch_decompile"),
|
||||
);
|
||||
const advertisedAddresses = (
|
||||
advertised.properties as Record<string, unknown>
|
||||
).addresses;
|
||||
expect(advertisedAddresses).toMatchObject({
|
||||
type: "array",
|
||||
maxItems: MAX_ENHANCED_RECORDS,
|
||||
});
|
||||
|
||||
const ajv = new Ajv2020({ strict: false, validateFormats: false });
|
||||
expect(ajv.validateSchema(advertised)).toBe(true);
|
||||
const validate = ajv.compile(advertised);
|
||||
expect(validate({ addresses })).toBe(true);
|
||||
expect(validate({ addresses: [...addresses, "0x1"] })).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -26,6 +26,7 @@ export const enhancedInputSchemas = {
|
||||
batch_decompile: z.strictObject({
|
||||
addresses: z
|
||||
.array(z.string().describe("A provider-normalized procedure address"))
|
||||
.max(MAX_ENHANCED_RECORDS)
|
||||
.default([]),
|
||||
}),
|
||||
decompile_all: decompileDirectoryInputSchema,
|
||||
|
||||
@@ -12,6 +12,7 @@ import { batchDecompileResultSchema } from "../../../src/domain/native/batchDeco
|
||||
import { ENHANCED_TOOL_CONTRACTS } from "../../../src/contracts/enhancedToolContracts.js";
|
||||
import type { RelationshipCoverage } from "../../../src/domain/relationshipCoverage.js";
|
||||
import { err, ok as resultOk } from "../../../src/domain/result.js";
|
||||
import { MAX_ENHANCED_RECORDS } from "../../../src/contracts/enhancedInputs.js";
|
||||
|
||||
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
||||
import {
|
||||
@@ -485,6 +486,38 @@ describe("enhanced MCP batch cancellation", () => {
|
||||
});
|
||||
|
||||
describe("enhanced MCP batches", () => {
|
||||
it("advertises and enforces the batch input budget before provider calls", async () => {
|
||||
let calls = 0;
|
||||
const client = await connect({
|
||||
execute: async () => {
|
||||
calls += 1;
|
||||
throw new Error("provider must not be called for invalid input");
|
||||
},
|
||||
});
|
||||
const { tools } = await client.listTools();
|
||||
expect(
|
||||
tools.find(({ name }) => name === "batch_decompile")?.inputSchema,
|
||||
).toMatchObject({
|
||||
properties: { addresses: { maxItems: MAX_ENHANCED_RECORDS } },
|
||||
});
|
||||
|
||||
const result = await client.callTool({
|
||||
name: "batch_decompile",
|
||||
arguments: {
|
||||
addresses: Array.from(
|
||||
{ length: MAX_ENHANCED_RECORDS + 1 },
|
||||
() => "0x0",
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.isError).toBe(true);
|
||||
expect(parseMcpToolError(result)).toMatchObject({
|
||||
error: { code: "invalid_request", category: "invalid_input" },
|
||||
});
|
||||
expect(calls).toBe(0);
|
||||
});
|
||||
|
||||
it("returns every batch item in caller order with its own result", async () => {
|
||||
const addresses = Array.from(
|
||||
{ length: 37 },
|
||||
|
||||
Reference in new issue
Block a user