{"record":{"id":"5e8de0507b21711d","repo":"koala73/worldmonitor","slug":"invalid-company-patch","errorCode":null,"errorMessage":"INVALID_COMPANY_PATCH","messagePattern":"INVALID_COMPANY_PATCH","errorType":"exception","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/companyMonitoring/companies.ts","lineNumber":211,"sourceCode":"  handler: async (ctx, args) => {\n    const account = await requireActiveAccount(ctx, args.ownerUserId);\n    const company = await ctx.db\n      .query(\"companyMonitoringCompanies\")\n      .withIndex(\"by_account_companyId\", (q) =>\n        q.eq(\"ownerAccountId\", account.logicalAccountId).eq(\"companyId\", args.companyId),\n      )\n      .unique();\n    if (!company || company.lifecycle === \"removed\" || !company.name || !company.domicileCountry) {\n      throw new ConvexError(\"NOT_FOUND\");\n    }\n    const patch = args.patch;\n    const addClaimInputs = patch.addClaims ?? [];\n    const removeClaimInputs = patch.removeClaimIds ?? [];\n    if (\n      addClaimInputs.length > COMPANY_MONITORING_LIMITS.maxClaimsPerCompany ||\n      removeClaimInputs.length > COMPANY_MONITORING_LIMITS.maxClaimsPerCompany\n    ) {\n      throw new ConvexError(\"INVALID_COMPANY_PATCH\");\n    }\n\n    const hasName = Object.prototype.hasOwnProperty.call(patch, \"name\");\n    const hasDomicile = Object.prototype.hasOwnProperty.call(patch, \"domicileCountry\");\n    const hasCustomerReference = Object.prototype.hasOwnProperty.call(patch, \"customerReference\");\n    const normalizedFields = normalizeMonitoredCompanyInput({\n      name: hasName ? patch.name! : company.name,\n      domicileCountry: hasDomicile ? patch.domicileCountry! : company.domicileCountry,\n      customerReference: hasCustomerReference\n        ? patch.customerReference\n        : company.customerReference,\n    });\n    if (hasCustomerReference && normalizedFields.customerReference) {\n      const conflict = await findNoopByCustomerReference(\n        ctx,\n        account.logicalAccountId,\n        normalizedFields.customerReference,\n      );","sourceCodeStart":193,"sourceCodeEnd":229,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/companyMonitoring/companies.ts#L193-L229","documentation":"Thrown by updateCompanyForOwner when the patch's claim mutation arrays exceed COMPANY_MONITORING_LIMITS.maxClaimsPerCompany. The check runs on the raw addClaims and removeClaimIds lengths before any deduplication, so the limit applies to the request size, not the resulting claim set.","triggerScenarios":"Submitting a patch with addClaims.length > maxClaimsPerCompany or removeClaimIds.length > maxClaimsPerCompany in a single updateCompanyForOwner call.","commonSituations":"Bulk-import UI that sends the entire claim list in one patch; a migration script that batches too many claim edits per company; client not paginating claim removals.","solutions":["Split the claim additions/removals into batches each no larger than COMPANY_MONITORING_LIMITS.maxClaimsPerCompany.","Read the limit from the shared company-monitoring-contract on the client and cap the patch size before sending.","Prefer targeted single-claim edits over wholesale list replacement."],"exampleFix":"// before\nawait update({ companyId, patch: { addClaims: allClaims, removeClaimIds: allRemovals } });\n\n// after\nconst LIMIT = COMPANY_MONITORING_LIMITS.maxClaimsPerCompany;\nfor (let i = 0; i < allClaims.length; i += LIMIT) {\n  await update({ companyId, patch: { addClaims: allClaims.slice(i, i + LIMIT) } });\n}\nfor (let i = 0; i < allRemovals.length; i += LIMIT) {\n  await update({ companyId, patch: { removeClaimIds: allRemovals.slice(i, i + LIMIT) } });\n}","handlingStrategy":"validation","validationCode":"import { COMPANY_MONITORING_LIMITS } from \"shared/company-monitoring-contract\";\nconst LIMIT = COMPANY_MONITORING_LIMITS.maxClaimsPerCompany;\nif ((patch.addClaims?.length ?? 0) > LIMIT || (patch.removeClaimIds?.length ?? 0) > LIMIT) {\n  throw new Error(\"Claim patch exceeds per-company limit\");\n}","typeGuard":"function patchWithinClaimLimit(patch: { addClaims?: unknown[]; removeClaimIds?: unknown[] }, limit: number): boolean {\n  return (patch.addClaims?.length ?? 0) <= limit && (patch.removeClaimIds?.length ?? 0) <= limit;\n}","tryCatchPattern":null,"preventionTips":["Paginate claim additions and removals in batches of maxClaimsPerCompany.","Read the limit from the shared contract, never hard-code it."],"tags":["convex","validation","limits","company-monitoring"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}