{"record":{"id":"f7b5f07c12099c4c","repo":"multica-ai/multica","slug":"too-many-runtime-providers","errorCode":null,"errorMessage":"too many runtime providers","messagePattern":"too many runtime providers","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"server/internal/handler/client_usage.go","lineNumber":206,"sourceCode":"\tif result != \"success\" && result != \"error\" {\n\t\treturn validatedRuntimeProbe{}, errors.New(\"runtime probe_result must be success or error\")\n\t}\n\tvalidated := validatedRuntimeProbe{Result: pgtype.Text{String: result, Valid: true}}\n\tif result == \"error\" {\n\t\tif probe.RuntimeCount != nil || probe.ProviderSummary != nil || probe.OnlineCount != nil || probe.OfflineCount != nil {\n\t\t\treturn validatedRuntimeProbe{}, errors.New(\"failed runtime probes must not include counts\")\n\t\t}\n\t\treturn validated, nil\n\t}\n\n\tif probe.RuntimeCount == nil || probe.ProviderSummary == nil || probe.OnlineCount == nil || probe.OfflineCount == nil {\n\t\treturn validatedRuntimeProbe{}, errors.New(\"successful runtime probes require all counts\")\n\t}\n\tif *probe.RuntimeCount < 0 || *probe.RuntimeCount > 1000 || *probe.OnlineCount < 0 || *probe.OfflineCount < 0 || *probe.OnlineCount+*probe.OfflineCount != *probe.RuntimeCount {\n\t\treturn validatedRuntimeProbe{}, errors.New(\"invalid runtime counts\")\n\t}\n\tif len(probe.ProviderSummary) > 32 {\n\t\treturn validatedRuntimeProbe{}, errors.New(\"too many runtime providers\")\n\t}\n\tvar providerTotal int64\n\tfor provider, count := range probe.ProviderSummary {\n\t\tif !providerNamePattern.MatchString(provider) || count < 0 || count > 1000 {\n\t\t\treturn validatedRuntimeProbe{}, errors.New(\"invalid runtime provider summary\")\n\t\t}\n\t\tproviderTotal += int64(count)\n\t}\n\tif providerTotal != int64(*probe.RuntimeCount) {\n\t\treturn validatedRuntimeProbe{}, errors.New(\"runtime provider counts do not match runtime_count\")\n\t}\n\tsummary, err := json.Marshal(probe.ProviderSummary)\n\tif err != nil {\n\t\treturn validatedRuntimeProbe{}, errors.New(\"invalid runtime provider summary\")\n\t}\n\tvalidated.RuntimeCount = pgtype.Int4{Int32: *probe.RuntimeCount, Valid: true}\n\tvalidated.ProviderSummary = summary\n\tvalidated.OnlineCount = pgtype.Int4{Int32: *probe.OnlineCount, Valid: true}","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/internal/handler/client_usage.go#L188-L224","documentation":"validateClientUsageRuntime rejects a success probe whose provider_summary map contains more than 32 entries. The summary is stored as a JSON blob, and the entry cap keeps the per-report payload and stored row size bounded. It is a pure cardinality limit on map keys, independent of the counts' correctness (which is checked separately).","triggerScenarios":"POST a success probe with a provider_summary containing 33+ distinct provider keys, e.g. synthesized per-provider or per-version keys like 'openai/gpt-4', 'openai/gpt-4o', ... crossing 32 entries.","commonSituations":"Client keying the summary by provider+model+version instead of provider alone, exploding cardinality; a bug generating dynamic provider names (e.g. embedding an id or timestamp in the key); test fixtures enumerating many fake providers.","solutions":["Aggregate the summary by top-level provider only (<=32 distinct providers is the expected domain).","Fold long-tail providers into an 'other' bucket once the map exceeds ~30 keys.","If genuinely more than 32 providers, report the top N by count and merge the rest.","Add a client-side guard: if Object.keys(summary).length > 32, merge smallest buckets before sending."],"exampleFix":"// before\nconst summary = {};\nfor (const rt of runtimes) summary[`${rt.provider}/${rt.version}`] = (summary[`${rt.provider}/${rt.version}`] || 0) + 1;\n\n// after\nconst summary = {};\nfor (const rt of runtimes) summary[rt.provider] = (summary[rt.provider] || 0) + 1;\nif (Object.keys(summary).length > 32) mergeSmallestIntoOther(summary);","handlingStrategy":"validation","validationCode":"function capProviders(summary, max = 32) {\n  const keys = Object.keys(summary);\n  if (keys.length <= max) return summary;\n  // fold smallest buckets into 'other' to stay within the cap\n  const sorted = keys.sort((a, b) => summary[a] - summary[b]);\n  const out = {};\n  let other = 0;\n  sorted.forEach((k, i) => {\n    if (i < max - 1) out[k] = summary[k]; else other += summary[k];\n  });\n  if (other) out.other = other;\n  return out;\n}","typeGuard":"const providerCountWithinCap = (summary) => Object.keys(summary).length <= 32;","tryCatchPattern":null,"preventionTips":["Key the summary by bare provider name — never provider+version or other composite keys.","Add the 'other' bucket strategy to the reporter so the cap can never be exceeded organically.","Monitor cardinality client-side: if providers exceed ~30, that is a key-generation bug until proven otherwise."],"tags":["validation","telemetry","cardinality-limit","http-400"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}