diff --git a/packages/validation/api.test.ts b/packages/validation/api.test.ts index e186af88f..959974cb6 100644 --- a/packages/validation/api.test.ts +++ b/packages/validation/api.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "bun:test" import { readFileSync } from "node:fs" import { + BulkDeleteMemoriesSchema, DocumentsWithMemoriesQuerySchema, ListMemoriesQuerySchema, SearchRequestSchema, @@ -151,4 +152,22 @@ describe("pagination query schemas", () => { expect(parsed.page).toBe(2) expect(parsed.limit).toBe(50) }) + + it("DocumentsWithMemoriesQuerySchema caps limit at 100", () => { + expect(DocumentsWithMemoriesQuerySchema.safeParse({ limit: 101 }).success) + .toBe(false) + }) + + it("BulkDeleteMemoriesSchema caps containerTags at 100 entries of bounded length", () => { + const tooMany = { + containerTags: Array.from({ length: 101 }, (_, i) => `tag_${i}`), + } + expect(BulkDeleteMemoriesSchema.safeParse(tooMany).success).toBe(false) + + const tagTooLong = { containerTags: ["x".repeat(257)] } + expect(BulkDeleteMemoriesSchema.safeParse(tagTooLong).success).toBe(false) + + const ok = { containerTags: ["tag_a", "tag_b"] } + expect(BulkDeleteMemoriesSchema.safeParse(ok).success).toBe(true) + }) }) diff --git a/packages/validation/api.ts b/packages/validation/api.ts index f066bfcd4..f1700c6bb 100644 --- a/packages/validation/api.ts +++ b/packages/validation/api.ts @@ -1102,8 +1102,11 @@ export const DocumentsWithMemoriesQuerySchema = z description: "Page number to fetch", example: 1, }), - limit: z.number().int().min(1).default(10).openapi({ - description: "Number of items per page", + // Capped like every sibling list schema (SearchRequest <= 100, + // ListMemories <= 1100): each row expands joined memoryEntries, so an + // unbounded limit here is a memory/CPU/cost amplifier. + limit: z.number().int().min(1).max(100).default(10).openapi({ + description: "Number of items per page (max 100)", example: 10, }), sort: z.enum(["createdAt", "updatedAt"]).default("createdAt").openapi({ @@ -1408,13 +1411,17 @@ export const BulkDeleteMemoriesSchema = z description: "Array of memory IDs to delete (max 100 at once)", example: ["acxV5LHMEsG2hMSNb4umbn", "bxcV5LHMEsG2hMSNb4umbn"], }), + // Bounded like the ids array above: this is the most destructive + // operation in the schema ("delete ALL memories in these containers"), + // so the tag list must not be an unbounded fan-out vector. containerTags: z - .array(z.string()) + .array(z.string().max(256)) .min(1) + .max(100) .optional() .openapi({ description: - "Array of container tags - all memories in these containers will be deleted", + "Array of container tags - all memories in these containers will be deleted (max 100 at once)", example: ["user_123", "project_123"], }), })